Compare commits

..

37 Commits

Author SHA1 Message Date
nadoo
38b34030bc chore: use Go1.26
Some checks failed
Build / Build (push) Has been cancelled
2026-06-26 00:58:58 +08:00
nadoo
e7b12e36a9 ssr: drop ssr protocol support
Some checks are pending
Build / Build (push) Waiting to run
2026-06-25 23:18:47 +08:00
nadoo
86f9f078fd anytls: support udp 2026-06-25 22:10:53 +08:00
nadoo
533571f1ec anytls: support cleartext mode
Some checks failed
Build / Build (push) Has been cancelled
2026-06-24 00:07:19 +08:00
nadoo
73a594aae1 chore: update dependencies
Some checks are pending
Build / Build (push) Waiting to run
2026-06-23 23:28:42 +08:00
nadoo
cb6f2d1e1a anytls: support anytls 2026-06-23 23:17:09 +08:00
nadoo
bb439c9345 redir: fix test error
Some checks failed
Build / Build (push) Has been cancelled
2025-02-28 00:42:46 +08:00
nadoo
6aee7b35c0 chore: update math/rand to math/rand/v2 2025-02-28 00:40:20 +08:00
nadoo
8e81e09a8f pool: optimized the bytes buffer pool
Some checks are pending
Build / Build (push) Waiting to run
2025-02-26 21:44:54 +08:00
nadoo
bd40b07388 chore: update dependencies and optimize some code style 2025-02-26 21:34:43 +08:00
nadoo
40809b56a9 chore: use Go1.24
Some checks failed
Build / Build (push) Has been cancelled
2025-02-22 12:27:37 +08:00
nadoo
2f154678a9 docker: update binary folder 2024-12-21 23:36:52 +08:00
nadoo
b598c03dab chore: update dependencies 2024-12-21 20:05:13 +08:00
nadoo
1108ef29a0 chore: update dependencies 2024-08-16 22:12:40 +08:00
nadoo
708db591e9 ci: use Go1.23 2024-08-15 00:10:25 +08:00
nadoo
0d75bbda7e chore: update dependencies 2024-08-14 22:58:09 +08:00
nadoo
62f2a85677 chore: update dependencies 2024-08-06 22:03:02 +08:00
nadoo
7d4075282d dns: change UDPMaxLen to 1232 bytes 2024-08-05 23:31:19 +08:00
nadoo
c71c95482a chore: update dependencies 2024-04-28 23:03:41 +08:00
Lentin
4b8b05aa3e
chore: update glider.conf.example (#403)
fix https://github.com/nadoo/glider/issues/354
2024-04-28 22:51:32 +08:00
nadoo
6d2b1e95cc chore: change config example to default listen on 127.0.0.1 (fix #391) 2024-01-29 22:30:28 +08:00
nadoo
80a7d3b7fd dhcpd: use unicast to reply messages 2024-01-29 21:10:02 +08:00
nadoo
7016a3d340 dhcpd: handle client requested ip option 2024-01-26 18:53:46 +08:00
nadoo
c7d072372b chore: update dependencies 2023-11-28 18:26:33 +08:00
nadoo
d0e2d9be42 Merge branch 'master' into dev 2023-03-20 21:06:16 +08:00
Felix Yan
4f12a4f308
chore: Correct a typo in config/README.md (#365) 2023-03-20 21:03:10 +08:00
nadoo
6815f866cb goreleaser: remove deprecated options 2023-03-13 20:42:36 +08:00
nadoo
7e800555d7 chore(deps): update smux pkg 2023-03-09 18:36:30 +08:00
nadoo
8d0d8881b1 chore: use Go1.20 2023-03-07 18:39:49 +08:00
nadoo
03157367ca dhcpd: do not release static records 2022-12-19 00:01:14 +08:00
nadoo
d57d35c062 chore: fix typo and update dependencies 2022-06-02 09:59:51 +08:00
xfzka
0ef0208615
chore: Update README.md (#327)
if only wanna listen dns port, we need remove '-listen' option, otherwise can't run. will report error: `log.go:40: [mixed] failed to listen on -dns=:53: listen tcp: lookup -dns=: no such host`
2022-05-27 18:22:43 +08:00
nadoo
badb17e921 chore: do not override config file when installing deb package 2022-05-13 18:46:39 +08:00
nadoo
d1eacebd25 chore: bump version to v0.16.2 2022-05-12 23:25:25 +08:00
nadoo
1c0106ce6b fix(ssh): optimized the connection initialization 2022-05-08 23:57:55 +08:00
nadoo
1e01d8692d ssh: fixed an issue for unreachable addrs (fix #324) 2022-05-05 17:12:45 +08:00
nadoo
846ca0b699 chore: fixed typo and add some help info 2022-05-03 18:19:33 +08:00
85 changed files with 1962 additions and 4588 deletions

View File

@ -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 \

View File

@ -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 }}

View File

@ -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
View File

@ -17,12 +17,15 @@
# custom
.idea
.vscode
.zed
.DS_Store
# dev test only
/dev/
.gocache/
dev*.go
*_test.go
dist

View File

@ -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"

View File

@ -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"

View File

@ -1,11 +1,12 @@
# [glider](https://github.com/nadoo/glider)
[![Go Version](https://img.shields.io/github/go-mod/go-version/nadoo/glider?style=flat-square)](https://go.dev/dl/)
[![Go Report Card](https://goreportcard.com/badge/github.com/nadoo/glider?style=flat-square)](https://goreportcard.com/report/github.com/nadoo/glider)
[![GitHub release](https://img.shields.io/github/v/release/nadoo/glider.svg?style=flat-square&include_prereleases)](https://github.com/nadoo/glider/releases)
[![Actions Status](https://img.shields.io/github/workflow/status/nadoo/glider/Build?style=flat-square)](https://github.com/nadoo/glider/actions)
[![Go Version](https://img.shields.io/github/go-mod/go-version/nadoo/glider?style=flat-square)](https://go.dev/dl/)
[![Actions Status](https://img.shields.io/github/actions/workflow/status/nadoo/glider/build.yml?branch=dev&style=flat-square)](https://github.com/nadoo/glider/actions)
[![DockerHub](https://img.shields.io/docker/image-size/nadoo/glider?color=blue&label=docker&style=flat-square)](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`

View File

@ -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.
`

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -17,7 +17,7 @@ import (
)
var (
version = "0.16.1"
version = "0.17.0"
config = parseConfig()
)

View File

@ -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))
}
}
}

View File

@ -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.

View File

@ -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 {

View File

@ -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 (

View File

@ -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")
}
}

View File

@ -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

View File

@ -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]

View File

@ -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
}
}

View File

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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) }

View File

@ -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

View File

@ -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
`)
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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()

View File

@ -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://
`)
}

View File

@ -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

View File

@ -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])
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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]
}

View File

@ -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
}

View File

@ -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
`)
}

View File

@ -1,3 +1,5 @@
//go:build linux
package tproxy
import (

View File

@ -1,3 +1,5 @@
//go:build linux
package tproxy
import (

View File

@ -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)

View File

@ -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 {

View File

@ -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)
}

View File

@ -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}

View File

@ -1,3 +1,5 @@
//go:build linux
package vsock
import (

View File

@ -1,3 +1,5 @@
//go:build linux
package vsock
import (

View File

@ -1,6 +1,7 @@
//go:build linux
// Source code from:
// https://github.com/linuxkit/virtsock/tree/master/pkg/vsock
package vsock
import (

View File

@ -1,3 +1,5 @@
//go:build linux
package vsock
import (

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -1,3 +1,5 @@
//go:build linux
package dhcpd
import (

View File

@ -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)
}
}

View File

@ -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
View 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, &ethAddr)
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)
}

View File

@ -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