mirror of
https://github.com/nadoo/glider.git
synced 2026-09-28 08:15:49 +08:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38b34030bc | ||
|
|
e7b12e36a9 | ||
|
|
86f9f078fd | ||
|
|
533571f1ec | ||
|
|
73a594aae1 | ||
|
|
cb6f2d1e1a | ||
|
|
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 |
@ -7,7 +7,7 @@ RUN apk add --no-cache ca-certificates
|
||||
ARG TARGETPLATFORM
|
||||
RUN case $TARGETPLATFORM in \
|
||||
'linux/386') \
|
||||
export FOLDER='default_linux_386'; \
|
||||
export FOLDER='default_linux_386_sse2'; \
|
||||
;; \
|
||||
'linux/amd64') \
|
||||
export FOLDER='default_linux_amd64_v1'; \
|
||||
@ -19,10 +19,10 @@ RUN case $TARGETPLATFORM in \
|
||||
export FOLDER='default_linux_arm_7'; \
|
||||
;; \
|
||||
'linux/arm64') \
|
||||
export FOLDER='default_linux_arm64'; \
|
||||
export FOLDER='default_linux_arm64_v8.0'; \
|
||||
;; \
|
||||
'linux/riscv64') \
|
||||
export FOLDER='default_linux_riscv64'; \
|
||||
export FOLDER='default_linux_riscv64_rva20u64'; \
|
||||
;; \
|
||||
*) echo >&2 "error: unsupported architecture '$TARGETPLATFORM'"; exit 1 ;; \
|
||||
esac \
|
||||
|
||||
72
.github/workflows/build.yml
vendored
72
.github/workflows/build.yml
vendored
@ -2,9 +2,9 @@ name: Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'dev'
|
||||
- "dev"
|
||||
tags:
|
||||
- '*'
|
||||
- "*"
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
@ -19,96 +19,63 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set Vars
|
||||
run: |
|
||||
echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
echo "GO_MOD_VERSION=$(grep -P "go \d+\." go.mod | cut -d " " -f2)" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
check-latest: true
|
||||
go-version: ${{ env.GO_MOD_VERSION}}
|
||||
|
||||
- name: Set up Cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
~/.cache/go-build
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
go-version-file: "go.mod"
|
||||
cache: true
|
||||
|
||||
- name: Test
|
||||
run: go test -v .
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Build
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
uses: goreleaser/goreleaser-action@v7
|
||||
if: "!startsWith(github.ref, 'refs/tags/')"
|
||||
with:
|
||||
version: latest
|
||||
args: build --snapshot --rm-dist
|
||||
args: build --snapshot --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload Artifact - Linux amd64
|
||||
uses: actions/upload-artifact@v3
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
if: "!startsWith(github.ref, 'refs/tags/')"
|
||||
with:
|
||||
name: ${{ env.APP_NAME }}-dev-${{ env.SHA_SHORT }}-linux-amd64
|
||||
name: ${{ env.APP_NAME }}-dev-${{ env.SHA_SHORT }}
|
||||
path: |
|
||||
./dist/default_linux_amd64_v1/${{ env.APP_NAME }}
|
||||
|
||||
- name: Upload Artifact - Linux arm64
|
||||
uses: actions/upload-artifact@v3
|
||||
if: "!startsWith(github.ref, 'refs/tags/')"
|
||||
with:
|
||||
name: ${{ env.APP_NAME }}-dev-${{ env.SHA_SHORT }}-linux-arm64
|
||||
path: |
|
||||
./dist/default_linux_arm64/${{ env.APP_NAME }}
|
||||
|
||||
- name: Upload Artifact - macOS arm64
|
||||
uses: actions/upload-artifact@v3
|
||||
if: "!startsWith(github.ref, 'refs/tags/')"
|
||||
with:
|
||||
name: ${{ env.APP_NAME }}-dev-${{ env.SHA_SHORT }}-macos-arm64
|
||||
path: |
|
||||
./dist/default_darwin_arm64/${{ env.APP_NAME }}
|
||||
|
||||
- name: Upload Artifact - Windows amd64
|
||||
uses: actions/upload-artifact@v3
|
||||
if: "!startsWith(github.ref, 'refs/tags/')"
|
||||
with:
|
||||
name: ${{ env.APP_NAME }}-dev-${{ env.SHA_SHORT }}-windows-amd64
|
||||
path: |
|
||||
./dist/default_windows_amd64_v1/${{ env.APP_NAME }}.exe
|
||||
|
||||
- name: Release
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
uses: goreleaser/goreleaser-action@v7
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Docker - Set up Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Docker - Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Docker - Login to GHCR
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@ -116,7 +83,7 @@ jobs:
|
||||
|
||||
- name: Docker - Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: |
|
||||
${{ env.DOCKERHUB_REPO }}
|
||||
@ -127,11 +94,10 @@ jobs:
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
- name: Docker - Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: .Dockerfile
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
- uses: actions/stale@v10
|
||||
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
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -17,12 +17,15 @@
|
||||
# custom
|
||||
.idea
|
||||
.vscode
|
||||
.zed
|
||||
.DS_Store
|
||||
|
||||
# dev test only
|
||||
/dev/
|
||||
.gocache/
|
||||
dev*.go
|
||||
|
||||
*_test.go
|
||||
|
||||
dist
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
version: 2
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
@ -35,13 +37,11 @@ archives:
|
||||
- id: default
|
||||
builds:
|
||||
- default
|
||||
replacements:
|
||||
darwin: macos
|
||||
wrap_in_directory: true
|
||||
format: tar.gz
|
||||
formats: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
formats: zip
|
||||
files:
|
||||
- LICENSE
|
||||
- README.md
|
||||
@ -49,7 +49,7 @@ archives:
|
||||
- systemd/*
|
||||
|
||||
snapshot:
|
||||
name_template: '{{ incpatch .Version }}-dev-{{.ShortCommit}}'
|
||||
version_template: '{{ incpatch .Version }}-dev-{{.ShortCommit}}'
|
||||
|
||||
checksum:
|
||||
name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
|
||||
@ -83,7 +83,7 @@ nfpms:
|
||||
dst: /etc/systemd/system/glider@.service
|
||||
|
||||
- src: config/glider.conf.example
|
||||
dst: /etc/glider/glider.conf
|
||||
dst: /etc/glider/glider.conf.example
|
||||
|
||||
scripts:
|
||||
postinstall: "systemd/postinstall.sh"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Build Stage
|
||||
FROM golang:1.18-alpine AS build-env
|
||||
FROM golang:1.26-alpine AS build-env
|
||||
ADD . /src
|
||||
RUN apk --no-cache add git \
|
||||
&& cd /src && go build -v -ldflags "-s -w"
|
||||
|
||||
80
README.md
80
README.md
@ -1,11 +1,12 @@
|
||||
# [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://go.dev/dl/)
|
||||
[](https://github.com/nadoo/glider/actions)
|
||||
[](https://hub.docker.com/r/nadoo/glider)
|
||||
|
||||
glider is a forward proxy with multiple protocols support, and also a dns/dhcp server with ipset management features(like dnsmasq).
|
||||
glider is a forward proxy with multiple protocols support, and also a dns/dhcp server with ipset management features.
|
||||
|
||||
we can set up local listeners as proxy servers, and forward requests to internet via forwarders.
|
||||
|
||||
@ -51,11 +52,12 @@ we can set up local listeners as proxy servers, and forward requests to internet
|
||||
|HTTP |√| |√| |client & server
|
||||
|SOCKS5 |√|√|√|√|client & server
|
||||
|SS |√|√|√|√|client & server
|
||||
|Trojan |√|√|√|√|client & server
|
||||
|Trojanc |√|√|√|√|trojan cleartext(without tls)
|
||||
|VLESS |√|√|√|√|client & server
|
||||
|Trojan |√| |√|√|client & server
|
||||
|Trojanc |√| |√|√|trojan cleartext(without tls)
|
||||
|AnyTLS |√| |√|√|client & server
|
||||
|AnyTLSc |√| |√|√|anytls cleartext(without tls)
|
||||
|VLESS |√| |√|√|client & server
|
||||
|VMess | | |√|√|client only
|
||||
|SSR | | |√| |client only
|
||||
|SSH | | |√| |client only
|
||||
|SOCKS4 | | |√| |client only
|
||||
|SOCKS4A | | |√| |client only
|
||||
@ -72,7 +74,7 @@ we can set up local listeners as proxy servers, and forward requests to internet
|
||||
|Simple-Obfs | | |√| |transport client only
|
||||
|Redir |√| | | |linux redirect proxy
|
||||
|Redir6 |√| | | |linux redirect proxy(ipv6)
|
||||
|Tproxy | |√| | |linux tproxy(udp only)
|
||||
|TProxy | |√| | |linux tproxy(udp only)
|
||||
|Reject | | |√|√|reject all requests
|
||||
|
||||
</details>
|
||||
@ -81,7 +83,10 @@ we can set up local listeners as proxy servers, and forward requests to internet
|
||||
|
||||
- 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
|
||||
@ -89,10 +94,7 @@ we can set up local listeners as proxy servers, and forward requests to internet
|
||||
#### Run
|
||||
|
||||
```bash
|
||||
glider -config CONFIG_PATH
|
||||
```
|
||||
```bash
|
||||
glider -verbose -listen :8443 -forward SCHEME://HOST:PORT
|
||||
glider -verbose -listen :8443
|
||||
# docker run --rm -it nadoo/glider -verbose -listen :8443
|
||||
```
|
||||
|
||||
@ -117,7 +119,7 @@ OPTION:
|
||||
-checkdisabledonly
|
||||
check disabled fowarders only
|
||||
-checkinterval int
|
||||
fowarder check interval(seconds) (default 30)
|
||||
fowarder check interval(seconds) (default 30)ß
|
||||
-checklatencysamples int
|
||||
use the average latency of the latest N checks (default 10)
|
||||
-checktimeout int
|
||||
@ -196,8 +198,8 @@ URL:
|
||||
-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
|
||||
listen : anytls anytlsc http kcp mixed pxyproto redir redir6 smux sni socks5 ss tcp tls tproxy trojan trojanc udp unix vless vsock ws wss
|
||||
forward: anytls anytlsc direct http kcp reject simple-obfs smux socks4 socks4a socks5 ss ssh 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.
|
||||
|
||||
@ -223,7 +225,7 @@ Help:
|
||||
|
||||
see README.md and glider.conf.example for more details.
|
||||
--
|
||||
glider 0.16.1, https://github.com/nadoo/glider (glider.proxy@gmail.com)
|
||||
glider 0.16.4, https://github.com/nadoo/glider (glider.proxy@gmail.com)
|
||||
```
|
||||
|
||||
</details>
|
||||
@ -234,6 +236,19 @@ glider 0.16.1, https://github.com/nadoo/glider (glider.proxy@gmail.com)
|
||||
<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
|
||||
|
||||
@ -254,6 +269,10 @@ Simple-Obfs scheme:
|
||||
Available types for simple-obfs:
|
||||
http, tls
|
||||
|
||||
--
|
||||
Reject scheme:
|
||||
reject://
|
||||
|
||||
--
|
||||
Smux scheme:
|
||||
smux://host:port
|
||||
@ -284,10 +303,6 @@ 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
|
||||
|
||||
--
|
||||
TLS client scheme:
|
||||
tls://host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&alpn=proto1][&alpn=proto2]
|
||||
@ -316,6 +331,15 @@ 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)
|
||||
|
||||
--
|
||||
AnyTLS client scheme:
|
||||
anytls://password@host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&synackTimeout=10s]
|
||||
anytlsc://password@host:port (cleartext, without TLS)
|
||||
|
||||
AnyTLS server scheme:
|
||||
anytls://password@host:port?cert=PATH&key=PATH[&fallback=127.0.0.1:80]
|
||||
anytlsc://password@host:port[?fallback=127.0.0.1:80] (cleartext, without TLS)
|
||||
|
||||
--
|
||||
Unix domain socket scheme:
|
||||
unix://path
|
||||
@ -395,7 +419,7 @@ Examples:
|
||||
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 -listen -dns=:53 -dnsserver=8.8.8.8:53 -forward socks5://serverA:1080 -dnsrecord=abc.com/1.2.3.4
|
||||
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.
|
||||
```
|
||||
|
||||
@ -404,6 +428,10 @@ Examples:
|
||||
|
||||
## Config
|
||||
|
||||
```bash
|
||||
glider -config CONFIG_PATH
|
||||
```
|
||||
|
||||
- [ConfigFile](config)
|
||||
- [glider.conf.example](config/glider.conf.example)
|
||||
- [office.rule.example](config/rules.d/office.rule.example)
|
||||
@ -422,7 +450,7 @@ Examples:
|
||||
|
||||
## Linux Daemon
|
||||
|
||||
- systemd: [https://github.com/nadoo/glider/blob/master/systemd/](https://github.com/nadoo/glider/blob/master/systemd/)
|
||||
- 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>
|
||||
|
||||
@ -433,14 +461,14 @@ Examples:
|
||||
-v /etc/localtime:/etc/localtime:ro \
|
||||
nadoo/glider -config=/etc/glider/glider.conf
|
||||
```
|
||||
- run watchtower (if you need auto update for glider)
|
||||
- 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)
|
||||
- open udp ports if you need udp nat fullcone
|
||||
```
|
||||
iptables -I INPUT -p udp -m udp --dport 1024:65535 -j ACCEPT
|
||||
```
|
||||
@ -464,7 +492,7 @@ Examples:
|
||||
// _ "github.com/nadoo/glider/proxy/kcp"
|
||||
```
|
||||
|
||||
3. Build it(requires **Go 1.18+** )
|
||||
3. Build it:
|
||||
```bash
|
||||
go build -v -ldflags "-s -w"
|
||||
```
|
||||
@ -516,5 +544,5 @@ Examples:
|
||||
|
||||
- [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`
|
||||
|
||||
16
config.go
16
config.go
@ -92,20 +92,19 @@ check=disable: disable health check`)
|
||||
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)
|
||||
}
|
||||
|
||||
if *scheme != "" {
|
||||
fmt.Fprintf(flag.Output(), proxy.Usage(*scheme))
|
||||
fmt.Fprint(flag.Output(), proxy.Usage(*scheme))
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *example {
|
||||
fmt.Fprintf(flag.Output(), examples)
|
||||
fmt.Fprint(flag.Output(), examples)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
@ -128,6 +127,11 @@ check=disable: disable health check`)
|
||||
proxy.UDPBufSize = conf.UDPBufSize
|
||||
}
|
||||
|
||||
loadRules(conf)
|
||||
return conf
|
||||
}
|
||||
|
||||
func loadRules(conf *Config) {
|
||||
// rulefiles
|
||||
for _, ruleFile := range conf.RuleFiles {
|
||||
if !path.IsAbs(ruleFile) {
|
||||
@ -156,8 +160,6 @@ check=disable: disable health check`)
|
||||
conf.rules = append(conf.rules, rule)
|
||||
}
|
||||
}
|
||||
|
||||
return conf
|
||||
}
|
||||
|
||||
func usage() {
|
||||
@ -247,6 +249,6 @@ Examples:
|
||||
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 -listen -dns=:53 -dnsserver=8.8.8.8:53 -forward socks5://serverA:1080 -dnsrecord=abc.com/1.2.3.4
|
||||
glider -verbose -dns=:53 -dnsserver=8.8.8.8:53 -forward socks5://serverA:1080 -dnsrecord=abc.com/1.2.3.4
|
||||
-dns over proxy: listen on :53 as dns server, forward to 8.8.8.8:53 via socks5 server.
|
||||
`
|
||||
|
||||
@ -31,7 +31,7 @@ forward=ss://method:pass@1.1.1.1:8443
|
||||
# upstream forward proxy (forward chain)
|
||||
forward=http://1.1.1.1:8080,socks5://2.2.2.2:1080
|
||||
|
||||
# multiple upstream proxies forwad strategy
|
||||
# multiple upstream proxies forward strategy
|
||||
strategy=rr
|
||||
|
||||
# forwarder health check
|
||||
|
||||
@ -82,7 +82,8 @@ Set server's nameserver to glider:
|
||||
echo nameserver 127.0.0.1 > /etc/resolv.conf
|
||||
```
|
||||
|
||||
#### Client DNS settings
|
||||
#### Client settings
|
||||
Use the linux server's ip as your gateway.
|
||||
Use the linux server's ip as your dns server.
|
||||
|
||||
#### When client requesting to access http://example1.com (in office.rule), the whole process:
|
||||
@ -91,10 +92,10 @@ DNS Resolving:
|
||||
2. upstream dns server choice: glider will lookup it's rule config and find out the dns server to use for this domain(matched "example1.com" in office.rule, so 208.67.222.222:53 will be chosen)
|
||||
3. glider uses the forwarder in office.rule to ask 208.67.222.222:53 for the resolve answers(dns over proxy).
|
||||
4. glider updates it's office rule config, adds the resolved ip address to it.
|
||||
5. glider adds the resolved ip into ipset "glider", and return the dns answer to client.
|
||||
5. glider adds the resolved ip into ipset "glider", and returns the dns answer to client.
|
||||
|
||||
Destination Accessing:
|
||||
1. client sends http request to the resolved ip of example1.com.
|
||||
2. linux gateway server will get the request.
|
||||
3. iptabes matches the ip in ipset "glider" and redirect this request to :1081(glider)
|
||||
3. iptables matches the ip in ipset "glider" and redirect this request to :1081(glider)
|
||||
4. glider finds the ip in office rule, and then choose a forwarder in office.rule to complete the request.
|
||||
|
||||
@ -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
|
||||
@ -79,10 +80,16 @@ 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
|
||||
|
||||
# anytls server
|
||||
# listen=anytls://PASSWORD@:8443?cert=/path/to/cert&key=/path/to/key&fallback=127.0.0.1:80
|
||||
|
||||
# anytlsc server (anytls without tls)
|
||||
# listen=anytlsc://PASSWORD@:8443?fallback=127.0.0.1:80
|
||||
|
||||
# FORWARDERS
|
||||
# ----------
|
||||
@ -108,9 +115,6 @@ listen=socks5://:1080
|
||||
# SS proxy as forwarder
|
||||
# forward=ss://method:pass@1.1.1.1:8443
|
||||
|
||||
# SSR proxy as forwarder
|
||||
# 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&timeout=SECONDS]
|
||||
# forward=ssh://root:pass@host:port
|
||||
@ -126,6 +130,12 @@ listen=socks5://:1080
|
||||
# trojanc as forwarder
|
||||
# forward=trojanc://PASSWORD@1.1.1.1:8080
|
||||
|
||||
# anytls as forwarder
|
||||
# forward=anytls://PASSWORD@1.1.1.1:8443[?serverName=SERVERNAME][&skipVerify=true]
|
||||
|
||||
# anytlsc as forwarder
|
||||
# forward=anytlsc://PASSWORD@1.1.1.1:8443
|
||||
|
||||
# vless forwarder
|
||||
# forward=vless://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443
|
||||
|
||||
@ -139,7 +149,7 @@ listen=socks5://:1080
|
||||
# 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
|
||||
# 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
|
||||
@ -222,7 +232,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
|
||||
@ -279,7 +289,7 @@ dnsrecord=www.example.com/2606:2800:220:1:248:1893:25c8:1946
|
||||
# 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
|
||||
@ -293,4 +303,4 @@ rules-dir=rules.d
|
||||
# ENVIRONMENT VARIABLES
|
||||
# ----------
|
||||
# use {$ENV_VAR_NAME} in VALUE to get the Environment Variable value.
|
||||
# forwarder=socks5://{$USER_NAME}:{$USER_PASS}@:1080
|
||||
# forward=socks5://{$USER_NAME}:{$USER_PASS}@:1080
|
||||
|
||||
@ -179,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 {
|
||||
|
||||
@ -5,17 +5,14 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"math/rand"
|
||||
"math/rand/v2"
|
||||
"net/netip"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// UDPMaxLen is the max size of udp dns request.
|
||||
// https://www.rfc-editor.org/rfc/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
|
||||
@ -149,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 {
|
||||
@ -184,7 +181,6 @@ func UnmarshalMessage(b []byte) (*Message, error) {
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | ARCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
//
|
||||
type Header struct {
|
||||
ID uint16
|
||||
Bits uint16
|
||||
@ -214,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) {
|
||||
@ -446,7 +443,7 @@ func (m *Message) UnmarshalRR(start int, rr *RR) (n int, err error) {
|
||||
// MarshalDomainTo marshals domain string struct to []byte and write to w.
|
||||
func MarshalDomainTo(w io.Writer, domain string) (n int, err error) {
|
||||
nn := 0
|
||||
for _, seg := range strings.Split(domain, ".") {
|
||||
for seg := range strings.SplitSeq(domain, ".") {
|
||||
nn, err = w.Write([]byte{byte(len(seg))})
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
// _ "github.com/nadoo/glider/service/xxx"
|
||||
|
||||
// comment out the protocols you don't need to make the compiled binary smaller.
|
||||
_ "github.com/nadoo/glider/proxy/anytls"
|
||||
_ "github.com/nadoo/glider/proxy/http"
|
||||
_ "github.com/nadoo/glider/proxy/kcp"
|
||||
_ "github.com/nadoo/glider/proxy/mixed"
|
||||
@ -16,7 +17,6 @@ import (
|
||||
_ "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/tcp"
|
||||
_ "github.com/nadoo/glider/proxy/tls"
|
||||
_ "github.com/nadoo/glider/proxy/trojan"
|
||||
|
||||
34
go.mod
34
go.mod
@ -1,36 +1,24 @@
|
||||
module github.com/nadoo/glider
|
||||
|
||||
go 1.18
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
|
||||
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d
|
||||
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb
|
||||
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152
|
||||
github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41
|
||||
github.com/insomniacslk/dhcp v0.0.0-20260603135910-a415979eb11e
|
||||
github.com/nadoo/conflag v0.3.1
|
||||
github.com/nadoo/ipset v0.5.0
|
||||
github.com/xtaci/kcp-go/v5 v5.6.1
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150
|
||||
github.com/xtaci/kcp-go/v5 v5.6.72
|
||||
golang.org/x/crypto v0.53.0
|
||||
golang.org/x/sys v0.46.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
||||
github.com/klauspost/reedsolomon v1.9.16 // indirect
|
||||
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 // indirect
|
||||
github.com/mdlayher/raw v0.1.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/klauspost/reedsolomon v1.14.1 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.27 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/templexxx/cpu v0.0.10-0.20211111114238-98168dcec14a // indirect
|
||||
github.com/templexxx/xorsimd v0.4.1 // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect
|
||||
golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 // indirect
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
||||
golang.org/x/net v0.56.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
)
|
||||
|
||||
// Replace dependency modules with local developing copy
|
||||
// use `go list -m all` to confirm the final module used
|
||||
// replace (
|
||||
// github.com/nadoo/conflag => ../conflag
|
||||
// )
|
||||
|
||||
149
go.sum
149
go.sum
@ -7,18 +7,9 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d h1:CPqTNIigGweVPT4CYb+OO2E6XyRKFOmvTHwWRLgCAlE=
|
||||
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=
|
||||
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb/go.mod h1:F7WkpqJj9t98ePxB/WJGQTIDeOVPuSJ3qdn6JUjg170=
|
||||
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 h1:ED31mPIxDJnrLt9W9dH5xgd/6KjzEACKHBVGQ33czc0=
|
||||
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/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/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
|
||||
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=
|
||||
@ -34,154 +25,80 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
|
||||
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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41 h1:Yg3n3AI7GoHnWt7dyjsLPU+TEuZfPAg0OdiA3MJUV6I=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
|
||||
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
|
||||
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/reedsolomon v1.9.9/go.mod h1:O7yFFHiQwDR6b2t63KPUpccPtNdp5ADgh1gg4fd12wo=
|
||||
github.com/klauspost/reedsolomon v1.9.16 h1:mR0AwphBwqFv/I3B9AHtNKvzuowI1vrj8/3UX4XRmHA=
|
||||
github.com/klauspost/reedsolomon v1.9.16/go.mod h1:eqPAcE7xar5CIzcdfwydOEdcmchAKAP/qs14y4GCBOk=
|
||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
|
||||
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 h1:2oDp6OOhLxQ9JBoUuysVz9UZ9uI6oLUbvAZu0x8o+vE=
|
||||
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118/go.mod h1:ZFUnHIVchZ9lJoWoEGUg8Q3M4U8aNNWA3CVSUTkW4og=
|
||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
||||
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
||||
github.com/mdlayher/packet v1.0.0 h1:InhZJbdShQYt6XV2GPj5XHxChzOfhJJOMbvnGAmOfQ8=
|
||||
github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU=
|
||||
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||
github.com/mdlayher/raw v0.1.0 h1:K4PFMVy+AFsp0Zdlrts7yNhxc/uXoPVHi9RzRvtZF2Y=
|
||||
github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5s9Sg=
|
||||
github.com/mdlayher/socket v0.2.1 h1:F2aaOwb53VsBE+ebRS9bLd7yPOfYUMC8lOODdCBDY6w=
|
||||
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
|
||||
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104/go.mod h1:wqKykBG2QzQDJEzvRkcS8x6MiSJkF52hXZsXcjaB3ls=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20260603135910-a415979eb11e h1:7j1+lOuGBg7PQF1RxeVx0iP+/GpiAxSG/F+9t5JOS94=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20260603135910-a415979eb11e/go.mod h1:qfvBmyDNp+/liLEYWRvqny/PEz9hGe2Dz833eXILSmo=
|
||||
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.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/reedsolomon v1.14.1 h1:swE9kzyWXD/wVG+l5Pe8bWnQ0giIY7D1GjCBKk3kG2U=
|
||||
github.com/klauspost/reedsolomon v1.14.1/go.mod h1:yjqqjgMTQkBUHSG97/rm4zipffCNbCiZcB3kTqr++sQ=
|
||||
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.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk=
|
||||
github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
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.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
||||
github.com/templexxx/cpu v0.0.7/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
||||
github.com/templexxx/cpu v0.0.10-0.20211111114238-98168dcec14a h1:f0GQM8LuKYnXdNLcAg+di6PULSlR5iQtZT3bDwDRiA0=
|
||||
github.com/templexxx/cpu v0.0.10-0.20211111114238-98168dcec14a/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
||||
github.com/templexxx/xorsimd v0.4.1 h1:iUZcywbOYDRAZUasAs2eSCUW8eobuZDy0I9FJiORkVg=
|
||||
github.com/templexxx/xorsimd v0.4.1/go.mod h1:W+ffZz8jJMH2SXwuKu9WhygqBMbFnp14G2fqEr8qaNo=
|
||||
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
|
||||
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-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 h1:hl6sK6aFgTLISijk6xIzeqnPzQcsLqqvL6vEfTPinME=
|
||||
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||
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/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.72 h1:FLaQPalgpufJYQRk0OK+gErEhXGLUPjv6FSRPrFR8Lk=
|
||||
github.com/xtaci/kcp-go/v5 v5.6.72/go.mod h1:9O3D8WR+cyyUjGiTILYfg17vn72otWuXK2AFfqIe6CM=
|
||||
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=
|
||||
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
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/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-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-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 h1:yssD99+7tqHWO5Gwh81phT+67hg+KttniBr6UnEXOY8=
|
||||
golang.org/x/net v0.0.0-20220421235706-1d1ef9303861/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
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-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||
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/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
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-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
@ -196,9 +113,7 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
||||
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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
2
main.go
2
main.go
@ -17,7 +17,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
version = "0.16.1"
|
||||
version = "0.17.0"
|
||||
config = parseConfig()
|
||||
)
|
||||
|
||||
|
||||
@ -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() any {
|
||||
return make([]byte, size)
|
||||
buf := make([]byte, size)
|
||||
return unsafe.SliceData(buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -30,11 +32,10 @@ func init() {
|
||||
// otherwise, this function will call make([]byte, size) directly.
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016-2017 Daniel Fu
|
||||
|
||||
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.
|
||||
@ -1,3 +1,25 @@
|
||||
// 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 (
|
||||
@ -38,16 +60,18 @@ const (
|
||||
|
||||
// Frame defines a packet from or to be multiplexed into a single connection
|
||||
type Frame struct {
|
||||
ver byte
|
||||
cmd byte
|
||||
sid uint32
|
||||
data []byte
|
||||
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 {
|
||||
@ -71,6 +95,7 @@ func (h rawHeader) String() string {
|
||||
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 {
|
||||
|
||||
@ -1,7 +1,25 @@
|
||||
// Package smux is a multiplexing library for Golang.
|
||||
// MIT License
|
||||
//
|
||||
// It relies on an underlying connection to provide reliability and ordering, such as TCP or KCP,
|
||||
// and provides stream-oriented multiplexing over a single channel.
|
||||
// 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 (
|
||||
|
||||
@ -1,86 +0,0 @@
|
||||
package smux
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type buffer struct {
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func (b *buffer) Close() error {
|
||||
b.Buffer.Reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
VerifyConfig(DefaultConfig())
|
||||
|
||||
config := DefaultConfig()
|
||||
config.KeepAliveInterval = 0
|
||||
err := VerifyConfig(config)
|
||||
t.Log(err)
|
||||
if err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config = DefaultConfig()
|
||||
config.KeepAliveInterval = 10
|
||||
config.KeepAliveTimeout = 5
|
||||
err = VerifyConfig(config)
|
||||
t.Log(err)
|
||||
if err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config = DefaultConfig()
|
||||
config.MaxFrameSize = 0
|
||||
err = VerifyConfig(config)
|
||||
t.Log(err)
|
||||
if err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config = DefaultConfig()
|
||||
config.MaxFrameSize = 65536
|
||||
err = VerifyConfig(config)
|
||||
t.Log(err)
|
||||
if err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config = DefaultConfig()
|
||||
config.MaxReceiveBuffer = 0
|
||||
err = VerifyConfig(config)
|
||||
t.Log(err)
|
||||
if err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config = DefaultConfig()
|
||||
config.MaxStreamBuffer = 0
|
||||
err = VerifyConfig(config)
|
||||
t.Log(err)
|
||||
if err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config = DefaultConfig()
|
||||
config.MaxStreamBuffer = 100
|
||||
config.MaxReceiveBuffer = 99
|
||||
err = VerifyConfig(config)
|
||||
t.Log(err)
|
||||
if err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var bts buffer
|
||||
if _, err := Server(&bts, config); err == nil {
|
||||
t.Fatal("server started with wrong config")
|
||||
}
|
||||
|
||||
if _, err := Client(&bts, config); err == nil {
|
||||
t.Fatal("client started with wrong config")
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,34 @@
|
||||
// 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"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@ -17,32 +38,52 @@ import (
|
||||
|
||||
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 = errors.New("timeout")
|
||||
ErrTimeout = fmt.Errorf("smux: %w", os.ErrDeadlineExceeded)
|
||||
ErrTimeout net.Error = &timeoutError{}
|
||||
ErrWouldBlock = errors.New("operation would block on IO")
|
||||
)
|
||||
|
||||
// writeRequest represents a request to write a frame
|
||||
type writeRequest struct {
|
||||
prio uint32
|
||||
class CLASSID
|
||||
frame Frame
|
||||
seq uint32
|
||||
result chan writeResult
|
||||
}
|
||||
|
||||
// writeResult represents the result of a write request
|
||||
type writeResult struct {
|
||||
n int
|
||||
err error
|
||||
}
|
||||
|
||||
type buffersWriter interface {
|
||||
WriteBuffers(v [][]byte) (n int, err error)
|
||||
}
|
||||
|
||||
// Session defines a multiplexed connection for streams
|
||||
type Session struct {
|
||||
conn io.ReadWriteCloser
|
||||
@ -54,7 +95,7 @@ type Session struct {
|
||||
bucket int32 // token bucket
|
||||
bucketNotify chan struct{} // used for waiting for tokens
|
||||
|
||||
streams map[uint32]*Stream // all streams in this session
|
||||
streams map[uint32]*stream // all streams in this session
|
||||
streamLock sync.Mutex // locks streams
|
||||
|
||||
die chan struct{} // flag session has died
|
||||
@ -73,7 +114,7 @@ type Session struct {
|
||||
chProtoError chan struct{}
|
||||
protoErrorOnce sync.Once
|
||||
|
||||
chAccepts chan *Stream
|
||||
chAccepts chan *stream
|
||||
|
||||
dataReady int32 // flag data has arrived
|
||||
|
||||
@ -81,6 +122,7 @@ type Session struct {
|
||||
|
||||
deadline atomic.Value
|
||||
|
||||
requestID uint32 // Monotonic increasing write request ID
|
||||
shaper chan writeRequest // a shaper for writing
|
||||
writes chan writeRequest
|
||||
}
|
||||
@ -90,8 +132,8 @@ func newSession(config *Config, conn io.ReadWriteCloser, client bool) *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.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)
|
||||
@ -139,7 +181,7 @@ func (s *Session) OpenStream() (*Stream, error) {
|
||||
|
||||
stream := newStream(sid, s.config.MaxFrameSize, s)
|
||||
|
||||
if _, err := s.writeFrame(newFrame(byte(s.config.Version), cmdSYN, sid)); err != nil {
|
||||
if _, err := s.writeControlFrame(newFrame(byte(s.config.Version), cmdSYN, sid)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -154,7 +196,14 @@ func (s *Session) OpenStream() (*Stream, error) {
|
||||
return nil, io.ErrClosedPipe
|
||||
default:
|
||||
s.streams[sid] = stream
|
||||
return stream, nil
|
||||
wrapper := &Stream{stream: stream}
|
||||
// NOTE(x): disabled finalizer for issue #997
|
||||
/*
|
||||
runtime.SetFinalizer(wrapper, func(s *Stream) {
|
||||
s.Close()
|
||||
})
|
||||
*/
|
||||
return wrapper, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,7 +224,11 @@ func (s *Session) AcceptStream() (*Stream, error) {
|
||||
|
||||
select {
|
||||
case stream := <-s.chAccepts:
|
||||
return stream, nil
|
||||
wrapper := &Stream{stream: stream}
|
||||
runtime.SetFinalizer(wrapper, func(s *Stream) {
|
||||
s.Close()
|
||||
})
|
||||
return wrapper, nil
|
||||
case <-deadline:
|
||||
return nil, ErrTimeout
|
||||
case <-s.chSocketReadError:
|
||||
@ -212,6 +265,12 @@ func (s *Session) Close() error {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@ -291,12 +350,15 @@ func (s *Session) RemoteAddr() net.Addr {
|
||||
// notify the session that a stream has closed
|
||||
func (s *Session) streamClosed(sid uint32) {
|
||||
s.streamLock.Lock()
|
||||
if n := s.streams[sid].recycleTokens(); n > 0 { // return remaining tokens to the bucket
|
||||
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()
|
||||
}
|
||||
|
||||
@ -331,7 +393,7 @@ func (s *Session) recvLoop() {
|
||||
sid := hdr.StreamID()
|
||||
switch hdr.Cmd() {
|
||||
case cmdNOP:
|
||||
case cmdSYN:
|
||||
case cmdSYN: // stream opening
|
||||
s.streamLock.Lock()
|
||||
if _, ok := s.streams[sid]; !ok {
|
||||
stream := newStream(sid, s.config.MaxFrameSize, s)
|
||||
@ -342,22 +404,26 @@ func (s *Session) recvLoop() {
|
||||
}
|
||||
}
|
||||
s.streamLock.Unlock()
|
||||
case cmdFIN:
|
||||
case cmdFIN: // stream closing
|
||||
s.streamLock.Lock()
|
||||
if stream, ok := s.streams[sid]; ok {
|
||||
stream.fin()
|
||||
stream.notifyReadEvent()
|
||||
}
|
||||
s.streamLock.Unlock()
|
||||
case cmdPSH:
|
||||
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 {
|
||||
@ -365,7 +431,7 @@ func (s *Session) recvLoop() {
|
||||
return
|
||||
}
|
||||
}
|
||||
case cmdUPD:
|
||||
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 {
|
||||
@ -387,6 +453,7 @@ func (s *Session) recvLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
@ -395,7 +462,7 @@ func (s *Session) keepalive() {
|
||||
for {
|
||||
select {
|
||||
case <-tickerPing.C:
|
||||
s.writeFrameInternal(newFrame(byte(s.config.Version), cmdNOP, 0), tickerPing.C, 0)
|
||||
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) {
|
||||
@ -412,13 +479,16 @@ func (s *Session) keepalive() {
|
||||
}
|
||||
}
|
||||
|
||||
// shaper shapes the sending sequence among streams
|
||||
// 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)
|
||||
@ -426,10 +496,22 @@ func (s *Session) shaperLoop() {
|
||||
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 := <-s.shaper:
|
||||
case r := <-chShaper:
|
||||
if chWrite != nil { // next is valid, reshape
|
||||
heap.Push(&reqs, next)
|
||||
}
|
||||
@ -439,13 +521,17 @@ func (s *Session) shaperLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
// 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.(buffersWriter)
|
||||
bw, ok := s.conn.(interface {
|
||||
WriteBuffers(v [][]byte) (n int, err error)
|
||||
})
|
||||
|
||||
if ok {
|
||||
buf = make([]byte, headerSize)
|
||||
vec = make([][]byte, 2)
|
||||
@ -463,6 +549,7 @@ func (s *Session) sendLoop() {
|
||||
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
|
||||
@ -494,17 +581,21 @@ func (s *Session) sendLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
// writeFrame writes the frame to the underlying connection
|
||||
// writeControlFrame writes the control frame to the underlying connection
|
||||
// and returns the number of bytes written if successful
|
||||
func (s *Session) writeFrame(f Frame) (n int, err error) {
|
||||
return s.writeFrameInternal(f, nil, 0)
|
||||
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, prio uint32) (int, error) {
|
||||
func (s *Session) writeFrameInternal(f Frame, deadline <-chan time.Time, class CLASSID) (int, error) {
|
||||
req := writeRequest{
|
||||
prio: prio,
|
||||
class: class,
|
||||
frame: f,
|
||||
seq: atomic.AddUint32(&s.requestID, 1),
|
||||
result: make(chan writeResult, 1),
|
||||
}
|
||||
select {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,17 +1,53 @@
|
||||
// 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) }
|
||||
func (h shaperHeap) Less(i, j int) bool { return _itimediff(h[j].prio, h[i].prio) > 0 }
|
||||
func (h shaperHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||
func (h *shaperHeap) Push(x any) { *h = append(*h, x.(writeRequest)) }
|
||||
|
||||
func (h *shaperHeap) Pop() any {
|
||||
// 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]
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
package smux
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestShaper(t *testing.T) {
|
||||
w1 := writeRequest{prio: 10}
|
||||
w2 := writeRequest{prio: 10}
|
||||
w3 := writeRequest{prio: 20}
|
||||
w4 := writeRequest{prio: 100}
|
||||
w5 := writeRequest{prio: (1 << 32) - 1}
|
||||
|
||||
var reqs shaperHeap
|
||||
heap.Push(&reqs, w5)
|
||||
heap.Push(&reqs, w4)
|
||||
heap.Push(&reqs, w3)
|
||||
heap.Push(&reqs, w2)
|
||||
heap.Push(&reqs, w1)
|
||||
|
||||
var lastPrio = reqs[0].prio
|
||||
for len(reqs) > 0 {
|
||||
w := heap.Pop(&reqs).(writeRequest)
|
||||
if int32(w.prio-lastPrio) < 0 {
|
||||
t.Fatal("incorrect shaper priority")
|
||||
}
|
||||
|
||||
t.Log("prio:", w.prio)
|
||||
lastPrio = w.prio
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,25 @@
|
||||
// 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 (
|
||||
@ -11,36 +33,41 @@ import (
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
)
|
||||
|
||||
// Stream implements net.Conn
|
||||
// wrapper for GC
|
||||
type Stream struct {
|
||||
id uint32
|
||||
*stream
|
||||
}
|
||||
|
||||
// Stream implements net.Conn
|
||||
type stream struct {
|
||||
id uint32 // Stream identifier
|
||||
sess *Session
|
||||
|
||||
buffers [][]byte
|
||||
heads [][]byte // slice heads kept for recycle
|
||||
buffers [][]byte // the sequential buffers of stream
|
||||
heads [][]byte // slice heads of the buffers above, kept for recycle
|
||||
|
||||
bufferLock sync.Mutex
|
||||
frameSize int
|
||||
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
|
||||
dieOnce sync.Once // Ensures die channel is closed only once
|
||||
|
||||
// FIN command
|
||||
chFinEvent chan struct{}
|
||||
finEventOnce sync.Once
|
||||
finEventOnce sync.Once // Ensures chFinEvent is closed only once
|
||||
|
||||
// deadlines
|
||||
readDeadline atomic.Value
|
||||
writeDeadline atomic.Value
|
||||
|
||||
// per stream sliding window control
|
||||
numRead uint32 // number of consumed bytes
|
||||
numRead uint32 // count num of bytes read
|
||||
numWritten uint32 // count num of bytes written
|
||||
incr uint32 // counting for sending
|
||||
incr uint32 // bytes sent since last window update
|
||||
|
||||
// UPD command
|
||||
peerConsumed uint32 // num of bytes the peer has consumed
|
||||
@ -48,9 +75,9 @@ type Stream struct {
|
||||
chUpdate chan struct{} // notify of remote data consuming and window update
|
||||
}
|
||||
|
||||
// newStream initiates a Stream struct
|
||||
func newStream(id uint32, frameSize int, sess *Session) *Stream {
|
||||
s := new(Stream)
|
||||
// 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)
|
||||
@ -59,16 +86,17 @@ func newStream(id uint32, frameSize int, sess *Session) *Stream {
|
||||
s.die = make(chan struct{})
|
||||
s.chFinEvent = make(chan struct{})
|
||||
s.peerWindow = initialPeerWindow // set to initial window size
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// ID returns the unique stream ID.
|
||||
func (s *Stream) ID() uint32 {
|
||||
// ID returns the stream's unique identifier.
|
||||
func (s *stream) ID() uint32 {
|
||||
return s.id
|
||||
}
|
||||
|
||||
// Read implements net.Conn
|
||||
func (s *Stream) Read(b []byte) (n int, err error) {
|
||||
// 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 {
|
||||
@ -81,8 +109,8 @@ func (s *Stream) Read(b []byte) (n int, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// tryRead is the nonblocking version of Read
|
||||
func (s *Stream) tryRead(b []byte) (n int, err error) {
|
||||
// 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)
|
||||
}
|
||||
@ -91,6 +119,7 @@ func (s *Stream) tryRead(b []byte) (n int, err error) {
|
||||
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])
|
||||
@ -118,7 +147,8 @@ func (s *Stream) tryRead(b []byte) (n int, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) tryReadv2(b []byte) (n int, err error) {
|
||||
// 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
|
||||
}
|
||||
@ -139,26 +169,32 @@ func (s *Stream) tryReadv2(b []byte) (n int, err error) {
|
||||
|
||||
// in an ideal environment:
|
||||
// if more than half of buffer has consumed, send read ack to peer
|
||||
// based on round-trip time of ACK, continuous flowing data
|
||||
// won't slow down because of waiting for ACK, as long as the
|
||||
// consumer keeps on reading data
|
||||
// s.numRead == n also notify window at the first read
|
||||
// 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
|
||||
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:
|
||||
@ -169,7 +205,12 @@ func (s *Stream) tryReadv2(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
// WriteTo implements io.WriteTo
|
||||
func (s *Stream) WriteTo(w io.Writer) (n int64, err error) {
|
||||
// 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)
|
||||
}
|
||||
@ -186,6 +227,7 @@ func (s *Stream) WriteTo(w io.Writer) (n int64, err error) {
|
||||
|
||||
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 {
|
||||
@ -201,7 +243,8 @@ func (s *Stream) WriteTo(w io.Writer) (n int64, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) writeTov2(w io.Writer) (n int64, err error) {
|
||||
// check comments in WriteTo
|
||||
func (s *stream) writeTov2(w io.Writer) (n int64, err error) {
|
||||
for {
|
||||
var notifyConsumed uint32
|
||||
var buf []byte
|
||||
@ -221,6 +264,7 @@ func (s *Stream) writeTov2(w io.Writer) (n int64, err error) {
|
||||
|
||||
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 {
|
||||
@ -242,7 +286,8 @@ func (s *Stream) writeTov2(w io.Writer) (n int64, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) sendWindowUpdate(consumed uint32) error {
|
||||
// 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() {
|
||||
@ -256,11 +301,12 @@ func (s *Stream) sendWindowUpdate(consumed uint32) error {
|
||||
binary.LittleEndian.PutUint32(hdr[:], consumed)
|
||||
binary.LittleEndian.PutUint32(hdr[4:], uint32(s.sess.config.MaxStreamBuffer))
|
||||
frame.data = hdr[:]
|
||||
_, err := s.sess.writeFrameInternal(frame, deadline, 0)
|
||||
_, err := s.sess.writeFrameInternal(frame, deadline, CLSCTRL)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Stream) waitRead() error {
|
||||
// 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() {
|
||||
@ -270,9 +316,15 @@ func (s *Stream) waitRead() error {
|
||||
}
|
||||
|
||||
select {
|
||||
case <-s.chReadEvent:
|
||||
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)
|
||||
@ -290,7 +342,7 @@ func (s *Stream) waitRead() error {
|
||||
//
|
||||
// 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) {
|
||||
func (s *stream) Write(b []byte) (n int, err error) {
|
||||
if s.sess.config.Version == 2 {
|
||||
return s.writeV2(b)
|
||||
}
|
||||
@ -304,6 +356,8 @@ func (s *Stream) Write(b []byte) (n int, err error) {
|
||||
|
||||
// check if stream has closed
|
||||
select {
|
||||
case <-s.chFinEvent: // passive closing
|
||||
return 0, io.EOF
|
||||
case <-s.die:
|
||||
return 0, io.ErrClosedPipe
|
||||
default:
|
||||
@ -320,7 +374,7 @@ func (s *Stream) Write(b []byte) (n int, err error) {
|
||||
}
|
||||
frame.data = bts[:sz]
|
||||
bts = bts[sz:]
|
||||
n, err := s.sess.writeFrameInternal(frame, deadline, s.numWritten)
|
||||
n, err := s.sess.writeFrameInternal(frame, deadline, CLSDATA)
|
||||
s.numWritten++
|
||||
sent += n
|
||||
if err != nil {
|
||||
@ -331,7 +385,8 @@ func (s *Stream) Write(b []byte) (n int, err error) {
|
||||
return sent, nil
|
||||
}
|
||||
|
||||
func (s *Stream) writeV2(b []byte) (n int, err error) {
|
||||
// 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
|
||||
@ -339,6 +394,8 @@ func (s *Stream) writeV2(b []byte) (n int, err error) {
|
||||
|
||||
// check if stream has closed
|
||||
select {
|
||||
case <-s.chFinEvent:
|
||||
return 0, io.EOF
|
||||
case <-s.die:
|
||||
return 0, io.ErrClosedPipe
|
||||
default:
|
||||
@ -365,14 +422,18 @@ func (s *Stream) writeV2(b []byte) (n int, err error) {
|
||||
// even if uint32 overflow, this math still works:
|
||||
// eg1: uint32(0) - uint32(math.MaxUint32) = 1
|
||||
// eg2: int32(uint32(0) - uint32(1)) = -1
|
||||
// security check for misbehavior
|
||||
//
|
||||
// basicially, you can take it as a MODULAR ARITHMETIC
|
||||
inflight := int32(atomic.LoadUint32(&s.numWritten) - atomic.LoadUint32(&s.peerConsumed))
|
||||
if inflight < 0 {
|
||||
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
|
||||
@ -381,14 +442,18 @@ func (s *Stream) writeV2(b []byte) (n int, err error) {
|
||||
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:]
|
||||
n, err := s.sess.writeFrameInternal(frame, deadline, atomic.LoadUint32(&s.numWritten))
|
||||
|
||||
// transmit of frame
|
||||
n, err := s.sess.writeFrameInternal(frame, deadline, CLSDATA)
|
||||
atomic.AddUint32(&s.numWritten, uint32(sz))
|
||||
sent += n
|
||||
if err != nil {
|
||||
@ -397,12 +462,12 @@ func (s *Stream) writeV2(b []byte) (n int, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// if there is any data remaining to be sent
|
||||
// if there is any data left to be sent,
|
||||
// wait until stream closes, window changes or deadline reached
|
||||
// this blocking behavior will inform upper layer to do flow control
|
||||
// this blocking behavior will back propagate flow control to upper layer.
|
||||
if len(b) > 0 {
|
||||
select {
|
||||
case <-s.chFinEvent: // if fin arrived, future window update is impossible
|
||||
case <-s.chFinEvent:
|
||||
return 0, io.EOF
|
||||
case <-s.die:
|
||||
return sent, io.ErrClosedPipe
|
||||
@ -410,7 +475,7 @@ func (s *Stream) writeV2(b []byte) (n int, err error) {
|
||||
return sent, ErrTimeout
|
||||
case <-s.sess.chSocketWriteError:
|
||||
return sent, s.sess.socketWriteError.Load().(error)
|
||||
case <-s.chUpdate:
|
||||
case <-s.chUpdate: // notify of remote data consuming and window update
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
@ -420,7 +485,7 @@ func (s *Stream) writeV2(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
// Close implements net.Conn
|
||||
func (s *Stream) Close() error {
|
||||
func (s *stream) Close() error {
|
||||
var once bool
|
||||
var err error
|
||||
s.dieOnce.Do(func() {
|
||||
@ -429,23 +494,30 @@ func (s *Stream) Close() error {
|
||||
})
|
||||
|
||||
if once {
|
||||
_, err = s.sess.writeFrame(newFrame(byte(s.sess.config.Version), cmdFIN, s.id))
|
||||
// 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{} {
|
||||
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 {
|
||||
func (s *stream) SetReadDeadline(t time.Time) error {
|
||||
s.readDeadline.Store(t)
|
||||
s.notifyReadEvent()
|
||||
return nil
|
||||
@ -454,7 +526,7 @@ func (s *Stream) SetReadDeadline(t time.Time) error {
|
||||
// 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 {
|
||||
func (s *stream) SetWriteDeadline(t time.Time) error {
|
||||
s.writeDeadline.Store(t)
|
||||
return nil
|
||||
}
|
||||
@ -462,7 +534,7 @@ func (s *Stream) SetWriteDeadline(t time.Time) error {
|
||||
// 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 {
|
||||
func (s *stream) SetDeadline(t time.Time) error {
|
||||
if err := s.SetReadDeadline(t); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -473,10 +545,10 @@ func (s *Stream) SetDeadline(t time.Time) error {
|
||||
}
|
||||
|
||||
// session closes
|
||||
func (s *Stream) sessionClose() { s.dieOnce.Do(func() { close(s.die) }) }
|
||||
func (s *stream) sessionClose() { s.dieOnce.Do(func() { close(s.die) }) }
|
||||
|
||||
// LocalAddr satisfies net.Conn interface
|
||||
func (s *Stream) LocalAddr() net.Addr {
|
||||
func (s *stream) LocalAddr() net.Addr {
|
||||
if ts, ok := s.sess.conn.(interface {
|
||||
LocalAddr() net.Addr
|
||||
}); ok {
|
||||
@ -486,7 +558,7 @@ func (s *Stream) LocalAddr() net.Addr {
|
||||
}
|
||||
|
||||
// RemoteAddr satisfies net.Conn interface
|
||||
func (s *Stream) RemoteAddr() net.Addr {
|
||||
func (s *stream) RemoteAddr() net.Addr {
|
||||
if ts, ok := s.sess.conn.(interface {
|
||||
RemoteAddr() net.Addr
|
||||
}); ok {
|
||||
@ -496,7 +568,7 @@ func (s *Stream) RemoteAddr() net.Addr {
|
||||
}
|
||||
|
||||
// pushBytes append buf to buffers
|
||||
func (s *Stream) pushBytes(buf []byte) (written int, err error) {
|
||||
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)
|
||||
@ -505,7 +577,7 @@ func (s *Stream) pushBytes(buf []byte) (written int, err error) {
|
||||
}
|
||||
|
||||
// recycleTokens transform remaining bytes to tokens(will truncate buffer)
|
||||
func (s *Stream) recycleTokens() (n int) {
|
||||
func (s *stream) recycleTokens() (n int) {
|
||||
s.bufferLock.Lock()
|
||||
for k := range s.buffers {
|
||||
n += len(s.buffers[k])
|
||||
@ -518,7 +590,7 @@ func (s *Stream) recycleTokens() (n int) {
|
||||
}
|
||||
|
||||
// notify read event
|
||||
func (s *Stream) notifyReadEvent() {
|
||||
func (s *stream) notifyReadEvent() {
|
||||
select {
|
||||
case s.chReadEvent <- struct{}{}:
|
||||
default:
|
||||
@ -526,7 +598,7 @@ func (s *Stream) notifyReadEvent() {
|
||||
}
|
||||
|
||||
// update command
|
||||
func (s *Stream) update(consumed uint32, window uint32) {
|
||||
func (s *stream) update(consumed uint32, window uint32) {
|
||||
atomic.StoreUint32(&s.peerConsumed, consumed)
|
||||
atomic.StoreUint32(&s.peerWindow, window)
|
||||
select {
|
||||
@ -536,7 +608,7 @@ func (s *Stream) update(consumed uint32, window uint32) {
|
||||
}
|
||||
|
||||
// mark this stream has been closed in protocol
|
||||
func (s *Stream) fin() {
|
||||
func (s *stream) fin() {
|
||||
s.finEventOnce.Do(func() {
|
||||
close(s.chFinEvent)
|
||||
})
|
||||
|
||||
116
proxy/anytls/anytls.go
Normal file
116
proxy/anytls/anytls.go
Normal file
@ -0,0 +1,116 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
type AnyTLS struct {
|
||||
dialer proxy.Dialer
|
||||
proxy proxy.Proxy
|
||||
|
||||
addr string
|
||||
password string
|
||||
withTLS bool
|
||||
serverName string
|
||||
skipVerify bool
|
||||
certFile string
|
||||
keyFile string
|
||||
fallback string
|
||||
tlsConfig *tls.Config
|
||||
|
||||
synackTimeout time.Duration
|
||||
padding paddingScheme
|
||||
}
|
||||
|
||||
func NewAnyTLS(s string, d proxy.Dialer, p proxy.Proxy) (*AnyTLS, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[anytls] parse url err: %s", err)
|
||||
}
|
||||
query := u.Query()
|
||||
a := &AnyTLS{
|
||||
dialer: d,
|
||||
proxy: p,
|
||||
addr: u.Host,
|
||||
password: u.User.Username(),
|
||||
withTLS: true,
|
||||
serverName: query.Get("serverName"),
|
||||
skipVerify: query.Get("skipVerify") == "true",
|
||||
certFile: query.Get("cert"),
|
||||
keyFile: query.Get("key"),
|
||||
fallback: query.Get("fallback"),
|
||||
synackTimeout: 10 * time.Second,
|
||||
}
|
||||
if a.password == "" {
|
||||
return nil, errors.New("[anytls] password must be specified")
|
||||
}
|
||||
if a.addr != "" {
|
||||
if _, port, _ := net.SplitHostPort(a.addr); port == "" {
|
||||
a.addr = net.JoinHostPort(a.addr, "443")
|
||||
}
|
||||
if a.serverName == "" {
|
||||
a.serverName = a.addr[:strings.LastIndex(a.addr, ":")]
|
||||
}
|
||||
}
|
||||
if timeout := query.Get("synackTimeout"); timeout != "" {
|
||||
d, err := time.ParseDuration(timeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[anytls] invalid synackTimeout: %s", err)
|
||||
}
|
||||
a.synackTimeout = d
|
||||
}
|
||||
if scheme := query.Get("paddingScheme"); scheme != "" {
|
||||
a.padding, err = parsePaddingScheme(scheme)
|
||||
} else {
|
||||
a.padding, err = parsePaddingScheme(defaultPaddingScheme)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[anytls] invalid padding scheme: %s", err)
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (s *AnyTLS) Addr() string {
|
||||
if s.addr == "" && s.dialer != nil {
|
||||
return s.dialer.Addr()
|
||||
}
|
||||
return s.addr
|
||||
}
|
||||
|
||||
func loadClientTLSConfig(serverName, certFile string, skipVerify bool) (*tls.Config, error) {
|
||||
conf := &tls.Config{ServerName: serverName, InsecureSkipVerify: skipVerify, MinVersion: tls.VersionTLS12}
|
||||
if certFile != "" {
|
||||
certData, err := os.ReadFile(certFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[anytls] read cert file error: %s", err)
|
||||
}
|
||||
certPool := x509.NewCertPool()
|
||||
if !certPool.AppendCertsFromPEM(certData) {
|
||||
return nil, fmt.Errorf("[anytls] can not append cert file: %s", certFile)
|
||||
}
|
||||
conf.RootCAs = certPool
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.AddUsage("anytls", `
|
||||
AnyTLS client scheme:
|
||||
anytls://password@host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&synackTimeout=10s]
|
||||
anytlsc://password@host:port (cleartext, without TLS)
|
||||
|
||||
AnyTLS server scheme:
|
||||
anytls://password@host:port?cert=PATH&key=PATH[&fallback=127.0.0.1:80]
|
||||
anytlsc://password@host:port[?fallback=127.0.0.1:80] (cleartext, without TLS)
|
||||
`)
|
||||
}
|
||||
153
proxy/anytls/client.go
Normal file
153
proxy/anytls/client.go
Normal file
@ -0,0 +1,153 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"github.com/nadoo/glider/pkg/socks"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
func init() {
|
||||
proxy.RegisterDialer("anytls", NewAnyTLSDialer)
|
||||
proxy.RegisterDialer("anytlsc", NewClearTextDialer)
|
||||
}
|
||||
|
||||
func NewAnyTLSDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||
a, err := NewAnyTLS(s, d, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[anytls] create instance error: %s", err)
|
||||
}
|
||||
a.tlsConfig, err = loadClientTLSConfig(a.serverName, a.certFile, a.skipVerify)
|
||||
return a, err
|
||||
}
|
||||
|
||||
func NewClearTextDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||
a, err := NewAnyTLS(s, d, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[anytlsc] create instance error: %s", err)
|
||||
}
|
||||
a.withTLS = false
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (s *AnyTLS) Dial(network, addr string) (net.Conn, error) {
|
||||
if network != "tcp" && network != "tcp4" && network != "tcp6" {
|
||||
return nil, proxy.ErrNotSupported
|
||||
}
|
||||
raw := socks.ParseAddr(addr)
|
||||
if raw == nil {
|
||||
return nil, fmt.Errorf("[anytls] invalid target address: %s", addr)
|
||||
}
|
||||
ss, err := s.newClientSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st, err := ss.openStream()
|
||||
if err != nil {
|
||||
_ = ss.Close()
|
||||
return nil, err
|
||||
}
|
||||
if _, err := st.Write(raw); err != nil {
|
||||
_ = st.Close()
|
||||
_ = ss.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err := ss.waitSYNACK(st.id, s.synackTimeout); err != nil {
|
||||
_ = st.Close()
|
||||
_ = ss.Close()
|
||||
return nil, err
|
||||
}
|
||||
return &clientConn{Conn: st, session: ss}, nil
|
||||
}
|
||||
|
||||
func (s *AnyTLS) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||
if network != "udp" && network != "udp4" && network != "udp6" {
|
||||
return nil, proxy.ErrNotSupported
|
||||
}
|
||||
target := socks.ParseAddr(addr)
|
||||
if target == nil {
|
||||
return nil, fmt.Errorf("[anytls] invalid target address: %s", addr)
|
||||
}
|
||||
raw := socks.ParseAddr(net.JoinHostPort(uotV2MagicHost, "0"))
|
||||
if raw == nil {
|
||||
return nil, fmt.Errorf("[anytls] invalid udp-over-tcp target address")
|
||||
}
|
||||
ss, err := s.newClientSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st, err := ss.openStream()
|
||||
if err != nil {
|
||||
_ = ss.Close()
|
||||
return nil, err
|
||||
}
|
||||
if _, err := st.Write(raw); err != nil {
|
||||
_ = st.Close()
|
||||
_ = ss.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err := writeUOTV2Request(st, target); err != nil {
|
||||
_ = st.Close()
|
||||
_ = ss.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err := ss.waitSYNACK(st.id, s.synackTimeout); err != nil {
|
||||
_ = st.Close()
|
||||
_ = ss.Close()
|
||||
return nil, err
|
||||
}
|
||||
return &clientPacketConn{PacketConn: newUOTPacketConn(st, target), session: ss}, nil
|
||||
}
|
||||
|
||||
func (s *AnyTLS) newClientSession() (*session, error) {
|
||||
rc, err := s.dialer.Dial("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[anytls] dial to %s error: %s", s.addr, err)
|
||||
return nil, err
|
||||
}
|
||||
c := rc
|
||||
if s.withTLS {
|
||||
tc := tls.Client(rc, s.tlsConfig)
|
||||
if err := tc.Handshake(); err != nil {
|
||||
_ = rc.Close()
|
||||
return nil, err
|
||||
}
|
||||
c = tc
|
||||
}
|
||||
if err := writeAuth(c, s.password, s.padding.authPaddingLen()); err != nil {
|
||||
_ = c.Close()
|
||||
return nil, err
|
||||
}
|
||||
ss := newSession(c)
|
||||
if err := ss.writeFrame(frame{command: cmdSettings, data: clientSettings(s.padding)}); err != nil {
|
||||
_ = c.Close()
|
||||
return nil, err
|
||||
}
|
||||
ss.start()
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
type clientConn struct {
|
||||
net.Conn
|
||||
session *session
|
||||
}
|
||||
|
||||
func (c *clientConn) Close() error {
|
||||
err := c.Conn.Close()
|
||||
_ = c.session.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
type clientPacketConn struct {
|
||||
net.PacketConn
|
||||
session *session
|
||||
}
|
||||
|
||||
func (c *clientPacketConn) Close() error {
|
||||
err := c.PacketConn.Close()
|
||||
_ = c.session.Close()
|
||||
return err
|
||||
}
|
||||
95
proxy/anytls/packet.go
Normal file
95
proxy/anytls/packet.go
Normal file
@ -0,0 +1,95 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
"github.com/nadoo/glider/pkg/socks"
|
||||
)
|
||||
|
||||
const uotV2MagicHost = "sp.v2.udp-over-tcp.arpa"
|
||||
|
||||
// uotPacketConn carries UDP packets over an AnyTLS stream using sing-box
|
||||
// udp-over-tcp v2 connect format.
|
||||
type uotPacketConn struct {
|
||||
net.Conn
|
||||
target socks.Addr
|
||||
}
|
||||
|
||||
func newUOTPacketConn(c net.Conn, target socks.Addr) *uotPacketConn {
|
||||
return &uotPacketConn{Conn: c, target: target}
|
||||
}
|
||||
|
||||
func (pc *uotPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
if len(b) < 2 {
|
||||
return 0, pc.target, errors.New("buf size is not enough")
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(pc.Conn, b[:2]); err != nil {
|
||||
return 0, pc.target, err
|
||||
}
|
||||
length := int(binary.BigEndian.Uint16(b[:2]))
|
||||
if len(b) < length {
|
||||
return 0, pc.target, errors.New("buf size is not enough")
|
||||
}
|
||||
|
||||
n, err := io.ReadFull(pc.Conn, b[:length])
|
||||
return n, pc.target, err
|
||||
}
|
||||
|
||||
func (pc *uotPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
buf := pool.GetBytesBuffer()
|
||||
defer pool.PutBytesBuffer(buf)
|
||||
|
||||
var head [2]byte
|
||||
binary.BigEndian.PutUint16(head[:], uint16(len(b)))
|
||||
buf.Write(head[:])
|
||||
buf.Write(b)
|
||||
|
||||
n, err := pc.Write(buf.Bytes())
|
||||
if n > 2 {
|
||||
return n - 2, err
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func (pc *uotPacketConn) SetDeadline(t time.Time) error {
|
||||
return pc.Conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (pc *uotPacketConn) SetReadDeadline(t time.Time) error {
|
||||
return pc.Conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (pc *uotPacketConn) SetWriteDeadline(t time.Time) error {
|
||||
return pc.Conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func writeUOTV2Request(w io.Writer, target socks.Addr) error {
|
||||
if target == nil {
|
||||
return errors.New("invalid target address")
|
||||
}
|
||||
|
||||
buf := pool.GetBytesBuffer()
|
||||
defer pool.PutBytesBuffer(buf)
|
||||
|
||||
buf.WriteByte(1) // connect stream format
|
||||
buf.Write(target)
|
||||
_, err := w.Write(buf.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
func readUOTV2Request(r io.Reader) (socks.Addr, error) {
|
||||
var connect [1]byte
|
||||
if _, err := io.ReadFull(r, connect[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if connect[0] != 1 {
|
||||
return nil, errors.New("udp-over-tcp v2 non-connect format is not supported")
|
||||
}
|
||||
return socks.ReadAddr(r)
|
||||
}
|
||||
70
proxy/anytls/padding.go
Normal file
70
proxy/anytls/padding.go
Normal file
@ -0,0 +1,70 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const defaultPaddingScheme = "stop=8\n0=30-30\n1=100-400\n2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000\n3=9-9,500-1000\n4=500-1000\n5=500-1000\n6=500-1000\n7=500-1000"
|
||||
|
||||
type paddingScheme struct {
|
||||
raw string
|
||||
authRange [2]int
|
||||
}
|
||||
|
||||
func parsePaddingScheme(raw string) (paddingScheme, error) {
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
raw = defaultPaddingScheme
|
||||
}
|
||||
ps := paddingScheme{raw: raw, authRange: [2]int{0, 0}}
|
||||
for line := range strings.SplitSeq(raw, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
key, value, ok := strings.Cut(line, "=")
|
||||
if !ok {
|
||||
return ps, fmt.Errorf("invalid padding scheme line %q", line)
|
||||
}
|
||||
if key == "0" {
|
||||
r, err := parseRange(value)
|
||||
if err != nil {
|
||||
return ps, err
|
||||
}
|
||||
ps.authRange = r
|
||||
}
|
||||
}
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
func parseRange(s string) ([2]int, error) {
|
||||
part := strings.SplitN(s, ",", 2)[0]
|
||||
lo, hi, ok := strings.Cut(part, "-")
|
||||
if !ok {
|
||||
return [2]int{}, fmt.Errorf("invalid range %q", s)
|
||||
}
|
||||
min, err := strconv.Atoi(lo)
|
||||
if err != nil {
|
||||
return [2]int{}, err
|
||||
}
|
||||
max, err := strconv.Atoi(hi)
|
||||
if err != nil {
|
||||
return [2]int{}, err
|
||||
}
|
||||
if min < 0 || max < min || max > 65535 {
|
||||
return [2]int{}, fmt.Errorf("invalid range %q", s)
|
||||
}
|
||||
return [2]int{min, max}, nil
|
||||
}
|
||||
|
||||
func (p paddingScheme) authPaddingLen() int {
|
||||
return p.authRange[0]
|
||||
}
|
||||
|
||||
func (p paddingScheme) md5() string {
|
||||
sum := md5.Sum([]byte(p.raw))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
97
proxy/anytls/protocol.go
Normal file
97
proxy/anytls/protocol.go
Normal file
@ -0,0 +1,97 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
cmdWaste byte = iota
|
||||
cmdSYN
|
||||
cmdPSH
|
||||
cmdFIN
|
||||
cmdSettings
|
||||
cmdAlert
|
||||
cmdUpdatePaddingScheme
|
||||
cmdSYNACK
|
||||
cmdHeartRequest
|
||||
cmdHeartResponse
|
||||
cmdServerSettings
|
||||
)
|
||||
|
||||
const (
|
||||
protocolVersion = 2
|
||||
maxFrameData = 65535
|
||||
)
|
||||
|
||||
type frame struct {
|
||||
command byte
|
||||
streamID uint32
|
||||
data []byte
|
||||
}
|
||||
|
||||
func passwordHash(password string) [32]byte {
|
||||
return sha256.Sum256([]byte(password))
|
||||
}
|
||||
|
||||
func readAuth(r io.Reader, password string) error {
|
||||
var head [34]byte
|
||||
if _, err := io.ReadFull(r, head[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
want := passwordHash(password)
|
||||
if subtle.ConstantTimeCompare(head[:32], want[:]) != 1 {
|
||||
return errors.New("authentication failed")
|
||||
}
|
||||
paddingLen := binary.BigEndian.Uint16(head[32:34])
|
||||
if paddingLen > 0 {
|
||||
_, err := io.CopyN(io.Discard, r, int64(paddingLen))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeAuth(w io.Writer, password string, paddingLen int) error {
|
||||
if paddingLen < 0 || paddingLen > 65535 {
|
||||
return fmt.Errorf("invalid auth padding length %d", paddingLen)
|
||||
}
|
||||
hash := passwordHash(password)
|
||||
buf := make([]byte, 34+paddingLen)
|
||||
copy(buf, hash[:])
|
||||
binary.BigEndian.PutUint16(buf[32:34], uint16(paddingLen))
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func readFrame(r io.Reader) (frame, error) {
|
||||
var head [7]byte
|
||||
if _, err := io.ReadFull(r, head[:]); err != nil {
|
||||
return frame{}, err
|
||||
}
|
||||
n := binary.BigEndian.Uint16(head[5:7])
|
||||
var data []byte
|
||||
if n > 0 {
|
||||
data = make([]byte, n)
|
||||
if _, err := io.ReadFull(r, data); err != nil {
|
||||
return frame{}, err
|
||||
}
|
||||
}
|
||||
return frame{command: head[0], streamID: binary.BigEndian.Uint32(head[1:5]), data: data}, nil
|
||||
}
|
||||
|
||||
func writeFrame(w io.Writer, f frame) error {
|
||||
if len(f.data) > maxFrameData {
|
||||
return errors.New("frame data too large")
|
||||
}
|
||||
buf := make([]byte, 7+len(f.data))
|
||||
buf[0] = f.command
|
||||
binary.BigEndian.PutUint32(buf[1:5], f.streamID)
|
||||
binary.BigEndian.PutUint16(buf[5:7], uint16(len(f.data)))
|
||||
copy(buf[7:], f.data)
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
182
proxy/anytls/server.go
Normal file
182
proxy/anytls/server.go
Normal file
@ -0,0 +1,182 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"github.com/nadoo/glider/pkg/socks"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
func init() {
|
||||
proxy.RegisterServer("anytls", NewAnyTLSServer)
|
||||
proxy.RegisterServer("anytlsc", NewClearTextServer)
|
||||
}
|
||||
|
||||
func NewAnyTLSServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
a, err := NewAnyTLS(s, nil, p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[anytls] create instance error: %s", err)
|
||||
}
|
||||
if a.certFile == "" || a.keyFile == "" {
|
||||
return nil, errors.New("[anytls] cert and key file path must be specified")
|
||||
}
|
||||
cert, err := tls.LoadX509KeyPair(a.certFile, a.keyFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[anytls] unable to load cert: %s, key %s, error: %s", a.certFile, a.keyFile, err)
|
||||
}
|
||||
a.tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS12}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func NewClearTextServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
a, err := NewAnyTLS(s, nil, p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[anytlsc] create instance error: %s", err)
|
||||
}
|
||||
a.withTLS = false
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (s *AnyTLS) ListenAndServe() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.Fatalf("[anytls] failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
log.F("[anytls] listening TCP on %s, with TLS: %v", s.addr, s.withTLS)
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
log.F("[anytls] failed to accept: %v", err)
|
||||
continue
|
||||
}
|
||||
go s.Serve(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AnyTLS) Serve(c net.Conn) {
|
||||
if s.withTLS {
|
||||
tlsConn := tls.Server(c, s.tlsConfig)
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
_ = tlsConn.Close()
|
||||
log.F("[anytls] error in tls handshake: %s", err)
|
||||
return
|
||||
}
|
||||
c = tlsConn
|
||||
}
|
||||
headBuf := bytes.NewBuffer(nil)
|
||||
if err := readAuth(io.TeeReader(c, headBuf), s.password); err != nil {
|
||||
if s.fallback != "" {
|
||||
s.serveFallback(c, s.fallback, headBuf)
|
||||
return
|
||||
}
|
||||
_ = c.Close()
|
||||
log.F("[anytls] auth error from %s: %s", c.RemoteAddr(), err)
|
||||
return
|
||||
}
|
||||
|
||||
ss := newSession(c)
|
||||
ss.start()
|
||||
for {
|
||||
st, err := ss.acceptStream()
|
||||
if err != nil {
|
||||
_ = ss.Close()
|
||||
return
|
||||
}
|
||||
go s.serveStream(ss, st)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AnyTLS) serveFallback(c net.Conn, target string, headBuf *bytes.Buffer) {
|
||||
defer c.Close()
|
||||
|
||||
dialer := s.proxy.NextDialer(target)
|
||||
rc, err := dialer.Dial("tcp", target)
|
||||
if err != nil {
|
||||
log.F("[anytls-fallback] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), target, dialer.Addr(), err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
if _, err := rc.Write(headBuf.Bytes()); err != nil {
|
||||
log.F("[anytls-fallback] write to rc error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.F("[anytls-fallback] %s <-> %s via %s", c.RemoteAddr(), target, dialer.Addr())
|
||||
if err := proxy.Relay(c, rc); err != nil {
|
||||
log.F("[anytls-fallback] %s <-> %s via %s, relay error: %v", c.RemoteAddr(), target, dialer.Addr(), err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AnyTLS) serveStream(ss *session, st *stream) {
|
||||
defer st.Close()
|
||||
|
||||
target, err := socks.ReadAddr(st)
|
||||
if err != nil {
|
||||
_ = ss.writeFrame(frame{command: cmdSYNACK, streamID: st.id, data: []byte(err.Error())})
|
||||
log.F("[anytls] read target error: %v", err)
|
||||
return
|
||||
}
|
||||
host, _, err := net.SplitHostPort(target.String())
|
||||
if err == nil && host == uotV2MagicHost {
|
||||
s.serveUoT(ss, st)
|
||||
return
|
||||
}
|
||||
|
||||
rc, dialer, err := s.proxy.Dial("tcp", target.String())
|
||||
if err != nil {
|
||||
_ = ss.writeFrame(frame{command: cmdSYNACK, streamID: st.id, data: []byte(err.Error())})
|
||||
log.F("[anytls] %s <-> %s via %s, error in dial: %v", st.RemoteAddr(), target, dialer.Addr(), err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
_ = ss.writeFrame(frame{command: cmdSYNACK, streamID: st.id})
|
||||
log.F("[anytls] %s <-> %s via %s", st.RemoteAddr(), target, dialer.Addr())
|
||||
if err = proxy.Relay(st, rc); err != nil {
|
||||
log.F("[anytls] %s <-> %s via %s, relay error: %v", st.RemoteAddr(), target, dialer.Addr(), err)
|
||||
if !strings.Contains(err.Error(), s.addr) {
|
||||
s.proxy.Record(dialer, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AnyTLS) serveUoT(ss *session, st *stream) {
|
||||
target, err := readUOTV2Request(st)
|
||||
if err != nil {
|
||||
_ = ss.writeFrame(frame{command: cmdSYNACK, streamID: st.id, data: []byte(err.Error())})
|
||||
log.F("[anytls] read udp-over-tcp request error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
dstPC, dialer, err := s.proxy.DialUDP("udp", target.String())
|
||||
if err != nil {
|
||||
_ = ss.writeFrame(frame{command: cmdSYNACK, streamID: st.id, data: []byte(err.Error())})
|
||||
log.F("[anytls] %s <-UoT-> %s via %s, error in dial: %v", st.RemoteAddr(), target, dialer.Addr(), err)
|
||||
return
|
||||
}
|
||||
defer dstPC.Close()
|
||||
|
||||
_ = ss.writeFrame(frame{command: cmdSYNACK, streamID: st.id})
|
||||
pc := newUOTPacketConn(st, target)
|
||||
log.F("[anytls] %s <-UoT-> %s via %s", st.RemoteAddr(), target, dialer.Addr())
|
||||
|
||||
go proxy.CopyUDP(dstPC, nil, pc, 2*time.Minute, 5*time.Second)
|
||||
if err := proxy.CopyUDP(pc, nil, dstPC, 2*time.Minute, 5*time.Second); err != nil {
|
||||
log.F("[anytls] %s <-UoT-> %s via %s, relay error: %v", st.RemoteAddr(), target, dialer.Addr(), err)
|
||||
if d, ok := dialer.(proxy.Dialer); ok && !strings.Contains(err.Error(), s.addr) {
|
||||
s.proxy.Record(d, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
233
proxy/anytls/session.go
Normal file
233
proxy/anytls/session.go
Normal file
@ -0,0 +1,233 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type session struct {
|
||||
conn net.Conn
|
||||
|
||||
writeMu sync.Mutex
|
||||
mu sync.Mutex
|
||||
streams map[uint32]*stream
|
||||
synack map[uint32]chan synackResult
|
||||
nextID uint32
|
||||
|
||||
incoming chan *stream
|
||||
done chan struct{}
|
||||
closeOnce sync.Once
|
||||
err atomic.Value
|
||||
|
||||
settingsSeen bool
|
||||
}
|
||||
|
||||
type synackResult struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func newSession(conn net.Conn) *session {
|
||||
return &session{
|
||||
conn: conn,
|
||||
streams: map[uint32]*stream{},
|
||||
synack: map[uint32]chan synackResult{},
|
||||
nextID: 1,
|
||||
incoming: make(chan *stream, 32),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) start() {
|
||||
go s.readLoop()
|
||||
}
|
||||
|
||||
func (s *session) acceptStream() (*stream, error) {
|
||||
select {
|
||||
case st, ok := <-s.incoming:
|
||||
if !ok {
|
||||
return nil, s.Err()
|
||||
}
|
||||
return st, nil
|
||||
case <-s.done:
|
||||
return nil, s.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) openStream() (*stream, error) {
|
||||
id := atomic.AddUint32(&s.nextID, 1) - 1
|
||||
st := newStream(id, s)
|
||||
s.mu.Lock()
|
||||
s.streams[id] = st
|
||||
s.synack[id] = make(chan synackResult, 1)
|
||||
s.mu.Unlock()
|
||||
if err := s.writeFrame(frame{command: cmdSYN, streamID: id}); err != nil {
|
||||
s.removeStream(id)
|
||||
return nil, err
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
|
||||
func (s *session) waitSYNACK(id uint32, timeout time.Duration) error {
|
||||
s.mu.Lock()
|
||||
ch := s.synack[id]
|
||||
s.mu.Unlock()
|
||||
if ch == nil {
|
||||
return nil
|
||||
}
|
||||
var timer <-chan time.Time
|
||||
if timeout > 0 {
|
||||
t := time.NewTimer(timeout)
|
||||
defer t.Stop()
|
||||
timer = t.C
|
||||
}
|
||||
select {
|
||||
case r, ok := <-ch:
|
||||
if !ok {
|
||||
return s.Err()
|
||||
}
|
||||
if len(r.data) > 0 {
|
||||
return fmt.Errorf("stream open failed: %s", string(r.data))
|
||||
}
|
||||
return nil
|
||||
case <-timer:
|
||||
return errors.New("timeout waiting for SYNACK")
|
||||
case <-s.done:
|
||||
return s.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) writeFrame(f frame) error {
|
||||
s.writeMu.Lock()
|
||||
defer s.writeMu.Unlock()
|
||||
return writeFrame(s.conn, f)
|
||||
}
|
||||
|
||||
func (s *session) readLoop() {
|
||||
for {
|
||||
f, err := readFrame(s.conn)
|
||||
if err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
s.setErr(err)
|
||||
}
|
||||
s.Close()
|
||||
return
|
||||
}
|
||||
if err := s.handleFrame(f); err != nil {
|
||||
s.setErr(err)
|
||||
s.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) handleFrame(f frame) error {
|
||||
switch f.command {
|
||||
case cmdWaste:
|
||||
return nil
|
||||
case cmdHeartRequest:
|
||||
return s.writeFrame(frame{command: cmdHeartResponse, streamID: f.streamID})
|
||||
case cmdHeartResponse:
|
||||
return nil
|
||||
case cmdSettings:
|
||||
m := parseSettings(f.data)
|
||||
s.settingsSeen = true
|
||||
if settingsVersion(m) >= 2 {
|
||||
return s.writeFrame(frame{command: cmdServerSettings, data: serverSettings()})
|
||||
}
|
||||
case cmdServerSettings:
|
||||
return nil
|
||||
case cmdAlert:
|
||||
return errors.New("alert: " + string(f.data))
|
||||
case cmdUpdatePaddingScheme:
|
||||
return nil
|
||||
case cmdSYN:
|
||||
if !s.settingsSeen {
|
||||
_ = s.writeFrame(frame{command: cmdAlert, data: []byte("cmdSYN received before cmdSettings")})
|
||||
return errors.New("cmdSYN received before cmdSettings")
|
||||
}
|
||||
st := newStream(f.streamID, s)
|
||||
s.mu.Lock()
|
||||
s.streams[f.streamID] = st
|
||||
s.mu.Unlock()
|
||||
select {
|
||||
case s.incoming <- st:
|
||||
case <-s.done:
|
||||
}
|
||||
case cmdSYNACK:
|
||||
s.mu.Lock()
|
||||
ch := s.synack[f.streamID]
|
||||
delete(s.synack, f.streamID)
|
||||
s.mu.Unlock()
|
||||
if ch != nil {
|
||||
ch <- synackResult{data: f.data}
|
||||
close(ch)
|
||||
}
|
||||
case cmdPSH:
|
||||
st := s.getStream(f.streamID)
|
||||
if st != nil {
|
||||
st.push(f.data)
|
||||
}
|
||||
case cmdFIN:
|
||||
st := s.getStream(f.streamID)
|
||||
if st != nil {
|
||||
st.closeRead()
|
||||
s.removeStream(f.streamID)
|
||||
}
|
||||
default:
|
||||
return errors.New("unknown command")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *session) getStream(id uint32) *stream {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.streams[id]
|
||||
}
|
||||
|
||||
func (s *session) removeStream(id uint32) {
|
||||
s.mu.Lock()
|
||||
delete(s.streams, id)
|
||||
if ch := s.synack[id]; ch != nil {
|
||||
delete(s.synack, id)
|
||||
close(ch)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *session) setErr(err error) {
|
||||
if err != nil && s.err.Load() == nil {
|
||||
s.err.Store(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) Err() error {
|
||||
if v := s.err.Load(); v != nil {
|
||||
return v.(error)
|
||||
}
|
||||
return net.ErrClosed
|
||||
}
|
||||
|
||||
func (s *session) Close() error {
|
||||
s.closeOnce.Do(func() {
|
||||
close(s.done)
|
||||
_ = s.conn.Close()
|
||||
s.mu.Lock()
|
||||
for _, st := range s.streams {
|
||||
st.closeRead()
|
||||
}
|
||||
s.streams = map[uint32]*stream{}
|
||||
for id, ch := range s.synack {
|
||||
delete(s.synack, id)
|
||||
close(ch)
|
||||
}
|
||||
close(s.incoming)
|
||||
s.mu.Unlock()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
59
proxy/anytls/settings.go
Normal file
59
proxy/anytls/settings.go
Normal file
@ -0,0 +1,59 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func encodeSettings(m map[string]string) []byte {
|
||||
keys := []string{"v", "client", "padding-md5"}
|
||||
lines := make([]string, 0, len(m))
|
||||
seen := map[string]bool{}
|
||||
for _, k := range keys {
|
||||
if v, ok := m[k]; ok {
|
||||
lines = append(lines, k+"="+v)
|
||||
seen[k] = true
|
||||
}
|
||||
}
|
||||
for k, v := range m {
|
||||
if !seen[k] {
|
||||
lines = append(lines, k+"="+v)
|
||||
}
|
||||
}
|
||||
return []byte(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
func parseSettings(data []byte) map[string]string {
|
||||
out := map[string]string{}
|
||||
for line := range strings.SplitSeq(string(data), "\n") {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
k, v, ok := strings.Cut(line, "=")
|
||||
if ok {
|
||||
out[k] = v
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func settingsVersion(m map[string]string) int {
|
||||
v, err := strconv.Atoi(m["v"])
|
||||
if err != nil {
|
||||
return 1
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func clientSettings(ps paddingScheme) []byte {
|
||||
return encodeSettings(map[string]string{
|
||||
"v": fmt.Sprint(protocolVersion),
|
||||
"client": "glider-anytls",
|
||||
"padding-md5": ps.md5(),
|
||||
})
|
||||
}
|
||||
|
||||
func serverSettings() []byte {
|
||||
return encodeSettings(map[string]string{"v": fmt.Sprint(protocolVersion)})
|
||||
}
|
||||
93
proxy/anytls/stream.go
Normal file
93
proxy/anytls/stream.go
Normal file
@ -0,0 +1,93 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var errStreamClosed = errors.New("stream closed")
|
||||
|
||||
type stream struct {
|
||||
id uint32
|
||||
s *session
|
||||
|
||||
in chan []byte
|
||||
readBuf []byte
|
||||
closeIn sync.Once
|
||||
closeOut sync.Once
|
||||
|
||||
mu sync.Mutex
|
||||
closed bool
|
||||
}
|
||||
|
||||
func newStream(id uint32, s *session) *stream {
|
||||
return &stream{id: id, s: s, in: make(chan []byte, 32)}
|
||||
}
|
||||
|
||||
func (st *stream) Read(p []byte) (int, error) {
|
||||
for len(st.readBuf) == 0 {
|
||||
b, ok := <-st.in
|
||||
if !ok {
|
||||
return 0, io.EOF
|
||||
}
|
||||
st.readBuf = b
|
||||
}
|
||||
n := copy(p, st.readBuf)
|
||||
st.readBuf = st.readBuf[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (st *stream) Write(p []byte) (int, error) {
|
||||
st.mu.Lock()
|
||||
closed := st.closed
|
||||
st.mu.Unlock()
|
||||
if closed {
|
||||
return 0, errStreamClosed
|
||||
}
|
||||
written := 0
|
||||
for len(p) > 0 {
|
||||
n := min(len(p), maxFrameData)
|
||||
if err := st.s.writeFrame(frame{command: cmdPSH, streamID: st.id, data: p[:n]}); err != nil {
|
||||
return written, err
|
||||
}
|
||||
written += n
|
||||
p = p[n:]
|
||||
}
|
||||
return written, nil
|
||||
}
|
||||
|
||||
func (st *stream) Close() error {
|
||||
st.mu.Lock()
|
||||
already := st.closed
|
||||
st.closed = true
|
||||
st.mu.Unlock()
|
||||
if !already {
|
||||
st.closeOut.Do(func() {
|
||||
_ = st.s.writeFrame(frame{command: cmdFIN, streamID: st.id})
|
||||
})
|
||||
st.s.removeStream(st.id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *stream) closeRead() {
|
||||
st.closeIn.Do(func() { close(st.in) })
|
||||
}
|
||||
|
||||
func (st *stream) push(data []byte) {
|
||||
cp := make([]byte, len(data))
|
||||
copy(cp, data)
|
||||
select {
|
||||
case st.in <- cp:
|
||||
case <-st.s.done:
|
||||
}
|
||||
}
|
||||
|
||||
func (st *stream) LocalAddr() net.Addr { return st.s.conn.LocalAddr() }
|
||||
func (st *stream) RemoteAddr() net.Addr { return st.s.conn.RemoteAddr() }
|
||||
func (st *stream) SetDeadline(t time.Time) error { return st.s.conn.SetDeadline(t) }
|
||||
func (st *stream) SetReadDeadline(t time.Time) error { return st.s.conn.SetReadDeadline(t) }
|
||||
func (st *stream) SetWriteDeadline(t time.Time) error { return st.s.conn.SetWriteDeadline(t) }
|
||||
@ -57,12 +57,10 @@ func Relay(left, right net.Conn) error {
|
||||
var wg sync.WaitGroup
|
||||
var wait = 5 * time.Second
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
_, err1 = Copy(right, left)
|
||||
right.SetReadDeadline(time.Now().Add(wait)) // unblock read on right
|
||||
}()
|
||||
})
|
||||
|
||||
_, err = Copy(left, right)
|
||||
left.SetReadDeadline(time.Now().Add(wait)) // unblock read on left
|
||||
|
||||
@ -133,3 +133,19 @@ func (d *Direct) IFaceIPs() (ips []net.IP) {
|
||||
}
|
||||
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
|
||||
`)
|
||||
}
|
||||
|
||||
@ -33,6 +33,8 @@ func (s *HTTP) Dial(network, addr string) (net.Conn, error) {
|
||||
}
|
||||
|
||||
buf := pool.GetBytesBuffer()
|
||||
defer pool.PutBytesBuffer(buf)
|
||||
|
||||
buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n")
|
||||
buf.WriteString("Host: " + addr + "\r\n")
|
||||
buf.WriteString("Proxy-Connection: Keep-Alive\r\n")
|
||||
@ -45,7 +47,6 @@ func (s *HTTP) Dial(network, addr string) (net.Conn, error) {
|
||||
// header ended
|
||||
buf.WriteString("\r\n")
|
||||
_, err = rc.Write(buf.Bytes())
|
||||
pool.PutBytesBuffer(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -64,10 +64,7 @@ func (c *TLSObfsConn) Write(b []byte) (int, error) {
|
||||
n := len(b)
|
||||
for i := 0; i < n; i += chunkSize {
|
||||
buf.Reset()
|
||||
end := i + chunkSize
|
||||
if end > n {
|
||||
end = n
|
||||
}
|
||||
end := min(i+chunkSize, n)
|
||||
|
||||
buf.Write([]byte{0x17, 0x03, 0x03})
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(b[i:end])))
|
||||
@ -107,10 +104,7 @@ func (c *TLSObfsConn) Read(b []byte) (int, error) {
|
||||
c.leftBytes = int(binary.BigEndian.Uint16(c.buf[:lenSize]))
|
||||
}
|
||||
|
||||
readLen := len(b)
|
||||
if readLen > c.leftBytes {
|
||||
readLen = c.leftBytes
|
||||
}
|
||||
readLen := min(len(b), c.leftBytes)
|
||||
|
||||
m, err := c.reader.Read(b[:readLen])
|
||||
if err != nil {
|
||||
|
||||
@ -60,7 +60,7 @@ func (s *RedirProxy) ListenAndServe() {
|
||||
return
|
||||
}
|
||||
|
||||
log.F("[redir] listening TCP on " + s.addr)
|
||||
log.F("[redir] listening TCP on %s", s.addr)
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
|
||||
@ -37,3 +37,10 @@ func (s *Reject) Dial(network, addr string) (net.Conn, error) {
|
||||
func (s *Reject) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||
return nil, errors.New("REJECT")
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.AddUsage("reject", `
|
||||
Reject scheme:
|
||||
reject://
|
||||
`)
|
||||
}
|
||||
|
||||
@ -118,13 +118,10 @@ 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)
|
||||
}
|
||||
|
||||
const baseBufSize = 8 + 1 // 1 is the len(userid)
|
||||
bufSize := baseBufSize
|
||||
|
||||
@ -24,10 +24,7 @@ func (w *writer) Write(p []byte) (n int, err error) {
|
||||
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)
|
||||
}
|
||||
end := min(n+len(buf), len(p))
|
||||
w.XORKeyStream(buf, p[n:end])
|
||||
nw, err = w.Writer.Write(buf[:end-n])
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ func (s *SS) Serve(c net.Conn) {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
@ -20,10 +21,12 @@ type SSH struct {
|
||||
proxy proxy.Proxy
|
||||
addr string
|
||||
|
||||
mu sync.Mutex
|
||||
conn net.Conn
|
||||
client *ssh.Client
|
||||
config *ssh.ClientConfig
|
||||
|
||||
once sync.Once
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -103,20 +106,14 @@ 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) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.once.Do(func() { go s.keepConn(s.initConn() == nil) })
|
||||
|
||||
if s.client != nil {
|
||||
if c, err := s.dial(network, addr); err == nil {
|
||||
return c, nil
|
||||
}
|
||||
s.conn.Close()
|
||||
}
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
if err := s.initConn(); err != nil {
|
||||
return nil, err
|
||||
if s.client == nil {
|
||||
return nil, errors.New("ssh client is nil")
|
||||
}
|
||||
|
||||
return s.dial(network, addr)
|
||||
}
|
||||
|
||||
@ -128,26 +125,52 @@ func (s *SSH) dial(network, addr string) (net.Conn, error) {
|
||||
}
|
||||
|
||||
func (s *SSH) initConn() error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
log.F("[ssh] connecting to %s", s.addr)
|
||||
c, err := s.dialer.Dial("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[ssh]: dial to %s error: %s", s.addr, err)
|
||||
log.F("[ssh] dial connection to %s error: %s", s.addr, err)
|
||||
return err
|
||||
}
|
||||
|
||||
c.SetDeadline(time.Now().Add(s.config.Timeout))
|
||||
sshConn, sshChan, sshReq, err := ssh.NewClientConn(c, s.addr, s.config)
|
||||
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)
|
||||
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(sshConn, sshChan, sshReq)
|
||||
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, err error) {
|
||||
return nil, proxy.ErrNotSupported
|
||||
|
||||
@ -1,342 +0,0 @@
|
||||
package cipher
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/des"
|
||||
"crypto/md5"
|
||||
"crypto/rc4"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/rand"
|
||||
|
||||
"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
|
||||
}
|
||||
@ -1,234 +0,0 @@
|
||||
// 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"
|
||||
"math/rand"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"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
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
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
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
@ -1,185 +0,0 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"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 i := 0; i < 32; i++ {
|
||||
ret = fmt.Sprintf("%s%c", ret, set[rand.Intn(len(set))])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *httpSimplePost) data2URLEncode(data []byte) (ret string) {
|
||||
for i := 0; i < len(data); i++ {
|
||||
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
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
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
|
||||
}
|
||||
@ -1,83 +0,0 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
|
||||
"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)
|
||||
rand.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
|
||||
}
|
||||
@ -1,313 +0,0 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"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)
|
||||
|
||||
rand.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)
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
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)
|
||||
rand.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
|
||||
rand.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))
|
||||
|
||||
rand.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
|
||||
}
|
||||
@ -1,280 +0,0 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
"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
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
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
|
||||
rand.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
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
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)
|
||||
|
||||
rand.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)
|
||||
rand.Read(a.data.clientID)
|
||||
b := make([]byte, 4)
|
||||
rand.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 {
|
||||
rand.Read(uid)
|
||||
} else {
|
||||
binary.LittleEndian.PutUint32(uid, uint32(userID))
|
||||
a.userKey = a.hashDigest([]byte(params[1]))
|
||||
}
|
||||
} else {
|
||||
rand.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])
|
||||
|
||||
rand.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
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
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
|
||||
}
|
||||
@ -1,320 +0,0 @@
|
||||
// https://github.com/shadowsocksr-backup/shadowsocks-rss/blob/master/doc/auth_chain_a.md
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
stdCipher "crypto/cipher"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
"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 := 0; i < 4; i++ {
|
||||
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
|
||||
}
|
||||
@ -1,81 +0,0 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sort"
|
||||
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("auth_chain_b", NewAuthChainB)
|
||||
}
|
||||
|
||||
func NewAuthChainB() IProtocol {
|
||||
a := &authChainA{
|
||||
salt: "auth_chain_b",
|
||||
hmac: tools.HmacMD5,
|
||||
hashDigest: tools.SHA1Sum,
|
||||
rnd: authChainBGetRandLen,
|
||||
recvInfo: recvInfo{
|
||||
recvID: 1,
|
||||
buffer: new(bytes.Buffer),
|
||||
},
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *authChainA) authChainBInitDataSize() {
|
||||
if len(a.Key) == 0 {
|
||||
return
|
||||
}
|
||||
// libev version
|
||||
random := &a.randomServer
|
||||
random.InitFromBin(a.Key)
|
||||
length := random.Next()%8 + 4
|
||||
a.dataSizeList = make([]int, length)
|
||||
for i := 0; i < int(length); i++ {
|
||||
a.dataSizeList[i] = int(random.Next() % 2340 % 2040 % 1440)
|
||||
}
|
||||
sort.Ints(a.dataSizeList)
|
||||
|
||||
length = random.Next()%16 + 8
|
||||
a.dataSizeList2 = make([]int, length)
|
||||
for i := 0; i < int(length); i++ {
|
||||
a.dataSizeList2[i] = int(random.Next() % 2340 % 2040 % 1440)
|
||||
}
|
||||
sort.Ints(a.dataSizeList2)
|
||||
}
|
||||
|
||||
func authChainBGetRandLen(dataLength int, random *tools.Shift128plusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int {
|
||||
if dataLength > 1440 {
|
||||
return 0
|
||||
}
|
||||
random.InitFromBinDatalen(lastHash[:16], dataLength)
|
||||
// libev version, upper_bound
|
||||
pos := sort.Search(len(dataSizeList), func(i int) bool { return dataSizeList[i] > dataLength+overhead })
|
||||
finalPos := uint64(pos) + random.Next()%uint64(len(dataSizeList))
|
||||
if finalPos < uint64(len(dataSizeList)) {
|
||||
return dataSizeList[finalPos] - dataLength - overhead
|
||||
}
|
||||
// libev version, upper_bound
|
||||
pos = sort.Search(len(dataSizeList2), func(i int) bool { return dataSizeList2[i] > dataLength+overhead })
|
||||
finalPos = uint64(pos) + random.Next()%uint64(len(dataSizeList2))
|
||||
if finalPos < uint64(len(dataSizeList2)) {
|
||||
return dataSizeList2[finalPos] - dataLength - overhead
|
||||
}
|
||||
if finalPos < uint64(pos+len(dataSizeList2)-1) {
|
||||
return 0
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@ -1,226 +0,0 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("auth_sha1_v4", NewAuthSHA1v4)
|
||||
}
|
||||
|
||||
type authSHA1v4 struct {
|
||||
ssr.ServerInfo
|
||||
data *AuthData
|
||||
hasSentHeader bool
|
||||
buffer bytes.Buffer
|
||||
}
|
||||
|
||||
func NewAuthSHA1v4() IProtocol {
|
||||
a := &authSHA1v4{}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *authSHA1v4) SetServerInfo(s *ssr.ServerInfo) {
|
||||
a.ServerInfo = *s
|
||||
}
|
||||
|
||||
func (a *authSHA1v4) GetServerInfo() (s *ssr.ServerInfo) {
|
||||
return &a.ServerInfo
|
||||
}
|
||||
|
||||
func (a *authSHA1v4) SetData(data any) {
|
||||
if auth, ok := data.(*AuthData); ok {
|
||||
a.data = auth
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authSHA1v4) GetData() any {
|
||||
if a.data == nil {
|
||||
a.data = &AuthData{}
|
||||
}
|
||||
return a.data
|
||||
}
|
||||
|
||||
func (a *authSHA1v4) packData(data []byte) (outData []byte) {
|
||||
dataLength := len(data)
|
||||
randLength := 1
|
||||
|
||||
if dataLength <= 1300 {
|
||||
if dataLength > 400 {
|
||||
randLength += rand.Intn(128)
|
||||
} else {
|
||||
randLength += rand.Intn(1024)
|
||||
}
|
||||
}
|
||||
|
||||
outLength := randLength + dataLength + 8
|
||||
outData = make([]byte, outLength)
|
||||
// 0~1, out length
|
||||
binary.BigEndian.PutUint16(outData[0:2], uint16(outLength&0xFFFF))
|
||||
// 2~3, crc of out length
|
||||
crc32 := ssr.CalcCRC32(outData, 2, 0xFFFFFFFF)
|
||||
binary.LittleEndian.PutUint16(outData[2:4], uint16(crc32&0xFFFF))
|
||||
// 4, rand length
|
||||
if randLength < 128 {
|
||||
outData[4] = uint8(randLength & 0xFF)
|
||||
} else {
|
||||
outData[4] = uint8(0xFF)
|
||||
binary.BigEndian.PutUint16(outData[5:7], uint16(randLength&0xFFFF))
|
||||
}
|
||||
// rand length+4~out length-4, data
|
||||
if dataLength > 0 {
|
||||
copy(outData[randLength+4:], data)
|
||||
}
|
||||
// out length-4~end, adler32 of full data
|
||||
adler := ssr.CalcAdler32(outData[:outLength-4])
|
||||
binary.LittleEndian.PutUint32(outData[outLength-4:], adler)
|
||||
|
||||
return outData
|
||||
}
|
||||
|
||||
func (a *authSHA1v4) packAuthData(data []byte) (outData []byte) {
|
||||
|
||||
dataLength := len(data)
|
||||
randLength := 1
|
||||
if dataLength <= 1300 {
|
||||
if dataLength > 400 {
|
||||
randLength += rand.Intn(128)
|
||||
} else {
|
||||
randLength += rand.Intn(1024)
|
||||
}
|
||||
}
|
||||
dataOffset := randLength + 4 + 2
|
||||
outLength := dataOffset + dataLength + 12 + ssr.ObfsHMACSHA1Len
|
||||
outData = make([]byte, outLength)
|
||||
a.data.connectionID++
|
||||
if a.data.connectionID > 0xFF000000 {
|
||||
a.data.clientID = nil
|
||||
}
|
||||
if len(a.data.clientID) == 0 {
|
||||
a.data.clientID = make([]byte, 8)
|
||||
rand.Read(a.data.clientID)
|
||||
b := make([]byte, 4)
|
||||
rand.Read(b)
|
||||
a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
||||
}
|
||||
// 0-1, out length
|
||||
binary.BigEndian.PutUint16(outData[0:2], uint16(outLength&0xFFFF))
|
||||
|
||||
// 2~6, crc of out length+salt+key
|
||||
salt := []byte("auth_sha1_v4")
|
||||
crcData := make([]byte, len(salt)+a.KeyLen+2)
|
||||
copy(crcData[0:2], outData[0:2])
|
||||
copy(crcData[2:], salt)
|
||||
copy(crcData[2+len(salt):], a.Key)
|
||||
crc32 := ssr.CalcCRC32(crcData, len(crcData), 0xFFFFFFFF)
|
||||
// 2~6, crc of out length+salt+key
|
||||
binary.LittleEndian.PutUint32(outData[2:], crc32)
|
||||
// 6~rand length+6, rand numbers
|
||||
rand.Read(outData[dataOffset-randLength : dataOffset])
|
||||
// 6, rand length
|
||||
if randLength < 128 {
|
||||
outData[6] = byte(randLength & 0xFF)
|
||||
} else {
|
||||
// 6, magic number 0xFF
|
||||
outData[6] = 0xFF
|
||||
// 7-8, rand length
|
||||
binary.BigEndian.PutUint16(outData[7:9], uint16(randLength&0xFFFF))
|
||||
}
|
||||
// rand length+6~rand length+10, time stamp
|
||||
now := time.Now().Unix()
|
||||
binary.LittleEndian.PutUint32(outData[dataOffset:dataOffset+4], uint32(now))
|
||||
// rand length+10~rand length+14, client ID
|
||||
copy(outData[dataOffset+4:dataOffset+4+4], a.data.clientID[0:4])
|
||||
// rand length+14~rand length+18, connection ID
|
||||
binary.LittleEndian.PutUint32(outData[dataOffset+8:dataOffset+8+4], a.data.connectionID)
|
||||
// rand length+18~rand length+18+data length, data
|
||||
copy(outData[dataOffset+12:], data)
|
||||
|
||||
key := make([]byte, a.IVLen+a.KeyLen)
|
||||
copy(key, a.IV)
|
||||
copy(key[a.IVLen:], a.Key)
|
||||
|
||||
h := tools.HmacSHA1(key, outData[:outLength-ssr.ObfsHMACSHA1Len])
|
||||
// out length-10~out length/rand length+18+data length~end, hmac
|
||||
copy(outData[outLength-ssr.ObfsHMACSHA1Len:], h[0:ssr.ObfsHMACSHA1Len])
|
||||
return outData
|
||||
}
|
||||
|
||||
func (a *authSHA1v4) PreEncrypt(plainData []byte) (outData []byte, err error) {
|
||||
a.buffer.Reset()
|
||||
dataLength := len(plainData)
|
||||
offset := 0
|
||||
if !a.hasSentHeader && dataLength > 0 {
|
||||
headSize := ssr.GetHeadSize(plainData, 30)
|
||||
if headSize > dataLength {
|
||||
headSize = dataLength
|
||||
}
|
||||
a.buffer.Write(a.packAuthData(plainData[:headSize]))
|
||||
offset += headSize
|
||||
dataLength -= headSize
|
||||
a.hasSentHeader = true
|
||||
}
|
||||
const blockSize = 4096
|
||||
for dataLength > blockSize {
|
||||
a.buffer.Write(a.packData(plainData[offset : offset+blockSize]))
|
||||
offset += blockSize
|
||||
dataLength -= blockSize
|
||||
}
|
||||
if dataLength > 0 {
|
||||
a.buffer.Write(a.packData(plainData[offset:]))
|
||||
}
|
||||
|
||||
return a.buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func (a *authSHA1v4) PostDecrypt(plainData []byte) (outData []byte, n int, err error) {
|
||||
a.buffer.Reset()
|
||||
dataLength := len(plainData)
|
||||
plainLength := dataLength
|
||||
for dataLength > 4 {
|
||||
crc32 := ssr.CalcCRC32(plainData, 2, 0xFFFFFFFF)
|
||||
if binary.LittleEndian.Uint16(plainData[2:4]) != uint16(crc32&0xFFFF) {
|
||||
//common.Error("auth_sha1_v4 post decrypt data crc32 error")
|
||||
return nil, 0, ssr.ErrAuthSHA1v4CRC32Error
|
||||
}
|
||||
length := int(binary.BigEndian.Uint16(plainData[0:2]))
|
||||
if length >= 8192 || length < 8 {
|
||||
//common.Error("auth_sha1_v4 post decrypt data length error")
|
||||
dataLength = 0
|
||||
plainData = nil
|
||||
return nil, 0, ssr.ErrAuthSHA1v4DataLengthError
|
||||
}
|
||||
if length > dataLength {
|
||||
break
|
||||
}
|
||||
|
||||
if ssr.CheckAdler32(plainData, length) {
|
||||
pos := int(plainData[4])
|
||||
if pos != 0xFF {
|
||||
pos += 4
|
||||
} else {
|
||||
pos = int(binary.BigEndian.Uint16(plainData[5:5+2])) + 4
|
||||
}
|
||||
outLength := length - pos - 4
|
||||
a.buffer.Write(plainData[pos : pos+outLength])
|
||||
dataLength -= length
|
||||
plainData = plainData[length:]
|
||||
} else {
|
||||
//common.Error("auth_sha1_v4 post decrypt incorrect checksum")
|
||||
dataLength = 0
|
||||
plainData = nil
|
||||
return nil, 0, ssr.ErrAuthSHA1v4IncorrectChecksum
|
||||
}
|
||||
}
|
||||
return a.buffer.Bytes(), plainLength - dataLength, nil
|
||||
}
|
||||
|
||||
func (a *authSHA1v4) GetOverhead() int {
|
||||
return 7
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||
)
|
||||
|
||||
type creator func() IProtocol
|
||||
|
||||
var (
|
||||
creatorMap = make(map[string]creator)
|
||||
)
|
||||
|
||||
type hmacMethod func(key []byte, data []byte) []byte
|
||||
type hashDigestMethod func(data []byte) []byte
|
||||
type rndMethod func(dataLength int, random *tools.Shift128plusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int
|
||||
|
||||
type IProtocol interface {
|
||||
SetServerInfo(s *ssr.ServerInfo)
|
||||
GetServerInfo() *ssr.ServerInfo
|
||||
PreEncrypt(data []byte) ([]byte, error)
|
||||
PostDecrypt(data []byte) ([]byte, int, error)
|
||||
SetData(data any)
|
||||
GetData() any
|
||||
GetOverhead() int
|
||||
}
|
||||
|
||||
type AuthData struct {
|
||||
clientID []byte
|
||||
connectionID uint32
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func register(name string, c creator) {
|
||||
creatorMap[name] = c
|
||||
}
|
||||
|
||||
func NewProtocol(name string) IProtocol {
|
||||
c, ok := creatorMap[strings.ToLower(name)]
|
||||
if ok {
|
||||
return c()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("origin", NewOrigin)
|
||||
}
|
||||
|
||||
type origin struct {
|
||||
ssr.ServerInfo
|
||||
}
|
||||
|
||||
func NewOrigin() IProtocol {
|
||||
a := &origin{}
|
||||
return a
|
||||
}
|
||||
|
||||
func (o *origin) SetServerInfo(s *ssr.ServerInfo) {
|
||||
o.ServerInfo = *s
|
||||
}
|
||||
|
||||
func (o *origin) GetServerInfo() (s *ssr.ServerInfo) {
|
||||
return &o.ServerInfo
|
||||
}
|
||||
|
||||
func (o *origin) PreEncrypt(data []byte) (encryptedData []byte, err error) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (o *origin) PostDecrypt(data []byte) ([]byte, int, error) {
|
||||
return data, len(data), nil
|
||||
}
|
||||
|
||||
func (o *origin) SetData(data any) {
|
||||
|
||||
}
|
||||
|
||||
func (o *origin) GetData() any {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *origin) GetOverhead() int {
|
||||
return 0
|
||||
}
|
||||
@ -1,105 +0,0 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("verify_sha1", NewVerifySHA1)
|
||||
register("ota", NewVerifySHA1)
|
||||
}
|
||||
|
||||
type verifySHA1 struct {
|
||||
ssr.ServerInfo
|
||||
hasSentHeader bool
|
||||
buffer bytes.Buffer
|
||||
chunkId uint32
|
||||
}
|
||||
|
||||
const (
|
||||
oneTimeAuthMask byte = 0x10
|
||||
)
|
||||
|
||||
func NewVerifySHA1() IProtocol {
|
||||
a := &verifySHA1{}
|
||||
return a
|
||||
}
|
||||
|
||||
func (v *verifySHA1) otaConnectAuth(data []byte) []byte {
|
||||
return append(data, tools.HmacSHA1(append(v.IV, v.Key...), data)...)
|
||||
}
|
||||
|
||||
func (v *verifySHA1) otaReqChunkAuth(chunkId uint32, data []byte) []byte {
|
||||
nb := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(nb, uint16(len(data)))
|
||||
chunkIdBytes := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(chunkIdBytes, chunkId)
|
||||
header := append(nb, tools.HmacSHA1(append(v.IV, chunkIdBytes...), data)...)
|
||||
return append(header, data...)
|
||||
}
|
||||
|
||||
func (v *verifySHA1) otaVerifyAuth(iv []byte, chunkId uint32, data []byte, expectedHmacSha1 []byte) bool {
|
||||
chunkIdBytes := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(chunkIdBytes, chunkId)
|
||||
actualHmacSha1 := tools.HmacSHA1(append(iv, chunkIdBytes...), data)
|
||||
return bytes.Equal(expectedHmacSha1, actualHmacSha1)
|
||||
}
|
||||
|
||||
func (v *verifySHA1) getAndIncreaseChunkId() (chunkId uint32) {
|
||||
chunkId = v.chunkId
|
||||
v.chunkId += 1
|
||||
return
|
||||
}
|
||||
|
||||
func (v *verifySHA1) SetServerInfo(s *ssr.ServerInfo) {
|
||||
v.ServerInfo = *s
|
||||
}
|
||||
|
||||
func (v *verifySHA1) GetServerInfo() (s *ssr.ServerInfo) {
|
||||
return &v.ServerInfo
|
||||
}
|
||||
|
||||
func (v *verifySHA1) SetData(data any) {
|
||||
|
||||
}
|
||||
|
||||
func (v *verifySHA1) GetData() any {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *verifySHA1) PreEncrypt(data []byte) (encryptedData []byte, err error) {
|
||||
v.buffer.Reset()
|
||||
dataLength := len(data)
|
||||
offset := 0
|
||||
if !v.hasSentHeader {
|
||||
data[0] |= oneTimeAuthMask
|
||||
v.buffer.Write(v.otaConnectAuth(data[:v.HeadLen]))
|
||||
v.hasSentHeader = true
|
||||
dataLength -= v.HeadLen
|
||||
offset += v.HeadLen
|
||||
}
|
||||
const blockSize = 4096
|
||||
for dataLength > blockSize {
|
||||
chunkId := v.getAndIncreaseChunkId()
|
||||
v.buffer.Write(v.otaReqChunkAuth(chunkId, data[offset:offset+blockSize]))
|
||||
dataLength -= blockSize
|
||||
offset += blockSize
|
||||
}
|
||||
if dataLength > 0 {
|
||||
chunkId := v.getAndIncreaseChunkId()
|
||||
v.buffer.Write(v.otaReqChunkAuth(chunkId, data[offset:]))
|
||||
}
|
||||
return v.buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func (v *verifySHA1) PostDecrypt(data []byte) ([]byte, int, error) {
|
||||
return data, len(data), nil
|
||||
}
|
||||
|
||||
func (v *verifySHA1) GetOverhead() int {
|
||||
return 0
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
package ssr
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
func calcShortAdler32(input []byte, a, b uint32) (uint32, uint32) {
|
||||
for _, i := range input {
|
||||
a += uint32(i)
|
||||
b += a
|
||||
}
|
||||
a %= 65521
|
||||
b %= 65521
|
||||
return a, b
|
||||
}
|
||||
|
||||
func CalcAdler32(input []byte) uint32 {
|
||||
var a uint32 = 1
|
||||
var b uint32 = 0
|
||||
const nMax = 5552
|
||||
for length := len(input); length > nMax; length -= nMax {
|
||||
a, b = calcShortAdler32(input[:nMax], a, b)
|
||||
input = input[nMax:]
|
||||
}
|
||||
a, b = calcShortAdler32(input, a, b)
|
||||
return (b << 16) + a
|
||||
}
|
||||
|
||||
func CheckAdler32(input []byte, l int) bool {
|
||||
adler32 := CalcAdler32(input[:l-4])
|
||||
checksum := binary.LittleEndian.Uint32(input[l-4:])
|
||||
return adler32 == checksum
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
package ssr
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
var (
|
||||
crc32Table = make([]uint32, 256)
|
||||
)
|
||||
|
||||
func init() {
|
||||
createCRC32Table()
|
||||
}
|
||||
|
||||
func createCRC32Table() {
|
||||
for i := 0; i < 256; i++ {
|
||||
crc := uint32(i)
|
||||
for j := 8; j > 0; j-- {
|
||||
if crc&1 == 1 {
|
||||
crc = (crc >> 1) ^ 0xEDB88320
|
||||
} else {
|
||||
crc >>= 1
|
||||
}
|
||||
}
|
||||
crc32Table[i] = crc
|
||||
}
|
||||
}
|
||||
|
||||
func CalcCRC32(input []byte, length int, value uint32) uint32 {
|
||||
value = 0xFFFFFFFF
|
||||
return DoCalcCRC32(input, 0, length, value)
|
||||
}
|
||||
|
||||
func DoCalcCRC32(input []byte, index int, length int, value uint32) uint32 {
|
||||
buffer := input
|
||||
for i := index; i < length; i++ {
|
||||
value = (value >> 8) ^ crc32Table[byte(value&0xFF)^buffer[i]]
|
||||
}
|
||||
return value ^ 0xFFFFFFFF
|
||||
}
|
||||
|
||||
func DoSetCRC32(buffer []byte, index int, length int) {
|
||||
crc := CalcCRC32(buffer[:length-4], length-4, 0xFFFFFFFF)
|
||||
binary.LittleEndian.PutUint32(buffer[length-4:], crc^0xFFFFFFFF)
|
||||
}
|
||||
|
||||
func SetCRC32(buffer []byte, length int) {
|
||||
DoSetCRC32(buffer, 0, length)
|
||||
}
|
||||
|
||||
func CheckCRC32(buffer []byte, length int) bool {
|
||||
crc := CalcCRC32(buffer, length, 0xFFFFFFFF)
|
||||
return crc == 0xFFFFFFFF
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
package ssr
|
||||
|
||||
import "errors"
|
||||
|
||||
const ObfsHMACSHA1Len = 10
|
||||
|
||||
var (
|
||||
ErrAuthSHA1v4CRC32Error = errors.New("auth_sha1_v4 post decrypt data crc32 error")
|
||||
ErrAuthSHA1v4DataLengthError = errors.New("auth_sha1_v4 post decrypt data length error")
|
||||
ErrAuthSHA1v4IncorrectChecksum = errors.New("auth_sha1_v4 post decrypt incorrect checksum")
|
||||
ErrAuthAES128IncorrectHMAC = errors.New("auth_aes128_* post decrypt incorrect hmac")
|
||||
ErrAuthAES128DataLengthError = errors.New("auth_aes128_* post decrypt length mismatch")
|
||||
ErrAuthChainDataLengthError = errors.New("auth_chain_* post decrypt length mismatch")
|
||||
ErrAuthChainIncorrectHMAC = errors.New("auth_chain_* post decrypt incorrect hmac")
|
||||
ErrAuthAES128IncorrectChecksum = errors.New("auth_aes128_* post decrypt incorrect checksum")
|
||||
ErrAuthAES128PosOutOfRange = errors.New("auth_aes128_* post decrypt pos out of range")
|
||||
ErrTLS12TicketAuthTooShortData = errors.New("tls1.2_ticket_auth too short data")
|
||||
ErrTLS12TicketAuthHMACError = errors.New("tls1.2_ticket_auth hmac verifying failed")
|
||||
ErrTLS12TicketAuthIncorrectMagicNumber = errors.New("tls1.2_ticket_auth incorrect magic number")
|
||||
)
|
||||
|
||||
type ServerInfo struct {
|
||||
Host string
|
||||
Port uint16
|
||||
Param string
|
||||
IV []byte
|
||||
IVLen int
|
||||
RecvIV []byte
|
||||
RecvIVLen int
|
||||
Key []byte
|
||||
KeyLen int
|
||||
HeadLen int
|
||||
TcpMss int
|
||||
Overhead int
|
||||
}
|
||||
|
||||
func GetHeadSize(data []byte, defaultValue int) int {
|
||||
if data == nil || len(data) < 2 {
|
||||
return defaultValue
|
||||
}
|
||||
headType := data[0] & 0x07
|
||||
switch headType {
|
||||
case 1:
|
||||
// IPv4 1+4+2
|
||||
return 7
|
||||
case 4:
|
||||
// IPv6 1+16+2
|
||||
return 19
|
||||
case 3:
|
||||
// domain name, variant length
|
||||
return 4 + int(data[1])
|
||||
}
|
||||
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func (s *ServerInfo) SetHeadLen(data []byte, defaultValue int) {
|
||||
s.HeadLen = GetHeadSize(data, defaultValue)
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
)
|
||||
|
||||
func HmacMD5(key []byte, data []byte) []byte {
|
||||
hmacMD5 := hmac.New(md5.New, key)
|
||||
hmacMD5.Write(data)
|
||||
return hmacMD5.Sum(nil)[:16]
|
||||
}
|
||||
|
||||
func HmacSHA1(key []byte, data []byte) []byte {
|
||||
hmacSHA1 := hmac.New(sha1.New, key)
|
||||
hmacSHA1.Write(data)
|
||||
return hmacSHA1.Sum(nil)[:20]
|
||||
}
|
||||
|
||||
func MD5Sum(d []byte) []byte {
|
||||
h := md5.New()
|
||||
h.Write(d)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func SHA1Sum(d []byte) []byte {
|
||||
h := sha1.New()
|
||||
h.Write(d)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func EVPBytesToKey(password string, keyLen int) (key []byte) {
|
||||
const md5Len = 16
|
||||
|
||||
cnt := (keyLen-1)/md5Len + 1
|
||||
m := make([]byte, cnt*md5Len)
|
||||
copy(m, MD5Sum([]byte(password)))
|
||||
|
||||
// Repeatedly call md5 until bytes generated is enough.
|
||||
// Each call to md5 uses data: prev md5 sum + password.
|
||||
d := make([]byte, md5Len+len(password))
|
||||
start := 0
|
||||
for i := 1; i < cnt; i++ {
|
||||
start += md5Len
|
||||
copy(d, m[start-md5Len:start])
|
||||
copy(d[md5Len:], password)
|
||||
copy(m[start:], MD5Sum(d))
|
||||
}
|
||||
return m[:keyLen]
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func IsLittleEndian() bool {
|
||||
const N int = int(unsafe.Sizeof(0))
|
||||
x := 0x1234
|
||||
p := unsafe.Pointer(&x)
|
||||
p2 := (*[N]byte)(p)
|
||||
if p2[0] == 0 {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
type Shift128plusContext struct {
|
||||
v [2]uint64
|
||||
}
|
||||
|
||||
func (ctx *Shift128plusContext) InitFromBin(bin []byte) {
|
||||
var fillBin [16]byte
|
||||
copy(fillBin[:], bin)
|
||||
|
||||
ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8])
|
||||
ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:])
|
||||
}
|
||||
|
||||
func (ctx *Shift128plusContext) InitFromBinDatalen(bin []byte, datalen int) {
|
||||
var fillBin [16]byte
|
||||
copy(fillBin[:], bin)
|
||||
binary.LittleEndian.PutUint16(fillBin[:2], uint16(datalen))
|
||||
|
||||
ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8])
|
||||
ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:])
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *Shift128plusContext) Next() uint64 {
|
||||
x := ctx.v[0]
|
||||
y := ctx.v[1]
|
||||
ctx.v[0] = y
|
||||
x ^= x << 23
|
||||
x ^= y ^ (x >> 17) ^ (y >> 26)
|
||||
ctx.v[1] = x
|
||||
return x + y
|
||||
}
|
||||
164
proxy/ssr/ssr.go
164
proxy/ssr/ssr.go
@ -1,164 +0,0 @@
|
||||
package ssr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"github.com/nadoo/glider/pkg/socks"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
|
||||
"github.com/nadoo/glider/proxy/ssr/internal"
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/cipher"
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/obfs"
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/protocol"
|
||||
ssrinfo "github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||
)
|
||||
|
||||
func init() {
|
||||
proxy.RegisterDialer("ssr", NewSSRDialer)
|
||||
}
|
||||
|
||||
// SSR struct.
|
||||
type SSR struct {
|
||||
dialer proxy.Dialer
|
||||
addr string
|
||||
|
||||
EncryptMethod string
|
||||
EncryptPassword string
|
||||
Obfs string
|
||||
ObfsParam string
|
||||
ObfsData any
|
||||
Protocol string
|
||||
ProtocolParam string
|
||||
ProtocolData any
|
||||
}
|
||||
|
||||
// NewSSR returns a shadowsocksr proxy, ssr://method:pass@host:port/query
|
||||
func NewSSR(s string, d proxy.Dialer) (*SSR, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("[ssr] parse err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := u.Host
|
||||
method := u.User.Username()
|
||||
pass, _ := u.User.Password()
|
||||
|
||||
p := &SSR{
|
||||
dialer: d,
|
||||
addr: addr,
|
||||
EncryptMethod: method,
|
||||
EncryptPassword: pass,
|
||||
}
|
||||
|
||||
query := u.Query()
|
||||
p.Protocol = query.Get("protocol")
|
||||
p.ProtocolParam = query.Get("protocol_param")
|
||||
p.Obfs = query.Get("obfs")
|
||||
p.ObfsParam = query.Get("obfs_param")
|
||||
|
||||
p.ProtocolData = new(protocol.AuthData)
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// NewSSRDialer returns a ssr proxy dialer.
|
||||
func NewSSRDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||
return NewSSR(s, d)
|
||||
}
|
||||
|
||||
// Addr returns forwarder's address
|
||||
func (s *SSR) 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 *SSR) Dial(network, addr string) (net.Conn, error) {
|
||||
target := socks.ParseAddr(addr)
|
||||
if target == nil {
|
||||
return nil, errors.New("[ssr] unable to parse address: " + addr)
|
||||
}
|
||||
|
||||
cipher, err := cipher.NewStreamCipher(s.EncryptMethod, s.EncryptPassword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := s.dialer.Dial("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[ssr] dial to %s error: %s", s.addr, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ssrconn := internal.NewSSTCPConn(c, cipher)
|
||||
if ssrconn.Conn == nil || ssrconn.RemoteAddr() == nil {
|
||||
return nil, errors.New("[ssr] nil connection")
|
||||
}
|
||||
|
||||
// should initialize obfs/protocol now
|
||||
rs := strings.Split(ssrconn.RemoteAddr().String(), ":")
|
||||
port, _ := strconv.Atoi(rs[1])
|
||||
|
||||
ssrconn.IObfs = obfs.NewObfs(s.Obfs)
|
||||
if ssrconn.IObfs == nil {
|
||||
return nil, errors.New("[ssr] unsupported obfs type: " + s.Obfs)
|
||||
}
|
||||
|
||||
obfsServerInfo := &ssrinfo.ServerInfo{
|
||||
Host: rs[0],
|
||||
Port: uint16(port),
|
||||
TcpMss: 1460,
|
||||
Param: s.ObfsParam,
|
||||
}
|
||||
ssrconn.IObfs.SetServerInfo(obfsServerInfo)
|
||||
|
||||
ssrconn.IProtocol = protocol.NewProtocol(s.Protocol)
|
||||
if ssrconn.IProtocol == nil {
|
||||
return nil, errors.New("[ssr] unsupported protocol type: " + s.Protocol)
|
||||
}
|
||||
|
||||
protocolServerInfo := &ssrinfo.ServerInfo{
|
||||
Host: rs[0],
|
||||
Port: uint16(port),
|
||||
TcpMss: 1460,
|
||||
Param: s.ProtocolParam,
|
||||
}
|
||||
ssrconn.IProtocol.SetServerInfo(protocolServerInfo)
|
||||
|
||||
if s.ObfsData == nil {
|
||||
s.ObfsData = ssrconn.IObfs.GetData()
|
||||
}
|
||||
ssrconn.IObfs.SetData(s.ObfsData)
|
||||
|
||||
if s.ProtocolData == nil {
|
||||
s.ProtocolData = ssrconn.IProtocol.GetData()
|
||||
}
|
||||
ssrconn.IProtocol.SetData(s.ProtocolData)
|
||||
|
||||
if _, err := ssrconn.Write(target); err != nil {
|
||||
ssrconn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ssrconn, err
|
||||
}
|
||||
|
||||
// DialUDP connects to the given address via the proxy.
|
||||
func (s *SSR) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||
return nil, proxy.ErrNotSupported
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.AddUsage("ssr", `
|
||||
SSR scheme:
|
||||
ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz
|
||||
`)
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
//go:build linux
|
||||
|
||||
package tproxy
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
//go:build linux
|
||||
|
||||
package tproxy
|
||||
|
||||
import (
|
||||
|
||||
@ -39,10 +39,7 @@ func (w *aeadWriter) Write(b []byte) (n int, err error) {
|
||||
|
||||
nonce := w.nonce[:w.NonceSize()]
|
||||
for left := len(b); left != 0; {
|
||||
writeLen = left + w.Overhead()
|
||||
if writeLen > chunkSize {
|
||||
writeLen = chunkSize
|
||||
}
|
||||
writeLen = min(left+w.Overhead(), chunkSize)
|
||||
dataLen = writeLen - w.Overhead()
|
||||
|
||||
w.chunkSizeEncoder.Encode(uint16(writeLen), lenBuf)
|
||||
|
||||
@ -23,10 +23,7 @@ func ChunkedWriter(w io.Writer, chunkSizeEncoder ChunkSizeEncoder) io.Writer {
|
||||
func (w *chunkedWriter) Write(p []byte) (n int, err error) {
|
||||
var dataLen int
|
||||
for left := len(p); left != 0; {
|
||||
dataLen = left
|
||||
if dataLen > chunkSize {
|
||||
dataLen = chunkSize
|
||||
}
|
||||
dataLen = min(left, chunkSize)
|
||||
w.chunkSizeEncoder.Encode(uint16(dataLen), w.buf)
|
||||
if _, err = (&net.Buffers{w.buf[:], p[n : n+dataLen]}).WriteTo(w.Writer); err != nil {
|
||||
break
|
||||
@ -69,10 +66,7 @@ func (r *chunkedReader) Read(p []byte) (int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
readLen := len(p)
|
||||
if readLen > r.left {
|
||||
readLen = r.left
|
||||
}
|
||||
readLen := min(len(p), r.left)
|
||||
|
||||
n, err := r.Reader.Read(p[:readLen])
|
||||
if err != nil {
|
||||
|
||||
@ -5,13 +5,14 @@ import (
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
crand "crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"math/rand"
|
||||
"math/rand/v2"
|
||||
"net"
|
||||
"runtime"
|
||||
"strings"
|
||||
@ -116,15 +117,12 @@ func NewClient(uuidStr, security string, alterID int, aead bool) (*Client, error
|
||||
return nil, errors.New("unknown security type: " + security)
|
||||
}
|
||||
|
||||
// NOTE: give rand a new seed to avoid the same sequence of values
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// NewConn returns a new vmess conn.
|
||||
func (c *Client) NewConn(rc net.Conn, target string, cmd CmdType) (*Conn, error) {
|
||||
r := rand.Intn(c.count)
|
||||
r := rand.IntN(c.count)
|
||||
conn := &Conn{user: c.users[r], opt: c.opt, aead: c.aead, security: c.security, Conn: rc}
|
||||
|
||||
var err error
|
||||
@ -134,12 +132,12 @@ func (c *Client) NewConn(rc net.Conn, target string, cmd CmdType) (*Conn, error)
|
||||
}
|
||||
|
||||
randBytes := pool.GetBuffer(32)
|
||||
rand.Read(randBytes)
|
||||
crand.Read(randBytes)
|
||||
copy(conn.reqBodyIV[:], randBytes[:16])
|
||||
copy(conn.reqBodyKey[:], randBytes[16:32])
|
||||
pool.PutBuffer(randBytes)
|
||||
|
||||
conn.reqRespV = byte(rand.Intn(1 << 8))
|
||||
conn.reqRespV = byte(rand.IntN(1 << 8))
|
||||
|
||||
if conn.aead {
|
||||
bodyIV := sha256.Sum256(conn.reqBodyIV[:])
|
||||
@ -195,7 +193,7 @@ func (c *Conn) Request(cmd CmdType) error {
|
||||
buf.WriteByte(c.opt) // Opt
|
||||
|
||||
// pLen and Sec
|
||||
paddingLen := rand.Intn(16)
|
||||
paddingLen := rand.IntN(16)
|
||||
pSec := byte(paddingLen<<4) | c.security // P(4bit) and Sec(4bit)
|
||||
buf.WriteByte(pSec)
|
||||
|
||||
@ -210,7 +208,7 @@ func (c *Conn) Request(cmd CmdType) error {
|
||||
// padding
|
||||
if paddingLen > 0 {
|
||||
padding := pool.GetBuffer(paddingLen)
|
||||
rand.Read(padding)
|
||||
crand.Read(padding)
|
||||
buf.Write(padding)
|
||||
pool.PutBuffer(padding)
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ func nextID(oldID [16]byte) (newID [16]byte) {
|
||||
func (u *User) GenAlterIDUsers(alterID int) []*User {
|
||||
users := make([]*User, alterID)
|
||||
preID := u.UUID
|
||||
for i := 0; i < alterID; i++ {
|
||||
for i := range alterID {
|
||||
newID := nextID(preID)
|
||||
// NOTE: alterID user is a user which have a different uuid but a same cmdkey with the primary user.
|
||||
users[i] = &User{UUID: newID, CmdKey: u.CmdKey}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
//go:build linux
|
||||
|
||||
package vsock
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
//go:build linux
|
||||
|
||||
package vsock
|
||||
|
||||
import (
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
//go:build linux
|
||||
|
||||
// Source code from:
|
||||
// https://github.com/linuxkit/virtsock/tree/master/pkg/vsock
|
||||
|
||||
package vsock
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
//go:build linux
|
||||
|
||||
package vsock
|
||||
|
||||
import (
|
||||
|
||||
@ -25,7 +25,7 @@ package ws
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"math/rand"
|
||||
"math/rand/v2"
|
||||
"net"
|
||||
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
@ -93,7 +93,7 @@ func (w *frameWriter) Write(b []byte) (int, error) {
|
||||
defer pool.PutBuffer(payload)
|
||||
|
||||
// payload with mask
|
||||
for i := 0; i < nPayload; i++ {
|
||||
for i := range nPayload {
|
||||
payload[i] = b[i] ^ w.maskKey[i%4]
|
||||
}
|
||||
|
||||
@ -151,10 +151,7 @@ func (r *frameReader) Read(b []byte) (int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
readLen := int64(len(b))
|
||||
if readLen > r.left {
|
||||
readLen = r.left
|
||||
}
|
||||
readLen := min(int64(len(b)), r.left)
|
||||
|
||||
m, err := io.ReadFull(r.Reader, b[:readLen])
|
||||
if err != nil {
|
||||
|
||||
@ -225,7 +225,7 @@ func (p *FwdrGroup) Check() {
|
||||
|
||||
log.F("[group] %s: using check config: %s", p.name, p.config.Check)
|
||||
|
||||
for i := 0; i < len(p.fwdrs); i++ {
|
||||
for i := range p.fwdrs {
|
||||
go p.check(p.fwdrs[i], checker)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
//go:build linux
|
||||
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
//go:build linux
|
||||
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
@ -126,58 +128,69 @@ func (d *dhcpd) handleDHCP(serverIP net.IP, mask net.IPMask, pool *Pool) server4
|
||||
return
|
||||
}
|
||||
|
||||
var reqIP netip.Addr
|
||||
var reqType, replyType dhcpv4.MessageType
|
||||
switch reqType = m.MessageType(); reqType {
|
||||
|
||||
reqType = m.MessageType()
|
||||
log.F("[dpcpd] %s: %s from %v(%v)", d.name, reqType, m.ClientHWAddr, m.ClientIPAddr)
|
||||
|
||||
switch reqType {
|
||||
case dhcpv4.MessageTypeDiscover:
|
||||
replyType = dhcpv4.MessageTypeOffer
|
||||
case dhcpv4.MessageTypeRequest, dhcpv4.MessageTypeInform:
|
||||
case dhcpv4.MessageTypeInform:
|
||||
replyType = dhcpv4.MessageTypeAck
|
||||
case dhcpv4.MessageTypeRelease:
|
||||
case dhcpv4.MessageTypeRequest:
|
||||
replyType = dhcpv4.MessageTypeAck
|
||||
if m.Options.Has(dhcpv4.OptionRequestedIPAddress) {
|
||||
reqIP, _ = netip.AddrFromSlice(m.Options.Get(dhcpv4.OptionRequestedIPAddress))
|
||||
} else {
|
||||
// client uses Unicast to renew ip address lease, just take client ip
|
||||
reqIP = netip.AddrFrom4([4]byte(m.ClientIPAddr.To4()))
|
||||
}
|
||||
case dhcpv4.MessageTypeRelease, dhcpv4.MessageTypeDecline:
|
||||
pool.ReleaseIP(m.ClientHWAddr)
|
||||
log.F("[dpcpd] %s:%v released ip %v", d.name, m.ClientHWAddr, m.ClientIPAddr)
|
||||
return
|
||||
case dhcpv4.MessageTypeDecline:
|
||||
pool.ReleaseIP(m.ClientHWAddr)
|
||||
log.F("[dpcpd] %s: received decline message from %v", d.name, m.ClientHWAddr)
|
||||
return
|
||||
default:
|
||||
log.F("[dpcpd] %s: can't handle type %v", d.name, reqType)
|
||||
log.F("[dpcpd] %s: can't handle type %v from %v", d.name, reqType, m.ClientHWAddr)
|
||||
return
|
||||
}
|
||||
|
||||
replyIP, err := pool.LeaseIP(m.ClientHWAddr)
|
||||
replyIP, err := pool.LeaseIP(m.ClientHWAddr, reqIP)
|
||||
if err != nil {
|
||||
log.F("[dpcpd] %s: can not assign IP, error %s", d.name, err)
|
||||
log.F("[dpcpd] %s: can not assign IP for %v, error: %s", d.name, m.ClientHWAddr, err)
|
||||
return
|
||||
}
|
||||
|
||||
reply, err := dhcpv4.NewReplyFromRequest(m,
|
||||
if reqType == dhcpv4.MessageTypeRequest && !reqIP.IsUnspecified() && reqIP != replyIP {
|
||||
replyType = dhcpv4.MessageTypeNak
|
||||
}
|
||||
|
||||
resp, err := dhcpv4.NewReplyFromRequest(m,
|
||||
dhcpv4.WithMessageType(replyType),
|
||||
dhcpv4.WithServerIP(serverIP),
|
||||
dhcpv4.WithNetmask(mask),
|
||||
dhcpv4.WithYourIP(replyIP.AsSlice()),
|
||||
dhcpv4.WithRouter(serverIP),
|
||||
dhcpv4.WithDNS(serverIP),
|
||||
// RFC 2131, Section 4.3.1. Server Identifier: MUST
|
||||
dhcpv4.WithOption(dhcpv4.OptServerIdentifier(serverIP)),
|
||||
dhcpv4.WithServerIP(serverIP), //
|
||||
// RFC 2131, Section 4.3.1. IP lease time: MUST
|
||||
dhcpv4.WithOption(dhcpv4.OptIPAddressLeaseTime(d.lease)),
|
||||
// RFC 2131, Section 4.3.1. Server Identifier: MUST
|
||||
dhcpv4.WithOption(dhcpv4.OptServerIdentifier(serverIP)),
|
||||
)
|
||||
if err != nil {
|
||||
log.F("[dpcpd] %s: can not create reply message, error %s", d.name, err)
|
||||
log.F("[dpcpd] %s: can not create reply message, error: %s", d.name, err)
|
||||
return
|
||||
}
|
||||
|
||||
if val := m.Options.Get(dhcpv4.OptionClientIdentifier); len(val) > 0 {
|
||||
reply.UpdateOption(dhcpv4.OptGeneric(dhcpv4.OptionClientIdentifier, val))
|
||||
}
|
||||
|
||||
if _, err := conn.WriteTo(reply.ToBytes(), peer); err != nil {
|
||||
log.F("[dpcpd] %s: could not write to client %s(%s): %s", d.name, peer, reply.ClientHWAddr, err)
|
||||
if err := reply(d.iface, resp); err != nil {
|
||||
log.F("[dpcpd] %s: could not write to %v(%v): %s",
|
||||
d.name, resp.ClientHWAddr, peer, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.F("[dpcpd] %s: lease %v to client %v", d.name, replyIP, reply.ClientHWAddr)
|
||||
log.F("[dpcpd] %s: %s to %v for %v",
|
||||
d.name, replyType, resp.ClientHWAddr, replyIP)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
//go:build linux
|
||||
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"math/rand/v2"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
@ -38,13 +40,12 @@ func NewPool(lease time.Duration, start, end netip.Addr) (*Pool, error) {
|
||||
for n := s; n <= e; n++ {
|
||||
items = append(items, &item{ip: numToIPv4(n)})
|
||||
}
|
||||
rand.Seed(time.Now().Unix())
|
||||
|
||||
p := &Pool{items: items, lease: lease}
|
||||
go func() {
|
||||
for now := range time.Tick(time.Second) {
|
||||
p.mutex.Lock()
|
||||
for i := 0; i < len(items); i++ {
|
||||
for i := range len(items) {
|
||||
if !items[i].expire.IsZero() && now.After(items[i].expire) {
|
||||
items[i].mac = nil
|
||||
items[i].expire = time.Time{}
|
||||
@ -58,17 +59,31 @@ func NewPool(lease time.Duration, start, end netip.Addr) (*Pool, error) {
|
||||
}
|
||||
|
||||
// LeaseIP leases an ip to mac from dhcp pool.
|
||||
func (p *Pool) LeaseIP(mac net.HardwareAddr) (netip.Addr, error) {
|
||||
func (p *Pool) LeaseIP(mac net.HardwareAddr, ip netip.Addr) (netip.Addr, error) {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
// static ip and leased ip
|
||||
for _, item := range p.items {
|
||||
if bytes.Equal(mac, item.mac) {
|
||||
if !item.expire.IsZero() {
|
||||
item.expire = time.Now().Add(p.lease)
|
||||
}
|
||||
return item.ip, nil
|
||||
}
|
||||
}
|
||||
|
||||
idx := rand.Intn(len(p.items))
|
||||
// requested ip
|
||||
for _, item := range p.items {
|
||||
if item.ip == ip && item.mac == nil {
|
||||
item.mac = mac
|
||||
item.expire = time.Now().Add(p.lease)
|
||||
return item.ip, nil
|
||||
}
|
||||
}
|
||||
|
||||
// lease new ip
|
||||
idx := rand.IntN(len(p.items))
|
||||
for _, item := range p.items[idx:] {
|
||||
if item.mac == nil {
|
||||
item.mac = mac
|
||||
@ -96,7 +111,7 @@ func (p *Pool) LeaseStaticIP(mac net.HardwareAddr, ip netip.Addr) {
|
||||
for _, item := range p.items {
|
||||
if item.ip == ip {
|
||||
item.mac = mac
|
||||
item.expire = time.Now().Add(time.Hour * 24 * 365 * 50) // 50 years
|
||||
item.expire = time.Time{}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -107,7 +122,8 @@ func (p *Pool) ReleaseIP(mac net.HardwareAddr) {
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
for _, item := range p.items {
|
||||
if bytes.Equal(mac, item.mac) {
|
||||
// not static ip
|
||||
if !item.expire.IsZero() && bytes.Equal(mac, item.mac) {
|
||||
item.mac = nil
|
||||
item.expire = time.Time{}
|
||||
}
|
||||
|
||||
86
service/dhcpd/reply.go
Normal file
86
service/dhcpd/reply.go
Normal file
@ -0,0 +1,86 @@
|
||||
//go:build linux
|
||||
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
)
|
||||
|
||||
func reply(iface *net.Interface, resp *dhcpv4.DHCPv4) error {
|
||||
p := [590]byte{12: 0x08, //ethernet layer: 14 bytes
|
||||
14: 0x45, 16: 0x02, 17: 0x40, 22: 0x40, 23: 0x11, //ip layer: 20 bytes
|
||||
35: 67, 37: 68, 38: 0x02, 39: 0x2c, //udp layer: 8 bytes
|
||||
}
|
||||
|
||||
copy(p[0:], resp.ClientHWAddr[0:6])
|
||||
copy(p[6:], iface.HardwareAddr[0:6])
|
||||
copy(p[26:], resp.ServerIPAddr[0:4])
|
||||
copy(p[30:], resp.YourIPAddr[0:4])
|
||||
|
||||
// ip layer checksum
|
||||
checksum := checksum(p[14:34])
|
||||
binary.BigEndian.PutUint16(p[24:], checksum)
|
||||
|
||||
// dhcp payload
|
||||
copy(p[42:], resp.ToBytes())
|
||||
|
||||
// udp layer checksum, set to zero
|
||||
// https://datatracker.ietf.org/doc/html/rfc768
|
||||
// An all zero transmitted checksum value means that the transmitter generated no
|
||||
// checksum (for debugging or for higher level protocols that don't care).
|
||||
|
||||
fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot open socket: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
err = syscall.Close(fd)
|
||||
if err != nil {
|
||||
log.F("dhcpd: cannot close socket: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||
if err != nil {
|
||||
log.F("dhcpd: cannot set option for socket: %v", err)
|
||||
}
|
||||
|
||||
var hwAddr [8]byte
|
||||
copy(hwAddr[0:6], resp.ClientHWAddr[0:6])
|
||||
ethAddr := syscall.SockaddrLinklayer{
|
||||
Protocol: 0,
|
||||
Ifindex: iface.Index,
|
||||
Halen: 6,
|
||||
Addr: hwAddr,
|
||||
}
|
||||
err = syscall.Sendto(fd, p[:], 0, ðAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot send frame via socket: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checksum(bytes []byte) uint16 {
|
||||
var csum uint32
|
||||
for i := 0; i < len(bytes); i += 2 {
|
||||
csum += uint32(bytes[i]) << 8
|
||||
csum += uint32(bytes[i+1])
|
||||
}
|
||||
for {
|
||||
// Break when sum is less or equals to 0xFFFF
|
||||
if csum <= 65535 {
|
||||
break
|
||||
}
|
||||
// Add carry to the sum
|
||||
csum = (csum >> 16) + uint32(uint16(csum))
|
||||
}
|
||||
// Flip all the bits
|
||||
return ^uint16(csum)
|
||||
}
|
||||
@ -2,6 +2,10 @@
|
||||
|
||||
set -e
|
||||
|
||||
if test ! -f "/etc/glider/glider.conf"; then
|
||||
cp /etc/glider/glider.conf.example /etc/glider/glider.conf
|
||||
fi
|
||||
|
||||
/bin/systemctl daemon-reload
|
||||
|
||||
if /bin/systemctl is-active --quiet glider@glider; then
|
||||
|
||||
Loading…
Reference in New Issue
Block a user