Compare commits

..

No commits in common. "main" and "v0.13.0" have entirely different histories.

148 changed files with 2293 additions and 6226 deletions

View File

@ -1,38 +0,0 @@
# Build Stage
FROM --platform=$BUILDPLATFORM alpine AS build-env
COPY ./dist /dist
RUN apk add --no-cache ca-certificates
ARG TARGETPLATFORM
RUN case $TARGETPLATFORM in \
'linux/386') \
export FOLDER='default_linux_386_sse2'; \
;; \
'linux/amd64') \
export FOLDER='default_linux_amd64_v1'; \
;; \
'linux/arm/v6') \
export FOLDER='default_linux_arm_6'; \
;; \
'linux/arm/v7') \
export FOLDER='default_linux_arm_7'; \
;; \
'linux/arm64') \
export FOLDER='default_linux_arm64_v8.0'; \
;; \
'linux/riscv64') \
export FOLDER='default_linux_riscv64_rva20u64'; \
;; \
*) echo >&2 "error: unsupported architecture '$TARGETPLATFORM'"; exit 1 ;; \
esac \
&& mv /dist/$FOLDER /app
# Final Stage
FROM scratch
COPY --from=build-env /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build-env /app /app
WORKDIR /app
USER 1000
ENTRYPOINT ["./glider"]

View File

@ -1,103 +1,35 @@
name: Build name: Build
on: on: [push]
push:
branches:
- "dev"
tags:
- "*"
pull_request:
env:
APP_NAME: glider
DOCKERHUB_REPO: nadoo/glider
GHCR_REPO: ghcr.io/nadoo/glider
PLATFORMS: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/riscv64
jobs: jobs:
build:
name: Build test:
name: Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set Vars
run: |
echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v1
with: with:
check-latest: true go-version: 1.15.x
go-version-file: "go.mod" - name: Go Env
cache: true run: go env
- name: Test - name: Test
run: go test -v ./... run: go test -v .
build:
name: Build
runs-on: ubuntu-latest
needs: [test]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.15.x
- name: Go Env
run: go env
- name: Build - name: Build
uses: goreleaser/goreleaser-action@v6 run: go build -v .
if: "!startsWith(github.ref, 'refs/tags/')"
with:
args: build --snapshot --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Artifacts
uses: actions/upload-artifact@v4
if: "!startsWith(github.ref, 'refs/tags/')"
with:
name: ${{ env.APP_NAME }}-dev-${{ env.SHA_SHORT }}
path: |
./dist/default_linux_amd64_v1/${{ env.APP_NAME }}
./dist/default_linux_arm64/${{ env.APP_NAME }}
./dist/default_darwin_arm64/${{ env.APP_NAME }}
./dist/default_windows_amd64_v1/${{ env.APP_NAME }}.exe
- name: Release
uses: goreleaser/goreleaser-action@v6
if: startsWith(github.ref, 'refs/tags/')
with:
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Docker - Set up Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Docker - Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker - Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker - Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.DOCKERHUB_REPO }}
${{ env.GHCR_REPO }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Docker - Build and push
uses: docker/build-push-action@v6
with:
context: .
file: .Dockerfile
platforms: ${{ env.PLATFORMS }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}

86
.github/workflows/docker.yml vendored Normal file
View File

@ -0,0 +1,86 @@
# https://github.com/docker/build-push-action#usage
name: Docker
on:
push:
branches:
- dev
tags:
- '*'
jobs:
publish:
name: Publish
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
buildkitd-flags: "--debug"
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GHCR_TOKEN }}
- name: Build dev branch and push
if: github.ref == 'refs/heads/dev'
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm/v7,linux/arm64
push: true
tags: 'nadoo/glider:dev,ghcr.io/nadoo/glider:dev'
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,mode=max,dest=/tmp/.buildx-cache
- name: Get all docker tags
if: startsWith(github.ref, 'refs/tags/')
uses: actions/github-script@v3
id: tags
with:
script: |
const ref = `${context.payload.ref.replace(/\/?refs\/tags\//, '')}`
const tags = [
'nadoo/glider:latest',
`nadoo/glider:${ref}`,
'ghcr.io/nadoo/glider:latest',
`ghcr.io/nadoo/glider:${ref}`
]
return tags.join(',')
result-encoding: string
- name: Build release and push
if: startsWith(github.ref, 'refs/tags/')
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm/v7,linux/arm64
push: true
tags: ${{steps.tags.outputs.result}}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,mode=max,dest=/tmp/.buildx-cache

28
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: Release
on:
push:
tags:
- v*
jobs:
release:
name: Release on GitHub
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.15.x
- name: Go Env
run: go env
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,17 +0,0 @@
name: 'Close stale issues'
on:
schedule:
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
days-before-stale: 90
days-before-close: 5
exempt-issue-labels: "bug,enhancement"
exempt-pr-labels: "bug,enhancement"

3
.gitignore vendored
View File

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

View File

@ -1,9 +1,23 @@
version: 2 # Make sure to check the documentation at http://goreleaser.com
# release:
# git tag -a v0.1.0 -m "v0.1.0"
# git push origin v0.1.0
# goreleaser release --skip-publish --rm-dist
# #git tag -d v0.1.0
# #git push origin --delete tag v0.1.0
# snapshot:
# goreleaser --snapshot --rm-dist
# https://goreleaser.com/customization/
before: before:
hooks: hooks:
- go mod tidy - go mod tidy
# https://goreleaser.com/customization/build/
builds: builds:
- id: default - id: default
env: env:
@ -12,7 +26,6 @@ builds:
- windows - windows
- linux - linux
- darwin - darwin
- freebsd
goarch: goarch:
- 386 - 386
- amd64 - amd64
@ -22,10 +35,6 @@ builds:
- mipsle - mipsle
- mips64 - mips64
- mips64le - mips64le
- riscv64
goamd64:
- v1
- v3
goarm: goarm:
- 6 - 6
- 7 - 7
@ -33,64 +42,37 @@ builds:
- hardfloat - hardfloat
- softfloat - softfloat
ignore:
- goos: darwin
goarch: 386
# https://goreleaser.com/customization/archive/
archives: archives:
- id: default - id: default
builds: builds:
- default - default
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
wrap_in_directory: true wrap_in_directory: true
formats: tar.gz format: tar.gz
format_overrides: format_overrides:
- goos: windows - goos: windows
formats: zip format: zip
files: files:
- LICENSE - LICENSE
- README.md - README.md
- config/**/* - config/**/*
- systemd/* - systemd/*
# https://goreleaser.com/customization/snapshots/
snapshot: snapshot:
version_template: '{{ incpatch .Version }}-dev-{{.ShortCommit}}' name_template: "dev@{{.ShortCommit}}"
# https://goreleaser.com/customization/checksum/
checksum: checksum:
name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt" name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
# https://goreleaser.com/customization/release/
release: release:
prerelease: true prerelease: true
draft: true draft: true
nfpms:
- id: glider
package_name: glider
vendor: nadoo
homepage: https://github.com/nadoo/glider
maintainer: nadoo
description: Glider is a forward proxy with multiple protocols support, and also a dns/dhcp server with ipset management features(like dnsmasq).
license: GPL-3.0 License
formats:
# - apk
- deb
# - rpm
dependencies:
- libsystemd0
bindir: /usr/bin
release: 1
epoch: 1
version_metadata: git
section: default
priority: extra
contents:
- src: systemd/glider@.service
dst: /etc/systemd/system/glider@.service
- src: config/glider.conf.example
dst: /etc/glider/glider.conf.example
scripts:
postinstall: "systemd/postinstall.sh"
preremove: "systemd/preremove.sh"
postremove: "systemd/postremove.sh"
deb:
triggers:
interest_noawait:
- /lib/systemd/systemd

View File

@ -1,14 +1,11 @@
# Build Stage # build stage
FROM golang:1.24-alpine AS build-env FROM golang:alpine AS build-env
RUN apk --no-cache add build-base git gcc
ADD . /src ADD . /src
RUN apk --no-cache add git \ RUN cd /src && go build -v -i -ldflags "-s -w"
&& cd /src && go build -v -ldflags "-s -w"
# Final Stage # final stage
FROM alpine FROM alpine
COPY --from=build-env /src/glider /app/
WORKDIR /app WORKDIR /app
RUN apk -U upgrade --no-cache \ COPY --from=build-env /src/glider /app/
&& apk --no-cache add ca-certificates
USER 1000
ENTRYPOINT ["./glider"] ENTRYPOINT ["./glider"]

400
README.md
View File

@ -1,10 +1,8 @@
# [glider](https://github.com/nadoo/glider) # [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) [![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) [![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/actions/workflow/status/nadoo/glider/build.yml?branch=dev&style=flat-square)](https://github.com/nadoo/glider/actions) [![Actions Status](https://img.shields.io/github/workflow/status/nadoo/glider/Build?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(like dnsmasq).
@ -39,7 +37,7 @@ we can set up local listeners as proxy servers, and forward requests to internet
- Periodical availability checking for forwarders - Periodical availability checking for forwarders
- Send requests from specific local ip/interface - Send requests from specific local ip/interface
- Services: - Services:
- dhcpd: a simple dhcp server that can run in failover mode - dhcpd: a simple dhcp server that can detect existing dhcp server and avoid conflicts
## Protocols ## Protocols
@ -59,68 +57,54 @@ we can set up local listeners as proxy servers, and forward requests to internet
|SSR | | |√| |client only |SSR | | |√| |client only
|SSH | | |√| |client only |SSH | | |√| |client only
|SOCKS4 | | |√| |client only |SOCKS4 | | |√| |client only
|SOCKS4A | | |√| |client only
|TCP |√| |√| |tcp tunnel client & server |TCP |√| |√| |tcp tunnel client & server
|UDP | |√| |√|udp tunnel client & server |UDP | |√| |√|udp tunnel client & server
|TLS |√| |√| |transport client & server |TLS |√| |√| |transport client & server
|KCP | |√|√| |transport client & server |KCP | |√|√| |transport client & server
|Unix |√|√|√|√|transport client & server |Unix |√|√|√|√|transport client & server
|VSOCK |√| |√| |transport client & server |Websocket |√| |√| |transport client & server
|Smux |√| |√| |transport client & server
|Websocket(WS) |√| |√| |transport client & server
|WS Secure |√| |√| |websocket secure (wss)
|Proxy Protocol |√| | | |version 1 server only
|Simple-Obfs | | |√| |transport client only |Simple-Obfs | | |√| |transport client only
|Redir |√| | | |linux redirect proxy |Redir |√| | | |linux only
|Redir6 |√| | | |linux redirect proxy(ipv6) |Redir6 |√| | | |linux only(ipv6)
|TProxy | |√| | |linux tproxy(udp only)
|Reject | | |√|√|reject all requests |Reject | | |√|√|reject all requests
</details> </details>
## Install ## Install
- Binary: [https://github.com/nadoo/glider/releases](https://github.com/nadoo/glider/releases) Download:
- Docker: `docker pull nadoo/glider` - [https://github.com/nadoo/glider/releases](https://github.com/nadoo/glider/releases)
- Manjaro: `pamac install glider`
- ArchLinux: `sudo pacman -S glider` Docker:
- Homebrew: `brew install glider` ```bash
- MacPorts: `sudo port install glider` docker pull nadoo/glider
- Source: `go install github.com/nadoo/glider@latest` #docker pull ghcr.io/nadoo/glider
```
ArchLinux:
```bash
sudo pacman -S glider
```
## Usage ## Usage
#### Run
```bash ```bash
glider -verbose -listen :8443 glider -h
# docker run --rm -it nadoo/glider -verbose -listen :8443
``` ```
#### Help
<details> <details>
<summary><code>glider -help</code></summary> <summary>click to see details</summary>
```bash ```bash
Usage: glider [-listen URL]... [-forward URL]... [OPTION]... glider 0.13.0 usage:
e.g. glider -config /etc/glider/glider.conf
glider -listen :8443 -forward socks5://serverA:1080 -forward socks5://serverB:1080 -verbose
OPTION:
-check string -check string
check=tcp[://HOST:PORT]: tcp port connect check check=tcp[://HOST:PORT]: tcp port connect check
check=http://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE] check=http://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]
check=https://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE] check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR
check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, env vars: FORWARDER_ADDR,FORWARDER_URL
check=disable: disable health check (default "http://www.msftconnecttest.com/connecttest.txt#expect=200") check=disable: disable health check (default "http://www.msftconnecttest.com/connecttest.txt#expect=200")
-checkdisabledonly -checkdisabledonly
check disabled fowarders only check disabled fowarders only
-checkinterval int -checkinterval int
fowarder check interval(seconds) (default 30) fowarder check interval(seconds) (default 30)
-checklatencysamples int
use the average latency of the latest N checks (default 10)
-checktimeout int -checktimeout int
fowarder check timeout(seconds) (default 10) fowarder check timeout(seconds) (default 10)
-checktolerance int -checktolerance int
@ -133,34 +117,26 @@ OPTION:
local dns server listen address local dns server listen address
-dnsalwaystcp -dnsalwaystcp
always use tcp to query upstream dns servers no matter there is a forwarder or not always use tcp to query upstream dns servers no matter there is a forwarder or not
-dnscachelog
show query log of dns cache
-dnscachesize int -dnscachesize int
max number of dns response in CACHE (default 4096) size of CACHE (default 4096)
-dnsmaxttl int -dnsmaxttl int
maximum TTL value for entries in the CACHE(seconds) (default 1800) maximum TTL value for entries in the CACHE(seconds) (default 1800)
-dnsminttl int -dnsminttl int
minimum TTL value for entries in the CACHE(seconds) minimum TTL value for entries in the CACHE(seconds)
-dnsnoaaaa
disable AAAA query
-dnsrecord value -dnsrecord value
custom dns record, format: domain/ip custom dns record, format: domain/ip
-dnsserver value -dnsserver value
remote dns server address remote dns server address
-dnstimeout int -dnstimeout int
timeout value used in multiple dnsservers switch(seconds) (default 3) timeout value used in multiple dnsservers switch(seconds) (default 3)
-example
show usage examples
-forward value -forward value
forward url, see the URL section below forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]
-include value -include value
include file include file
-interface string -interface string
source ip or source interface source ip or source interface
-listen value -listen value
listen url, see the URL section below listen url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS
-logflags int
do not change it if you do not know what it is, ref: https://pkg.go.dev/log#pkg-constants (default 19)
-maxfailures int -maxfailures int
max failures to change forwarder status to disabled (default 3) max failures to change forwarder status to disabled (default 3)
-relaytimeout int -relaytimeout int
@ -169,122 +145,37 @@ OPTION:
rule file path rule file path
-rules-dir string -rules-dir string
rule file folder rule file folder
-scheme string
show help message of proxy scheme, use 'all' to see all schemes
-service value -service value
run specified services, format: SERVICE_NAME[,SERVICE_CONFIG] run specified services, format: SERVICE_NAME[,SERVICE_CONFIG]
-strategy string -strategy string
rr: Round Robin mode forward strategy, default: rr (default "rr")
ha: High Availability mode
lha: Latency based High Availability mode
dh: Destination Hashing mode (default "rr")
-tcpbufsize int
tcp buffer size in Bytes (default 32768)
-udpbufsize int
udp buffer size in Bytes (default 2048)
-verbose -verbose
verbose mode verbose mode
URL:
proxy: SCHEME://[USER:PASS@][HOST]:PORT
chain: proxy,proxy[,proxy]...
e.g. -listen socks5://:1080
-listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// (protocol chain)
e.g. -forward socks5://server:1080
-forward tls://server.com:443,http:// (protocol chain)
-forward socks5://serverA:1080,socks5://serverB:1080 (proxy chain)
SCHEME:
listen : http kcp mixed pxyproto redir redir6 smux sni socks5 ss tcp tls tproxy trojan trojanc udp unix vless vsock ws wss
forward: direct http kcp reject simple-obfs smux socks4 socks4a socks5 ss ssh ssr tcp tls trojan trojanc udp unix vless vmess vsock ws wss
Note: use 'glider -scheme all' or 'glider -scheme SCHEME' to see help info for the scheme.
--
Forwarder Options: FORWARD_URL#OPTIONS
priority : the priority of that forwarder, the larger the higher, default: 0
interface: the local interface or ip address used to connect remote server.
e.g. -forward socks5://server:1080#priority=100
-forward socks5://server:1080#interface=eth0
-forward socks5://server:1080#priority=100&interface=192.168.1.99
Services:
dhcpd: service=dhcpd,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
service=dhcpd-failover,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
e.g. service=dhcpd,eth1,192.168.1.100,192.168.1.199,720
--
Help:
glider -help
glider -scheme all
glider -example
see README.md and glider.conf.example for more details.
--
glider 0.16.4, https://github.com/nadoo/glider (glider.proxy@gmail.com)
``` ```
</details> </details>
run:
```bash
glider -config CONFIGPATH
```
```bash
glider -verbose -listen :8443 -forward SCHEME://HOST:PORT
```
#### Schemes #### Schemes
<details> <details>
<summary><code>glider -scheme all</code></summary> <summary>click to see details</summary>
```bash ```bash
Direct scheme: Available schemes:
direct:// listen: mixed ss socks5 http vless trojan trojanc redir redir6 tcp udp tls ws unix kcp
forward: reject ss socks4 socks5 http ssr ssh vless vmess trojan trojanc tcp udp tls ws unix kcp simple-obfs
Only needed when you want to specify the outgoing interface:
glider -verbose -listen :8443 -forward direct://#interface=eth0
Or load balance multiple interfaces directly:
glider -verbose -listen :8443 -forward direct://#interface=eth0 -forward direct://#interface=eth1 -strategy rr
Or you can use the high availability mode:
glider -verbose -listen :8443 -forward direct://#interface=eth0&priority=100 -forward direct://#interface=eth1&priority=200 -strategy ha
--
Http scheme:
http://[user:pass@]host:port
--
KCP scheme:
kcp://CRYPT:KEY@host:port[?dataShards=NUM&parityShards=NUM&mode=MODE]
Available crypt types for KCP:
none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20
Available modes for KCP:
fast, fast2, fast3, normal, default: fast
--
Simple-Obfs scheme:
simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA]
Available types for simple-obfs:
http, tls
--
Reject scheme:
reject://
--
Smux scheme:
smux://host:port
--
Socks4 scheme:
socks4://host:port
--
Socks5 scheme: Socks5 scheme:
socks5://[user:pass@]host:port socks://[user:pass@]host:port
--
SS scheme: SS scheme:
ss://method:pass@host:port ss://method:pass@host:port
@ -297,18 +188,31 @@ SS scheme:
chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305 chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305
Plain: NONE Plain: NONE
--
SSH scheme:
ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS]
timeout: timeout of ssh handshake and channel operation, default: 5
--
SSR scheme: SSR scheme:
ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz
-- SSH scheme:
ssh://user[:pass]@host:port[?key=keypath]
VMess scheme:
vmess://[security:]uuid@host:port?alterID=num
VLESS scheme:
vless://uuid@host:port[?fallback=127.0.0.1:80]
Trojan client scheme:
trojan://pass@host:port[?serverName=SERVERNAME][&skipVerify=true]
trojanc://pass@host:port (cleartext, without TLS)
Trojan server scheme:
trojan://pass@host:port?cert=PATH&key=PATH[&fallback=127.0.0.1]
trojanc://pass@host:port[?fallback=127.0.0.1] (cleartext, without TLS)
Available securities for vmess:
none, aes-128-gcm, chacha20-poly1305
TLS client scheme: TLS client scheme:
tls://host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&alpn=proto1][&alpn=proto2] tls://host:port[?serverName=SERVERNAME][&skipVerify=true]
Proxy over tls client: Proxy over tls client:
tls://host:port[?skipVerify=true][&serverName=SERVERNAME],scheme:// tls://host:port[?skipVerify=true][&serverName=SERVERNAME],scheme://
@ -317,7 +221,7 @@ Proxy over tls client:
tls://host:port[?skipVerify=true],vmess://[security:]uuid@?alterID=num tls://host:port[?skipVerify=true],vmess://[security:]uuid@?alterID=num
TLS server scheme: TLS server scheme:
tls://host:port?cert=PATH&key=PATH[&alpn=proto1][&alpn=proto2] tls://host:port?cert=PATH&key=PATH
Proxy over tls server: Proxy over tls server:
tls://host:port?cert=PATH&key=PATH,scheme:// tls://host:port?cert=PATH&key=PATH,scheme://
@ -325,44 +229,17 @@ Proxy over tls server:
tls://host:port?cert=PATH&key=PATH,socks5:// tls://host:port?cert=PATH&key=PATH,socks5://
tls://host:port?cert=PATH&key=PATH,ss://method:pass@ tls://host:port?cert=PATH&key=PATH,ss://method:pass@
--
Trojan client scheme:
trojan://pass@host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH]
trojanc://pass@host:port (cleartext, without TLS)
Trojan server scheme:
trojan://pass@host:port?cert=PATH&key=PATH[&fallback=127.0.0.1]
trojanc://pass@host:port[?fallback=127.0.0.1] (cleartext, without TLS)
--
Unix domain socket scheme:
unix://path
--
VLESS scheme:
vless://uuid@host:port[?fallback=127.0.0.1:80]
--
VMess scheme:
vmess://[security:]uuid@host:port[?alterID=num]
if alterID=0 or not set, VMessAEAD will be enabled
Available security for vmess:
zero, none, aes-128-gcm, chacha20-poly1305
--
Websocket client scheme: Websocket client scheme:
ws://host:port[/path][?host=HOST][&origin=ORIGIN] ws://host:port[/path][?host=HOST][&origin=ORIGIN]
wss://host:port[/path][?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&host=HOST][&origin=ORIGIN]
Websocket server scheme: Websocket server scheme:
ws://:port[/path][?host=HOST] ws://:port[/path][?host=HOST]
wss://:port[/path]?cert=PATH&key=PATH[?host=HOST]
Websocket with a specified proxy protocol: Websocket with a specified proxy protocol:
ws://host:port[/path][?host=HOST],scheme:// ws://host:port[/path][?host=HOST],scheme://
ws://host:port[/path][?host=HOST],http://[user:pass@] ws://host:port[/path][?host=HOST],http://[user:pass@]
ws://host:port[/path][?host=HOST],socks5://[user:pass@] ws://host:port[/path][?host=HOST],socks5://[user:pass@]
ws://host:port[/path][?host=HOST],vmess://[security:]uuid@?alterID=num
TLS and Websocket with a specified proxy protocol: TLS and Websocket with a specified proxy protocol:
tls://host:port[?skipVerify=true][&serverName=SERVERNAME],ws://[@/path[?host=HOST]],scheme:// tls://host:port[?skipVerify=true][&serverName=SERVERNAME],ws://[@/path[?host=HOST]],scheme://
@ -370,11 +247,56 @@ TLS and Websocket with a specified proxy protocol:
tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],socks5://[user:pass@] tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],socks5://[user:pass@]
tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],vmess://[security:]uuid@?alterID=num tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],vmess://[security:]uuid@?alterID=num
-- Unix domain socket scheme:
VM socket scheme(linux only): unix://path
vsock://[CID]:port
if you want to listen on any address, just set CID to 4294967295. KCP scheme:
kcp://CRYPT:KEY@host:port[?dataShards=NUM&parityShards=NUM&mode=MODE]
Available crypt types for KCP:
none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20
Available modes for KCP:
fast, fast2, fast3, normal, default: fast
Simple-Obfs scheme:
simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA]
Available types for simple-obfs:
http, tls
DNS forwarding server:
dns=:53
dnsserver=8.8.8.8:53
dnsserver=1.1.1.1:53
dnsrecord=www.example.com/1.2.3.4
dnsrecord=www.example.com/2606:2800:220:1:248:1893:25c8:1946
Available forward strategies:
rr: Round Robin mode
ha: High Availability mode
lha: Latency based High Availability mode
dh: Destination Hashing mode
Forwarder option scheme: FORWARD_URL#OPTIONS
priority: set the priority of that forwarder, default:0
interface: set local interface or ip address used to connect remote server
-
Examples:
socks5://1.1.1.1:1080#priority=100
vmess://[security:]uuid@host:port?alterID=num#priority=200
vmess://[security:]uuid@host:port?alterID=num#priority=200&interface=192.168.1.99
vmess://[security:]uuid@host:port?alterID=num#priority=200&interface=eth0
Services:
dhcpd: service=dhcpd,INTERFACE,START_IP,END_IP
e.g.,service=dhcpd,eth1,192.168.50.100,192.168.50.199
Config file format(see `./glider.conf.example` as an example):
# COMMENT LINE
KEY=VALUE
KEY=VALUE
# KEY equals to command line flag name: listen forward strategy...
``` ```
</details> </details>
@ -382,50 +304,47 @@ VM socket scheme(linux only):
#### Examples #### Examples
<details> <details>
<summary><code>glider -example</code></summary> <summary>click to see details</summary>
```bash ```bash
Examples: ./glider -config glider.conf
glider -config glider.conf
-run glider with specified config file. -run glider with specified config file.
glider -listen :8443 -verbose ./glider -listen :8443 -verbose
-listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode. -listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode.
glider -listen socks5://:1080 -listen http://:8080 -verbose ./glider -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443 -verbose
-multiple listeners: listen on :1080 as socks5 proxy server, and on :8080 as http proxy server. -listen on 0.0.0.0:8443 as a ss server.
glider -listen :8443 -forward direct://#interface=eth0 -forward direct://#interface=eth1 ./glider -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose
-multiple forwarders: listen on 8443 and forward requests via interface eth0 and eth1 in round robin mode. -listen on :443 as a https(http over tls) proxy server.
glider -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose ./glider -listen http://:8080 -forward socks5://127.0.0.1:1080
-protocol chain: listen on :443 as a https(http over tls) proxy server. -listen on :8080 as a http proxy server, forward all requests via socks5 server.
glider -listen http://:8080 -forward socks5://serverA:1080,socks5://serverB:1080 ./glider -listen socks5://:1080 -forward "tls://abc.com:443,vmess://security:uuid@?alterID=10"
-proxy chain: listen on :8080 as a http proxy server, forward all requests via forward chain. -listen on :1080 as a socks5 server, forward all requests via remote tls+vmess server.
glider -listen :8443 -forward socks5://serverA:1080 -forward socks5://serverB:1080#priority=10 -forward socks5://serverC:1080#priority=10 ./glider -listen socks5://:1080 -forward ss://method:pass@server1:port1 -forward ss://method:pass@server2:port2 -strategy rr
-forwarder priority: serverA will only be used when serverB and serverC are not available. -listen on :1080 as socks5 server, forward requests via server1 and server2 in round robin mode.
glider -listen tcp://:80 -forward tcp://serverA:80 ./glider -listen tcp://:80 -forward tcp://2.2.2.2:80
-tcp tunnel: listen on :80 and forward all requests to serverA:80. -tcp tunnel: listen on :80 and forward all requests to 2.2.2.2:80.
glider -listen udp://:53 -forward socks5://serverA:1080,udp://8.8.8.8:53 ./glider -listen udp://:53 -forward ss://method:pass@1.1.1.1:8443,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. -listen on :53 and forward all udp requests to 8.8.8.8:53 via remote ss server.
glider -verbose -dns=:53 -dnsserver=8.8.8.8:53 -forward socks5://serverA:1080 -dnsrecord=abc.com/1.2.3.4 ./glider -listen socks5://:1080 -listen http://:8080 -forward ss://method:pass@1.1.1.1:8443
-dns over proxy: listen on :53 as dns server, forward to 8.8.8.8:53 via socks5 server. -listen on :1080 as socks5 server, :8080 as http proxy server, forward all requests via remote ss server.
./glider -verbose -listen -dns=:53 -dnsserver=8.8.8.8:53 -forward ss://method:pass@server:port -dnsrecord=www.example.com/1.2.3.4
-listen on :53 as dns server, forward to 8.8.8.8:53 via ss server.
``` ```
</details> </details>
## Config ## Config
```bash
glider -config CONFIG_PATH
```
- [ConfigFile](config) - [ConfigFile](config)
- [glider.conf.example](config/glider.conf.example) - [glider.conf.example](config/glider.conf.example)
- [office.rule.example](config/rules.d/office.rule.example) - [office.rule.example](config/rules.d/office.rule.example)
@ -435,40 +354,13 @@ glider -config CONFIG_PATH
## Service ## Service
- dhcpd / dhcpd-failover: - dhcpd:
- service=dhcpd,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...] - service=dhcpd,INTERFACE,START_IP,END_IP
- service=dhcpd,eth1,192.168.1.100,192.168.1.199,720,fc:23:34:9e:25:01=192.168.1.101 - e.g., service=dhcpd,eth1,192.168.50.100,192.168.50.199
- service=dhcpd-failover,eth2,192.168.2.100,192.168.2.199,720
- note: `dhcpd-failover` only serves requests when there's no other dhcp server exists in lan
- detect interval: 1min
## Linux Daemon ## Linux Service
- systemd: [https://github.com/nadoo/glider/tree/main/systemd](https://github.com/nadoo/glider/tree/main/systemd)
- <details> <summary>docker: click to see details</summary>
- run glider (config file path: /etc/glider/glider.conf)
```
docker run -d --name glider --net host --restart=always \
-v /etc/glider:/etc/glider \
-v /etc/localtime:/etc/localtime:ro \
nadoo/glider -config=/etc/glider/glider.conf
```
- run watchtower if you need auto update
```
docker run -d --name watchtower --restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower --interval 21600 --cleanup \
glider
```
- open udp ports if you need udp nat fullcone
```
iptables -I INPUT -p udp -m udp --dport 1024:65535 -j ACCEPT
```
</details>
- systemd: [https://github.com/nadoo/glider/blob/master/systemd/](https://github.com/nadoo/glider/blob/master/systemd/)
## Customize Build ## Customize Build
@ -477,7 +369,7 @@ glider -config CONFIG_PATH
1. Clone the source code: 1. Clone the source code:
```bash ```bash
git clone https://github.com/nadoo/glider && cd glider git clone https://github.com/nadoo/glider
``` ```
2. Customize features: 2. Customize features:
@ -486,9 +378,9 @@ glider -config CONFIG_PATH
// _ "github.com/nadoo/glider/proxy/kcp" // _ "github.com/nadoo/glider/proxy/kcp"
``` ```
3. Build it: 3. Build it(requires **Go 1.15+** )
```bash ```bash
go build -v -ldflags "-s -w" cd glider && go build -v -i -ldflags "-s -w"
``` ```
</details> </details>
@ -526,17 +418,11 @@ glider -config CONFIG_PATH
listen=tls://:443?cert=crtFilePath&key=keyFilePath,http:// listen=tls://:443?cert=crtFilePath&key=keyFilePath,http://
``` ```
- Chain protocols in listener: http over smux over websocket proxy server
``` bash
listen=ws://:10000,smux://,http://
```
</details> </details>
## Links ## Links
- [ipset](https://github.com/nadoo/ipset): netlink ipset package for Go. - [ipset](https://github.com/nadoo/ipset): netlink ipset package for Go.
- [conflag](https://github.com/nadoo/conflag): a drop-in replacement for Go's standard flag package with config file support. - [conflag](https://github.com/nadoo/conflag): a drop-in replacement for Go's standard flag package with config file support.
- [ArchLinux](https://archlinux.org/packages/extra/x86_64/glider): a great linux distribution with glider pre-built package. - [ArchLinux](https://www.archlinux.org/packages/community/x86_64/glider): a great linux distribution with glider pre-built package.
- [urlencode](https://www.w3schools.com/tags/ref_urlencode.asp): you should encode special characters in scheme url. e.g., `@`->`%40` - [urlencode](https://www.w3schools.com/tags/ref_urlencode.asp): you should encode special characters in scheme url. e.g., `@`->`%40`

354
config.go
View File

@ -8,8 +8,7 @@ import (
"github.com/nadoo/conflag" "github.com/nadoo/conflag"
"github.com/nadoo/glider/dns" "github.com/nadoo/glider/dns"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/rule" "github.com/nadoo/glider/rule"
) )
@ -18,9 +17,6 @@ var flag = conflag.New()
// Config is global config struct. // Config is global config struct.
type Config struct { type Config struct {
Verbose bool Verbose bool
LogFlags int
TCPBufSize int
UDPBufSize int
Listens []string Listens []string
@ -43,30 +39,15 @@ func parseConfig() *Config {
flag.SetOutput(os.Stdout) flag.SetOutput(os.Stdout)
scheme := flag.String("scheme", "", "show help message of proxy scheme, use 'all' to see all schemes")
example := flag.Bool("example", false, "show usage examples")
flag.BoolVar(&conf.Verbose, "verbose", false, "verbose mode") flag.BoolVar(&conf.Verbose, "verbose", false, "verbose mode")
flag.IntVar(&conf.LogFlags, "logflags", 19, "do not change it if you do not know what it is, ref: https://pkg.go.dev/log#pkg-constants") flag.StringSliceUniqVar(&conf.Listens, "listen", nil, "listen url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS")
flag.IntVar(&conf.TCPBufSize, "tcpbufsize", 32768, "tcp buffer size in Bytes")
flag.IntVar(&conf.UDPBufSize, "udpbufsize", 2048, "udp buffer size in Bytes")
flag.StringSliceUniqVar(&conf.Listens, "listen", nil, "listen url, see the URL section below")
flag.StringSliceVar(&conf.Forwards, "forward", nil, "forward url, see the URL section below") flag.StringSliceUniqVar(&conf.Forwards, "forward", nil, "forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]")
flag.StringVar(&conf.Strategy.Strategy, "strategy", "rr", `rr: Round Robin mode flag.StringVar(&conf.Strategy.Strategy, "strategy", "rr", "forward strategy, default: rr")
ha: High Availability mode flag.StringVar(&conf.Strategy.Check, "check", "http://www.msftconnecttest.com/connecttest.txt#expect=200", "check=tcp[://HOST:PORT]: tcp port connect check\ncheck=http://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]\ncheck=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR\ncheck=disable: disable health check")
lha: Latency based High Availability mode
dh: Destination Hashing mode`)
flag.StringVar(&conf.Strategy.Check, "check", "http://www.msftconnecttest.com/connecttest.txt#expect=200",
`check=tcp[://HOST:PORT]: tcp port connect check
check=http://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
check=https://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, env vars: FORWARDER_ADDR,FORWARDER_URL
check=disable: disable health check`)
flag.IntVar(&conf.Strategy.CheckInterval, "checkinterval", 30, "fowarder check interval(seconds)") flag.IntVar(&conf.Strategy.CheckInterval, "checkinterval", 30, "fowarder check interval(seconds)")
flag.IntVar(&conf.Strategy.CheckTimeout, "checktimeout", 10, "fowarder check timeout(seconds)") flag.IntVar(&conf.Strategy.CheckTimeout, "checktimeout", 10, "fowarder check timeout(seconds)")
flag.IntVar(&conf.Strategy.CheckTolerance, "checktolerance", 0, "fowarder check tolerance(ms), switch only when new_latency < old_latency - tolerance, only used in lha mode") flag.IntVar(&conf.Strategy.CheckTolerance, "checktolerance", 0, "fowarder check tolerance(ms), switch only when new_latency < old_latency - tolerance, only used in lha mode")
flag.IntVar(&conf.Strategy.CheckLatencySamples, "checklatencysamples", 10, "use the average latency of the latest N checks")
flag.BoolVar(&conf.Strategy.CheckDisabledOnly, "checkdisabledonly", false, "check disabled fowarders only") flag.BoolVar(&conf.Strategy.CheckDisabledOnly, "checkdisabledonly", false, "check disabled fowarders only")
flag.IntVar(&conf.Strategy.MaxFailures, "maxfailures", 3, "max failures to change forwarder status to disabled") flag.IntVar(&conf.Strategy.MaxFailures, "maxfailures", 3, "max failures to change forwarder status to disabled")
flag.IntVar(&conf.Strategy.DialTimeout, "dialtimeout", 3, "dial timeout(seconds)") flag.IntVar(&conf.Strategy.DialTimeout, "dialtimeout", 3, "dial timeout(seconds)")
@ -83,55 +64,31 @@ check=disable: disable health check`)
flag.IntVar(&conf.DNSConfig.Timeout, "dnstimeout", 3, "timeout value used in multiple dnsservers switch(seconds)") flag.IntVar(&conf.DNSConfig.Timeout, "dnstimeout", 3, "timeout value used in multiple dnsservers switch(seconds)")
flag.IntVar(&conf.DNSConfig.MaxTTL, "dnsmaxttl", 1800, "maximum TTL value for entries in the CACHE(seconds)") flag.IntVar(&conf.DNSConfig.MaxTTL, "dnsmaxttl", 1800, "maximum TTL value for entries in the CACHE(seconds)")
flag.IntVar(&conf.DNSConfig.MinTTL, "dnsminttl", 0, "minimum TTL value for entries in the CACHE(seconds)") flag.IntVar(&conf.DNSConfig.MinTTL, "dnsminttl", 0, "minimum TTL value for entries in the CACHE(seconds)")
flag.IntVar(&conf.DNSConfig.CacheSize, "dnscachesize", 4096, "max number of dns response in CACHE") flag.IntVar(&conf.DNSConfig.CacheSize, "dnscachesize", 4096, "size of CACHE")
flag.BoolVar(&conf.DNSConfig.CacheLog, "dnscachelog", false, "show query log of dns cache")
flag.BoolVar(&conf.DNSConfig.NoAAAA, "dnsnoaaaa", false, "disable AAAA query")
flag.StringSliceUniqVar(&conf.DNSConfig.Records, "dnsrecord", nil, "custom dns record, format: domain/ip") flag.StringSliceUniqVar(&conf.DNSConfig.Records, "dnsrecord", nil, "custom dns record, format: domain/ip")
// service configs // service configs
flag.StringSliceUniqVar(&conf.Services, "service", nil, "run specified services, format: SERVICE_NAME[,SERVICE_CONFIG]") flag.StringSliceUniqVar(&conf.Services, "service", nil, "run specified services, format: SERVICE_NAME[,SERVICE_CONFIG]")
flag.Usage = usage flag.Usage = usage
if err := flag.Parse(); err != nil { err := flag.Parse()
if err != nil {
// flag.Usage() // flag.Usage()
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err) fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
os.Exit(-1) os.Exit(-1)
} }
if *scheme != "" { // setup a log func
fmt.Fprint(flag.Output(), proxy.Usage(*scheme)) if conf.Verbose {
os.Exit(0) log.F = log.Debugf
} }
if *example {
fmt.Fprint(flag.Output(), examples)
os.Exit(0)
}
// setup logger
log.Set(conf.Verbose, conf.LogFlags)
if len(conf.Listens) == 0 && conf.DNS == "" && len(conf.Services) == 0 { if len(conf.Listens) == 0 && conf.DNS == "" && len(conf.Services) == 0 {
// flag.Usage() // flag.Usage()
fmt.Fprintf(os.Stderr, "ERROR: listen url must be specified.\n") fmt.Fprintf(os.Stderr, "ERROR: listen url must be specified.\n")
os.Exit(-1) os.Exit(-1)
} }
// tcpbufsize
if conf.TCPBufSize > 0 {
proxy.TCPBufSize = conf.TCPBufSize
}
// udpbufsize
if conf.UDPBufSize > 0 {
proxy.UDPBufSize = conf.UDPBufSize
}
loadRules(conf)
return conf
}
func loadRules(conf *Config) {
// rulefiles // rulefiles
for _, ruleFile := range conf.RuleFiles { for _, ruleFile := range conf.RuleFiles {
if !path.IsAbs(ruleFile) { if !path.IsAbs(ruleFile) {
@ -160,95 +117,210 @@ func loadRules(conf *Config) {
conf.rules = append(conf.rules, rule) conf.rules = append(conf.rules, rule)
} }
} }
return conf
} }
func usage() { func usage() {
fmt.Fprint(flag.Output(), usage1) app := os.Args[0]
w := flag.Output()
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "%s %s usage:\n", app, version)
flag.PrintDefaults() flag.PrintDefaults()
fmt.Fprintf(flag.Output(), usage2, proxy.ServerSchemes(), proxy.DialerSchemes(), version) fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Available schemes:\n")
fmt.Fprintf(w, " listen: mixed ss socks5 http vless trojan trojanc redir redir6 tcp udp tls ws unix kcp\n")
fmt.Fprintf(w, " forward: reject ss socks4 socks5 http ssr ssh vless vmess trojan trojanc tcp udp tls ws unix kcp simple-obfs\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Socks5 scheme:\n")
fmt.Fprintf(w, " socks://[user:pass@]host:port\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "SS scheme:\n")
fmt.Fprintf(w, " ss://method:pass@host:port\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Available methods for ss:\n")
fmt.Fprintf(w, " AEAD Ciphers:\n")
fmt.Fprintf(w, " AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AEAD_XCHACHA20_POLY1305\n")
fmt.Fprintf(w, " Stream Ciphers:\n")
fmt.Fprintf(w, " AES-128-CFB AES-128-CTR AES-192-CFB AES-192-CTR AES-256-CFB AES-256-CTR CHACHA20-IETF XCHACHA20 CHACHA20 RC4-MD5\n")
fmt.Fprintf(w, " Alias:\n")
fmt.Fprintf(w, " chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305\n")
fmt.Fprintf(w, " Plain: NONE\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "SSR scheme:\n")
fmt.Fprintf(w, " ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "SSH scheme:\n")
fmt.Fprintf(w, " ssh://user[:pass]@host:port[?key=keypath]\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "VMess scheme:\n")
fmt.Fprintf(w, " vmess://[security:]uuid@host:port?alterID=num\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "VLESS scheme:\n")
fmt.Fprintf(w, " vless://uuid@host:port[?fallback=127.0.0.1:80]\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Trojan client scheme:\n")
fmt.Fprintf(w, " trojan://pass@host:port[?serverName=SERVERNAME][&skipVerify=true]\n")
fmt.Fprintf(w, " trojanc://pass@host:port (cleartext, without TLS)\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Trojan server scheme:\n")
fmt.Fprintf(w, " trojan://pass@host:port?cert=PATH&key=PATH[&fallback=127.0.0.1]\n")
fmt.Fprintf(w, " trojanc://pass@host:port[?fallback=127.0.0.1] (cleartext, without TLS)\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Available securities for vmess:\n")
fmt.Fprintf(w, " none, aes-128-gcm, chacha20-poly1305\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "TLS client scheme:\n")
fmt.Fprintf(w, " tls://host:port[?serverName=SERVERNAME][&skipVerify=true]\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Proxy over tls client:\n")
fmt.Fprintf(w, " tls://host:port[?skipVerify=true][&serverName=SERVERNAME],scheme://\n")
fmt.Fprintf(w, " tls://host:port[?skipVerify=true],http://[user:pass@]\n")
fmt.Fprintf(w, " tls://host:port[?skipVerify=true],socks5://[user:pass@]\n")
fmt.Fprintf(w, " tls://host:port[?skipVerify=true],vmess://[security:]uuid@?alterID=num\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "TLS server scheme:\n")
fmt.Fprintf(w, " tls://host:port?cert=PATH&key=PATH\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Proxy over tls server:\n")
fmt.Fprintf(w, " tls://host:port?cert=PATH&key=PATH,scheme://\n")
fmt.Fprintf(w, " tls://host:port?cert=PATH&key=PATH,http://\n")
fmt.Fprintf(w, " tls://host:port?cert=PATH&key=PATH,socks5://\n")
fmt.Fprintf(w, " tls://host:port?cert=PATH&key=PATH,ss://method:pass@\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Websocket client scheme:\n")
fmt.Fprintf(w, " ws://host:port[/path][?host=HOST][&origin=ORIGIN]\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Websocket server scheme:\n")
fmt.Fprintf(w, " ws://:port[/path][?host=HOST]\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Websocket with a specified proxy protocol:\n")
fmt.Fprintf(w, " ws://host:port[/path][?host=HOST],scheme://\n")
fmt.Fprintf(w, " ws://host:port[/path][?host=HOST],http://[user:pass@]\n")
fmt.Fprintf(w, " ws://host:port[/path][?host=HOST],socks5://[user:pass@]\n")
fmt.Fprintf(w, " ws://host:port[/path][?host=HOST],vmess://[security:]uuid@?alterID=num\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "TLS and Websocket with a specified proxy protocol:\n")
fmt.Fprintf(w, " tls://host:port[?skipVerify=true][&serverName=SERVERNAME],ws://[@/path[?host=HOST]],scheme://\n")
fmt.Fprintf(w, " tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],http://[user:pass@]\n")
fmt.Fprintf(w, " tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],socks5://[user:pass@]\n")
fmt.Fprintf(w, " tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],vmess://[security:]uuid@?alterID=num\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Unix domain socket scheme:\n")
fmt.Fprintf(w, " unix://path\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "KCP scheme:\n")
fmt.Fprintf(w, " kcp://CRYPT:KEY@host:port[?dataShards=NUM&parityShards=NUM&mode=MODE]\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Available crypt types for KCP:\n")
fmt.Fprintf(w, " none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Available modes for KCP:\n")
fmt.Fprintf(w, " fast, fast2, fast3, normal, default: fast\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Simple-Obfs scheme:\n")
fmt.Fprintf(w, " simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA]\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Available types for simple-obfs:\n")
fmt.Fprintf(w, " http, tls\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "DNS forwarding server:\n")
fmt.Fprintf(w, " dns=:53\n")
fmt.Fprintf(w, " dnsserver=8.8.8.8:53\n")
fmt.Fprintf(w, " dnsserver=1.1.1.1:53\n")
fmt.Fprintf(w, " dnsrecord=www.example.com/1.2.3.4\n")
fmt.Fprintf(w, " dnsrecord=www.example.com/2606:2800:220:1:248:1893:25c8:1946\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Available forward strategies:\n")
fmt.Fprintf(w, " rr: Round Robin mode\n")
fmt.Fprintf(w, " ha: High Availability mode\n")
fmt.Fprintf(w, " lha: Latency based High Availability mode\n")
fmt.Fprintf(w, " dh: Destination Hashing mode\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Forwarder option scheme: FORWARD_URL#OPTIONS\n")
fmt.Fprintf(w, " priority: set the priority of that forwarder, default:0\n")
fmt.Fprintf(w, " interface: set local interface or ip address used to connect remote server\n")
fmt.Fprintf(w, " -\n")
fmt.Fprintf(w, " Examples:\n")
fmt.Fprintf(w, " socks5://1.1.1.1:1080#priority=100\n")
fmt.Fprintf(w, " vmess://[security:]uuid@host:port?alterID=num#priority=200\n")
fmt.Fprintf(w, " vmess://[security:]uuid@host:port?alterID=num#priority=200&interface=192.168.1.99\n")
fmt.Fprintf(w, " vmess://[security:]uuid@host:port?alterID=num#priority=200&interface=eth0\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Services:\n")
fmt.Fprintf(w, " dhcpd: service=dhcpd,INTERFACE,START_IP,END_IP\n")
fmt.Fprintf(w, " e.g.,service=dhcpd,eth1,192.168.50.100,192.168.50.199\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Config file format(see `"+app+".conf.example` as an example):\n")
fmt.Fprintf(w, " # COMMENT LINE\n")
fmt.Fprintf(w, " KEY=VALUE\n")
fmt.Fprintf(w, " KEY=VALUE\n")
fmt.Fprintf(w, " # KEY equals to command line flag name: listen forward strategy...\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Examples:\n")
fmt.Fprintf(w, " "+app+" -config glider.conf\n")
fmt.Fprintf(w, " -run glider with specified config file.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " "+app+" -listen :8443 -verbose\n")
fmt.Fprintf(w, " -listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " "+app+" -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443 -verbose\n")
fmt.Fprintf(w, " -listen on 0.0.0.0:8443 as a ss server.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " "+app+" -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose\n")
fmt.Fprintf(w, " -listen on :443 as a https(http over tls) proxy server.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " "+app+" -listen http://:8080 -forward socks5://127.0.0.1:1080\n")
fmt.Fprintf(w, " -listen on :8080 as a http proxy server, forward all requests via socks5 server.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " "+app+" -listen socks5://:1080 -forward \"tls://abc.com:443,vmess://security:uuid@?alterID=10\"\n")
fmt.Fprintf(w, " -listen on :1080 as a socks5 server, forward all requests via remote tls+vmess server.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " "+app+" -listen socks5://:1080 -forward ss://method:pass@server1:port1 -forward ss://method:pass@server2:port2 -strategy rr\n")
fmt.Fprintf(w, " -listen on :1080 as socks5 server, forward requests via server1 and server2 in round robin mode.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " "+app+" -listen tcp://:80 -forward tcp://2.2.2.2:80\n")
fmt.Fprintf(w, " -tcp tunnel: listen on :80 and forward all requests to 2.2.2.2:80.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " "+app+" -listen udp://:53 -forward ss://method:pass@1.1.1.1:8443,udp://8.8.8.8:53\n")
fmt.Fprintf(w, " -listen on :53 and forward all udp requests to 8.8.8.8:53 via remote ss server.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " "+app+" -listen socks5://:1080 -listen http://:8080 -forward ss://method:pass@1.1.1.1:8443\n")
fmt.Fprintf(w, " -listen on :1080 as socks5 server, :8080 as http proxy server, forward all requests via remote ss server.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " "+app+" -verbose -listen -dns=:53 -dnsserver=8.8.8.8:53 -forward ss://method:pass@server:port -dnsrecord=www.example.com/1.2.3.4\n")
fmt.Fprintf(w, " -listen on :53 as dns server, forward to 8.8.8.8:53 via ss server.\n")
fmt.Fprintf(w, "\n")
} }
var usage1 = `
Usage: glider [-listen URL]... [-forward URL]... [OPTION]...
e.g. glider -config /etc/glider/glider.conf
glider -listen :8443 -forward socks5://serverA:1080 -forward socks5://serverB:1080 -verbose
OPTION:
`
var usage2 = `
URL:
proxy: SCHEME://[USER:PASS@][HOST]:PORT
chain: proxy,proxy[,proxy]...
e.g. -listen socks5://:1080
-listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// (protocol chain)
e.g. -forward socks5://server:1080
-forward tls://server.com:443,http:// (protocol chain)
-forward socks5://serverA:1080,socks5://serverB:1080 (proxy chain)
SCHEME:
listen : %s
forward: %s
Note: use 'glider -scheme all' or 'glider -scheme SCHEME' to see help info for the scheme.
--
Forwarder Options: FORWARD_URL#OPTIONS
priority : the priority of that forwarder, the larger the higher, default: 0
interface: the local interface or ip address used to connect remote server.
e.g. -forward socks5://server:1080#priority=100
-forward socks5://server:1080#interface=eth0
-forward socks5://server:1080#priority=100&interface=192.168.1.99
Services:
dhcpd: service=dhcpd,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
service=dhcpd-failover,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
e.g. service=dhcpd,eth1,192.168.1.100,192.168.1.199,720
--
Help:
glider -help
glider -scheme all
glider -example
see README.md and glider.conf.example for more details.
--
glider %s, https://github.com/nadoo/glider (glider.proxy@gmail.com)
`
var examples = `
Examples:
glider -config glider.conf
-run glider with specified config file.
glider -listen :8443 -verbose
-listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode.
glider -listen socks5://:1080 -listen http://:8080 -verbose
-multiple listeners: listen on :1080 as socks5 proxy server, and on :8080 as http proxy server.
glider -listen :8443 -forward direct://#interface=eth0 -forward direct://#interface=eth1
-multiple forwarders: listen on 8443 and forward requests via interface eth0 and eth1 in round robin mode.
glider -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose
-protocol chain: listen on :443 as a https(http over tls) proxy server.
glider -listen http://:8080 -forward socks5://serverA:1080,socks5://serverB:1080
-proxy chain: listen on :8080 as a http proxy server, forward all requests via forward chain.
glider -listen :8443 -forward socks5://serverA:1080 -forward socks5://serverB:1080#priority=10 -forward socks5://serverC:1080#priority=10
-forwarder priority: serverA will only be used when serverB and serverC are not available.
glider -listen tcp://:80 -forward tcp://serverA:80
-tcp tunnel: listen on :80 and forward all requests to serverA:80.
glider -listen udp://:53 -forward socks5://serverA:1080,udp://8.8.8.8:53
-udp tunnel: listen on :53 and forward all udp requests to 8.8.8.8:53 via remote socks5 server.
glider -verbose -dns=:53 -dnsserver=8.8.8.8:53 -forward socks5://serverA:1080 -dnsrecord=abc.com/1.2.3.4
-dns over proxy: listen on :53 as dns server, forward to 8.8.8.8:53 via socks5 server.
`

View File

@ -31,7 +31,7 @@ forward=ss://method:pass@1.1.1.1:8443
# upstream forward proxy (forward chain) # upstream forward proxy (forward chain)
forward=http://1.1.1.1:8080,socks5://2.2.2.2:1080 forward=http://1.1.1.1:8080,socks5://2.2.2.2:1080
# multiple upstream proxies forward strategy # multiple upstream proxies forwad strategy
strategy=rr strategy=rr
# forwarder health check # forwarder health check
@ -56,8 +56,8 @@ rules-dir=rules.d
#include=more.inc.conf #include=more.inc.conf
``` ```
See: See:
- [glider.conf.example](glider.conf.example) - [glider.conf.example](config/glider.conf.example)
- [examples](examples) - [examples](config/examples)
## Rule File ## Rule File
Rule file, **same as the config file but specify forwarders based on destinations**: Rule file, **same as the config file but specify forwarders based on destinations**:

View File

@ -1,9 +1,9 @@
forward=http://forwarder1:8080 forward=http://forwarder4:8080
# first connect forwarder1 then forwarder2 then internet # first connect forwarder1 then forwarder2 then internet
forward=http://forwarder1:8080,socks5://forwarder2:1080 forward=http://forwarder5:8080,socks6://forwarder3:1080
# Round Robin mode: rr # Round Robin mode: rr

View File

@ -82,8 +82,7 @@ Set server's nameserver to glider:
echo nameserver 127.0.0.1 > /etc/resolv.conf echo nameserver 127.0.0.1 > /etc/resolv.conf
``` ```
#### Client settings #### Client DNS settings
Use the linux server's ip as your gateway.
Use the linux server's ip as your dns server. Use the linux server's ip as your dns server.
#### When client requesting to access http://example1.com (in office.rule), the whole process: #### When client requesting to access http://example1.com (in office.rule), the whole process:
@ -92,10 +91,10 @@ DNS Resolving:
2. upstream dns server choice: glider will lookup it's rule config and find out the dns server to use for this domain(matched "example1.com" in office.rule, so 208.67.222.222:53 will be chosen) 2. upstream dns server choice: glider will lookup it's rule config and find out the dns server to use for this domain(matched "example1.com" in office.rule, so 208.67.222.222:53 will be chosen)
3. glider uses the forwarder in office.rule to ask 208.67.222.222:53 for the resolve answers(dns over proxy). 3. glider uses the forwarder in office.rule to ask 208.67.222.222:53 for the resolve answers(dns over proxy).
4. glider updates it's office rule config, adds the resolved ip address to it. 4. glider updates it's office rule config, adds the resolved ip address to it.
5. glider adds the resolved ip into ipset "glider", and returns the dns answer to client. 5. glider adds the resolved ip into ipset "glider", and return the dns answer to client.
Destination Accessing: Destination Accessing:
1. client sends http request to the resolved ip of example1.com. 1. client sends http request to the resolved ip of example1.com.
2. linux gateway server will get the request. 2. linux gateway server will get the request.
3. iptables matches the ip in ipset "glider" and redirect this request to :1081(glider) 3. iptabes matches the ip in ipset "glider" and redirect this request to :1081(glider)
4. glider finds the ip in office rule, and then choose a forwarder in office.rule to complete the request. 4. glider finds the ip in office rule, and then choose a forwarder in office.rule to complete the request.

View File

@ -1,9 +1,9 @@
forward=http://forwarder1:8080 forward=http://forwarder4:8080
# first connect forwarder1 then forwarder2 then internet # first connect forwarder1 then forwarder2 then internet
forward=http://forwarder1:8080,socks5://forwarder2:1080 forward=http://forwarder5:8080,socks5://forwarder3:1080
# Round Robin mode: rr # Round Robin mode: rr

View File

@ -32,17 +32,16 @@ verbose=True
# different protocols. # different protocols.
# listen on 8443, serve as http/socks5 proxy on the same port. # listen on 8443, serve as http/socks5 proxy on the same port.
# listen=:8443 listen=:8443
listen=127.0.0.1:8443
# listen on 8448 as a ss server. # listen on 8448 as a ss server.
# listen=ss://AEAD_CHACHA20_POLY1305:pass@:8448 # listen=ss://AEAD_CHACHA20_POLY1305:pass@:8448
# listen on 8080 as a http proxy server. # listen on 8080 as a http proxy server.
# listen=http://:8080 listen=http://:8080
# listen on 1080 as a socks5 proxy server. # listen on 1080 as a socks5 proxy server.
# listen=socks5://:1080 listen=socks5://:1080
# listen on 1234 as vless proxy server. # listen on 1234 as vless proxy server.
# listen=vless://uuid@:1234 # listen=vless://uuid@:1234
@ -52,9 +51,6 @@ listen=127.0.0.1:8443
# listen on 1081 as a linux transparent proxy server. # listen on 1081 as a linux transparent proxy server.
# listen=redir://:1081 # listen=redir://:1081
# listen on 1082 as a linux transparent proxy server(tproxy).
# listen=tproxy://:1082
# http over tls (HTTPS proxy) # http over tls (HTTPS proxy)
# listen=tls://:443?cert=crtFilePath&key=keyFilePath,http:// # listen=tls://:443?cert=crtFilePath&key=keyFilePath,http://
@ -62,10 +58,7 @@ listen=127.0.0.1:8443
# listen=tls://:443?cert=crtFilePath&key=keyFilePath,ss://AEAD_CHACHA20_POLY1305:pass@ # listen=tls://:443?cert=crtFilePath&key=keyFilePath,ss://AEAD_CHACHA20_POLY1305:pass@
# socks5 over unix domain socket # socks5 over unix domain socket
# listen=unix:///dev/shm/socket,socks5:// # listen=unix:///tmp/glider.socket,socks5://
# socks5 over vm socket
# listen=vsock://:1234,socks5://
# socks5 over kcp # socks5 over kcp
# listen=kcp://aes:key@127.0.0.1:8444?dataShards=10&parityShards=3&mode=fast,socks5:// # listen=kcp://aes:key@127.0.0.1:8444?dataShards=10&parityShards=3&mode=fast,socks5://
@ -80,10 +73,10 @@ listen=127.0.0.1:8443
# listen=ws://:1234/path?host=domain.com,vless://707f20ea-d4b8-4d1d-8e2e-2c86cb2ed97a@?fallback=127.0.0.1:80 # listen=ws://:1234/path?host=domain.com,vless://707f20ea-d4b8-4d1d-8e2e-2c86cb2ed97a@?fallback=127.0.0.1:80
# trojan server # trojan server
# listen=trojan://PASSWORD@:1234?cert=/path/to/cert&key=/path/to/key&fallback=127.0.0.1 # listen=trojan://PASSWORD:1234?cert=/path/to/cert&key=/path/to/key&fallback=127.0.0.1
# trojanc server (trojan without tls) # trojanc server (trojan without tls)
# listen=trojanc://PASSWORD@:1234?fallback=127.0.0.1 # listen=trojanc://PASSWORD:1234?fallback=127.0.0.1
# FORWARDERS # FORWARDERS
# ---------- # ----------
@ -113,10 +106,9 @@ listen=127.0.0.1:8443
# forward=ssr://method:pass@1.1.1.1:8443?protocol=auth_aes128_md5&protocol_param=xxx&obfs=tls1.2_ticket_auth&obfs_param=yyy # forward=ssr://method:pass@1.1.1.1:8443?protocol=auth_aes128_md5&protocol_param=xxx&obfs=tls1.2_ticket_auth&obfs_param=yyy
# ssh forwarder # ssh forwarder
# forward=ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS] # forward=ssh://user[:pass]@host:port[?key=keypath]
# forward=ssh://root:pass@host:port # forward=ssh://root:pass@host:port
# forward=ssh://root@host:port?key=/path/to/keyfile # forward=ssh://root@host:port?key=/path/to/keyfile
# forward=ssh://root@host:port?key=/path/to/keyfile&timeout=5
# http proxy as forwarder # http proxy as forwarder
# forward=http://1.1.1.1:8080 # forward=http://1.1.1.1:8080
@ -130,21 +122,21 @@ listen=127.0.0.1:8443
# vless forwarder # vless forwarder
# forward=vless://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443 # forward=vless://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443
# vmess with aead auth # vmess with none security
# forward=vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443
# vmess with md5 auth (by setting alterID)
# forward=vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443?alterID=2 # forward=vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443?alterID=2
# vmess with aes-128-gcm security
# forward=vmess://aes-128-gcm:5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443?alterID=2
# vmess over tls # vmess over tls
# forward=tls://server.com:443,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98 # forward=tls://server.com:443,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
# vmess over websocket # vmess over websocket
# forward=ws://1.1.1.1:80/path?host=server.com,vmess://chacha20-poly1305:5a146038-0b56-4e95-b1dc-5c6f5a32cd98@ # forward=ws://1.1.1.1:80/path?host=server.com,vmess://chacha20-poly1305:5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
# vmess over ws over tls # vmess over ws over tls
# forward=tls://server.com:443,ws://,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98 # forward=tls://server.com:443,ws://,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
# forward=tls://server.com:443,ws://@/path,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98 # forward=tls://server.com:443,ws://@/path,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
# ss over tls # ss over tls
# forward=tls://server.com:443,ss://AEAD_CHACHA20_POLY1305:pass@ # forward=tls://server.com:443,ss://AEAD_CHACHA20_POLY1305:pass@
@ -156,7 +148,7 @@ listen=127.0.0.1:8443
# forward=simple-obfs://1.1.1.1:443?type=tls&host=apple.com,ss://AEAD_CHACHA20_POLY1305:pass@ # forward=simple-obfs://1.1.1.1:443?type=tls&host=apple.com,ss://AEAD_CHACHA20_POLY1305:pass@
# socks5 over unix domain socket # socks5 over unix domain socket
# forward=unix:///dev/shm/socket,socks5:// # forward=unix:///tmp/glider.socket,socks5://
# FORWARDER CHAIN # FORWARDER CHAIN
# --------------- # ---------------
@ -196,10 +188,8 @@ maxfailures=3
# Forwarder health check: # Forwarder health check:
# check=tcp[://HOST:PORT]: tcp port connect check # check=tcp[://HOST:PORT]: tcp port connect check
# check=http://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE] # check=http://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]
# check=https://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE] # check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR
# e.g. check=https://www.netflix.com/title/81215567#expect=301|404
# check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR,FORWARDER_URL
# check=disable: disable health check # check=disable: disable health check
check=http://www.msftconnecttest.com/connecttest.txt#expect=200 check=http://www.msftconnecttest.com/connecttest.txt#expect=200
@ -212,9 +202,6 @@ checktimeout=10
# switch forwarder only when new_latency < old_latency - tolerance, used in lha mode # switch forwarder only when new_latency < old_latency - tolerance, used in lha mode
checktolerance=100 checktolerance=100
# use the average latency of the latest N checks
checklatencysamples=10
# check disabled fowarders only # check disabled fowarders only
checkdisabledonly=false checkdisabledonly=false
@ -223,7 +210,7 @@ checkdisabledonly=false
# we can specify different upstream dns server in rule file for different destinations. # we can specify different upstream dns server in rule file for different destinations.
# Setup a dns forwarding server # Setup a dns forwarding server
# dns=:53 dns=:53
# global remote dns server (you can specify different dns server in rule file) # global remote dns server (you can specify different dns server in rule file)
dnsserver=8.8.8.8:53 dnsserver=8.8.8.8:53
@ -246,52 +233,35 @@ dnsminttl=0
# size of CACHE # size of CACHE
dnscachesize=4096 dnscachesize=4096
# show query log of dns cache
dnscachelog=True
# disable AAAA queries
# dnsnoaaaa=True
# custom records # custom records
dnsrecord=www.example.com/1.2.3.4 dnsrecord=www.example.com/1.2.3.4
dnsrecord=www.example.com/2606:2800:220:1:248:1893:25c8:1946 dnsrecord=www.example.com/2606:2800:220:1:248:1893:25c8:1946
# SERVICES # SERVICES
# service=dhcpd,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...] # service=dhcpd,INTERFACE,START_IP,END_IP
# service=dhcpd-failover,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
# e.g.: # e.g.:
# service=dhcpd,eth1,192.168.1.100,192.168.1.199,720 # service=dhcpd,eth1,192.168.50.100,192.168.50.199
# service=dhcpd,eth2,192.168.2.100,192.168.2.199,720,fc:23:34:9e:25:01=192.168.2.101,fc:23:34:9e:25:02=192.168.2.102
# INTERFACE SPECIFIC # INTERFACE SPECIFIC
# ------------------ # ------------------
# Specify global outbound ip/interface. # Specify the outbound ip/interface.
# #
# interface="" # interface=""
# interface="192.168.1.100" # interface="192.168.1.100"
# interface="eth0" # interface="eth0"
#
# Specify interface for a forwarder:
# forward=socks5://192.168.1.10:1080#priority=100&interface=eth0
# forward=socks5://192.168.1.10:1080#priority=100&interface=192.168.1.100
# RULE FILES # RULE FILES
# ---------- # ----------
# Specify additional forward rules. # Specify additional forward rules.
#
# specify rules folder, so all *.rule files under this folder will be parsed as rule file # specify rules folder, so all *.rule files under this folder will be parsed as rule file
# rules-dir=rules.d rules-dir=rules.d
#
# specify a rule file # specify a rule file
#rulefile=office.rule #rulefile=office.rule
#rulefile=home.rule #rulefile=home.rule
# INCLUDE CONFIG FILES
# ---------- # INCLUDE MORE CONFIG FILES
#include=dnsrecord.inc.conf #include=dnsrecord.inc.conf
#include=more.conf #include=more.conf
# ENVIRONMENT VARIABLES
# ----------
# use {$ENV_VAR_NAME} in VALUE to get the Environment Variable value.
# forward=socks5://{$USER_NAME}:{$USER_PASS}@:1080

View File

@ -26,7 +26,6 @@ dnsserver=208.67.222.222:53
# - add ip/cidrs in rule files on startup # - add ip/cidrs in rule files on startup
# - add resolved ips for domains in rule files by dns forwarding server # - add resolved ips for domains in rule files by dns forwarding server
# Usually used in transparent proxy mode on linux # Usually used in transparent proxy mode on linux
# Note: this will create 2 ipsets, glider for ipv4 and glider6 for ipv6
ipset=glider ipset=glider
# DESTINATIONS # DESTINATIONS

View File

@ -5,18 +5,17 @@ import (
"errors" "errors"
"io" "io"
"net" "net"
"net/netip"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/pool" "github.com/nadoo/glider/pool"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
) )
// AnswerHandler function handles the dns TypeA or TypeAAAA answer. // AnswerHandler function handles the dns TypeA or TypeAAAA answer.
type AnswerHandler func(domain string, ip netip.Addr) error type AnswerHandler func(domain, ip string) error
// Config for dns. // Config for dns.
type Config struct { type Config struct {
@ -27,8 +26,6 @@ type Config struct {
Records []string Records []string
AlwaysTCP bool AlwaysTCP bool
CacheSize int CacheSize int
CacheLog bool
NoAAAA bool
} }
// Client is a dns client struct. // Client is a dns client struct.
@ -53,9 +50,7 @@ func NewClient(proxy proxy.Proxy, config *Config) (*Client, error) {
// custom records // custom records
for _, record := range config.Records { for _, record := range config.Records {
if err := c.AddRecord(record); err != nil { c.AddRecord(record)
log.F("[dns] add record '%s' error: %s", record, err)
}
} }
return c, nil return c, nil
@ -69,21 +64,13 @@ func (c *Client) Exchange(reqBytes []byte, clientAddr string, preferTCP bool) ([
return nil, err return nil, err
} }
if c.config.NoAAAA && req.Question.QTYPE == QTypeAAAA {
respBytes := valCopy(reqBytes)
respBytes[2] |= uint8(ResponseMsg) << 7
return respBytes, nil
}
if req.Question.QTYPE == QTypeA || req.Question.QTYPE == QTypeAAAA { if req.Question.QTYPE == QTypeA || req.Question.QTYPE == QTypeAAAA {
if v, expired := c.cache.Get(qKey(req.Question)); len(v) > 2 { if v, expired := c.cache.Get(qKey(req.Question)); len(v) > 2 {
v = valCopy(v) v = valCopy(v)
binary.BigEndian.PutUint16(v[:2], req.ID) binary.BigEndian.PutUint16(v[:2], req.ID)
if c.config.CacheLog {
log.F("[dns] %s <-> cache, type: %d, %s", log.F("[dns] %s <-> cache, type: %d, %s",
clientAddr, req.Question.QTYPE, req.Question.QNAME) clientAddr, req.Question.QTYPE, req.Question.QNAME)
}
if expired { // update cache if expired { // update cache
go func(qname string, reqBytes []byte, preferTCP bool) { go func(qname string, reqBytes []byte, preferTCP bool) {
@ -119,19 +106,12 @@ func (c *Client) handleAnswer(respBytes []byte, clientAddr, dnsServer, network,
} }
ips, ttl := c.extractAnswer(resp) ips, ttl := c.extractAnswer(resp)
if ttl > c.config.MaxTTL { if len(ips) != 0 && ttl > 0 {
ttl = c.config.MaxTTL
} else if ttl < c.config.MinTTL {
ttl = c.config.MinTTL
}
if ttl <= 0 { // we got a null result
ttl = 1800
}
c.cache.Set(qKey(resp.Question), valCopy(respBytes), ttl) c.cache.Set(qKey(resp.Question), valCopy(respBytes), ttl)
log.F("[dns] %s <-> %s(%s) via %s, %s/%d: %s, ttl: %ds", }
clientAddr, dnsServer, network, dialerAddr, resp.Question.QNAME, resp.Question.QTYPE, strings.Join(ips, ","), ttl)
log.F("[dns] %s <-> %s(%s) via %s, type: %d, %s: %s",
clientAddr, dnsServer, network, dialerAddr, resp.Question.QTYPE, resp.Question.QNAME, strings.Join(ips, ","))
return nil return nil
} }
@ -141,11 +121,11 @@ func (c *Client) extractAnswer(resp *Message) ([]string, int) {
ttl := c.config.MinTTL ttl := c.config.MinTTL
for _, answer := range resp.Answers { for _, answer := range resp.Answers {
if answer.TYPE == QTypeA || answer.TYPE == QTypeAAAA { if answer.TYPE == QTypeA || answer.TYPE == QTypeAAAA {
if answer.IP.IsValid() && !answer.IP.IsUnspecified() {
for _, h := range c.handlers { for _, h := range c.handlers {
h(resp.Question.QNAME, answer.IP) h(resp.Question.QNAME, answer.IP)
} }
ips = append(ips, answer.IP.String()) if answer.IP != "" {
ips = append(ips, answer.IP)
} }
if answer.TTL != 0 { if answer.TTL != 0 {
ttl = int(answer.TTL) ttl = int(answer.TTL)
@ -153,6 +133,12 @@ func (c *Client) extractAnswer(resp *Message) ([]string, int) {
} }
} }
if ttl > c.config.MaxTTL {
ttl = c.config.MaxTTL
} else if ttl < c.config.MinTTL {
ttl = c.config.MinTTL
}
return ips, ttl return ips, ttl
} }
@ -162,13 +148,12 @@ func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) (
// use tcp to connect upstream server default // use tcp to connect upstream server default
network = "tcp" network = "tcp"
dialer := c.proxy.NextDialer(qname + ":0") dialer := c.proxy.NextDialer(qname + ":53")
// if we are resolving a domain which uses a forwarder `REJECT`, then use `DIRECT` instead // if we are resolving the dialer's domain, then use Direct to avoid denpency loop
// so we can resolve it correctly.
// TODO: dialer.Addr() == "REJECT", tricky // TODO: dialer.Addr() == "REJECT", tricky
if dialer.Addr() == "REJECT" { if strings.Contains(dialer.Addr(), qname) || dialer.Addr() == "REJECT" {
dialer = c.proxy.NextDialer("direct:0") dialer = proxy.Default
} }
// If client uses udp and no forwarders specified, use udp // If client uses udp and no forwarders specified, use udp
@ -179,7 +164,7 @@ func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) (
ups := c.UpStream(qname) ups := c.UpStream(qname)
server = ups.Server() server = ups.Server()
for range ups.Len() { for i := 0; i < ups.Len(); i++ {
var rc net.Conn var rc net.Conn
rc, err = dialer.Dial(network, server) rc, err = dialer.Dial(network, server)
if err != nil { if err != nil {
@ -286,13 +271,10 @@ func (c *Client) AddHandler(h AnswerHandler) {
// AddRecord adds custom record to dns cache, format: // AddRecord adds custom record to dns cache, format:
// www.example.com/1.2.3.4 or www.example.com/2606:2800:220:1:248:1893:25c8:1946 // www.example.com/1.2.3.4 or www.example.com/2606:2800:220:1:248:1893:25c8:1946
func (c *Client) AddRecord(record string) error { func (c *Client) AddRecord(record string) error {
domain, ip, found := strings.Cut(record, "/") r := strings.Split(record, "/")
if !found { domain, ip := r[0], r[1]
return errors.New("wrong record format, must contain '/'") m, err := c.MakeResponse(domain, ip)
}
m, err := MakeResponse(domain, ip, uint32(c.config.MaxTTL))
if err != nil { if err != nil {
log.F("[dns] add custom record error: %s", err)
return err return err
} }
@ -310,22 +292,27 @@ func (c *Client) AddRecord(record string) error {
} }
// MakeResponse makes a dns response message for the given domain and ip address. // MakeResponse makes a dns response message for the given domain and ip address.
// Note: you should make sure ttl > 0. func (c *Client) MakeResponse(domain string, ip string) (*Message, error) {
func MakeResponse(domain, ip string, ttl uint32) (*Message, error) { ipb := net.ParseIP(ip)
addr, err := netip.ParseAddr(ip) if ipb == nil {
if err != nil { return nil, errors.New("GenResponse: invalid ip format")
return nil, err
} }
var qtype, rdlen uint16 = QTypeA, net.IPv4len var rdata []byte
if addr.Is6() { var qtype, rdlen uint16
qtype, rdlen = QTypeAAAA, net.IPv6len if rdata = ipb.To4(); rdata != nil {
qtype = QTypeA
rdlen = net.IPv4len
} else {
qtype = QTypeAAAA
rdlen = net.IPv6len
rdata = ipb
} }
m := NewMessage(0, ResponseMsg) m := NewMessage(0, Response)
m.SetQuestion(NewQuestion(qtype, domain)) m.SetQuestion(NewQuestion(qtype, domain))
rr := &RR{NAME: domain, TYPE: qtype, CLASS: ClassINET, rr := &RR{NAME: domain, TYPE: qtype, CLASS: ClassINET,
TTL: ttl, RDLENGTH: rdlen, RDATA: addr.AsSlice()} TTL: uint32(c.config.MinTTL), RDLENGTH: rdlen, RDATA: rdata}
m.AddAnswer(rr) m.AddAnswer(rr)
return m, nil return m, nil

View File

@ -5,25 +5,25 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"io" "io"
"math/rand/v2" "math/rand"
"net/netip" "net"
"strings" "strings"
) )
// UDPMaxLen is the max size of udp dns request. // UDPMaxLen is the max size of udp dns request.
// https://www.dnsflagday.net/2020/ // https://tools.ietf.org/html/rfc1035#section-4.2.1
const UDPMaxLen = 1232 // 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
// HeaderLen is the length of dns msg header. // HeaderLen is the length of dns msg header.
const HeaderLen = 12 const HeaderLen = 12
// MsgType is the dns Message type.
type MsgType byte
// Message types. // Message types.
const ( const (
QueryMsg MsgType = 0 Query = 0
ResponseMsg MsgType = 1 Response = 1
) )
// Query types. // Query types.
@ -36,7 +36,7 @@ const (
const ClassINET uint16 = 1 const ClassINET uint16 = 1
// Message format: // Message format:
// https://www.rfc-editor.org/rfc/rfc1035#section-4.1 // https://tools.ietf.org/html/rfc1035#section-4.1
// All communications inside of the domain protocol are carried in a single // All communications inside of the domain protocol are carried in a single
// format called a message. The top level format of message is divided // format called a message. The top level format of message is divided
// into 5 sections (some of which are empty in certain cases) shown below: // into 5 sections (some of which are empty in certain cases) shown below:
@ -64,7 +64,7 @@ type Message struct {
} }
// NewMessage returns a new message. // NewMessage returns a new message.
func NewMessage(id uint16, msgType MsgType) *Message { func NewMessage(id uint16, msgType int) *Message {
if id == 0 { if id == 0 {
id = uint16(rand.Uint32()) id = uint16(rand.Uint32())
} }
@ -91,7 +91,8 @@ func (m *Message) AddAnswer(rr *RR) error {
// Marshal marshals message struct to []byte. // Marshal marshals message struct to []byte.
func (m *Message) Marshal() ([]byte, error) { func (m *Message) Marshal() ([]byte, error) {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
if _, err := m.MarshalTo(buf); err != nil { _, err := m.MarshalTo(buf)
if err != nil {
return nil, err return nil, err
} }
return buf.Bytes(), nil return buf.Bytes(), nil
@ -133,7 +134,8 @@ func UnmarshalMessage(b []byte) (*Message, error) {
} }
m := &Message{unMarshaled: b} m := &Message{unMarshaled: b}
if err := UnmarshalHeader(b[:HeaderLen], &m.Header); err != nil { err := UnmarshalHeader(b[:HeaderLen], &m.Header)
if err != nil {
return nil, err return nil, err
} }
@ -146,7 +148,7 @@ func UnmarshalMessage(b []byte) (*Message, error) {
// resp answers // resp answers
rrIdx := HeaderLen + qLen rrIdx := HeaderLen + qLen
for range int(m.Header.ANCOUNT) { for i := 0; i < int(m.Header.ANCOUNT); i++ {
rr := &RR{} rr := &RR{}
rrLen, err := m.UnmarshalRR(rrIdx, rr) rrLen, err := m.UnmarshalRR(rrIdx, rr)
if err != nil { if err != nil {
@ -163,7 +165,7 @@ func UnmarshalMessage(b []byte) (*Message, error) {
} }
// Header format: // Header format:
// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.1 // https://tools.ietf.org/html/rfc1035#section-4.1.1
// The header contains the following fields: // The header contains the following fields:
// //
// 1 1 1 1 1 1 // 1 1 1 1 1 1
@ -181,6 +183,7 @@ func UnmarshalMessage(b []byte) (*Message, error) {
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | ARCOUNT | // | ARCOUNT |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
type Header struct { type Header struct {
ID uint16 ID uint16
Bits uint16 Bits uint16
@ -191,7 +194,7 @@ type Header struct {
} }
// SetMsgType sets the message type. // SetMsgType sets the message type.
func (h *Header) SetMsgType(qr MsgType) { func (h *Header) SetMsgType(qr int) {
h.Bits |= uint16(qr) << 15 h.Bits |= uint16(qr) << 15
} }
@ -210,11 +213,10 @@ func (h *Header) SetAncount(ancount int) {
h.ANCOUNT = uint16(ancount) h.ANCOUNT = uint16(ancount)
} }
// Not used now, but keep it for future use. func (h *Header) setFlag(QR uint16, Opcode uint16, AA uint16,
// func (h *Header) setFlag(QR uint16, Opcode uint16, AA uint16, TC uint16, RD uint16, RA uint16, RCODE uint16) {
// TC uint16, RD uint16, RA uint16, RCODE uint16) { h.Bits = QR<<15 + Opcode<<11 + AA<<10 + TC<<9 + RD<<8 + RA<<7 + RCODE
// h.Bits = QR<<15 + Opcode<<11 + AA<<10 + TC<<9 + RD<<8 + RA<<7 + RCODE }
// }
// MarshalTo marshals header struct to []byte and write to w. // MarshalTo marshals header struct to []byte and write to w.
func (h *Header) MarshalTo(w io.Writer) (int, error) { func (h *Header) MarshalTo(w io.Writer) (int, error) {
@ -242,7 +244,7 @@ func UnmarshalHeader(b []byte, h *Header) error {
} }
// Question format: // Question format:
// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.2 // https://tools.ietf.org/html/rfc1035#section-4.1.2
// The question section is used to carry the "question" in most queries, // The question section is used to carry the "question" in most queries,
// i.e., the parameters that define what is being asked. The section // i.e., the parameters that define what is being asked. The section
// contains QDCOUNT (usually 1) entries, each of the following format: // contains QDCOUNT (usually 1) entries, each of the following format:
@ -280,12 +282,14 @@ func (q *Question) MarshalTo(w io.Writer) (n int, err error) {
return return
} }
if err = binary.Write(w, binary.BigEndian, q.QTYPE); err != nil { err = binary.Write(w, binary.BigEndian, q.QTYPE)
if err != nil {
return return
} }
n += 2 n += 2
if err = binary.Write(w, binary.BigEndian, q.QCLASS); err != nil { err = binary.Write(w, binary.BigEndian, q.QCLASS)
if err != nil {
return return
} }
n += 2 n += 2
@ -318,8 +322,8 @@ func (m *Message) UnmarshalQuestion(b []byte, q *Question) (n int, err error) {
} }
// RR format: // RR format:
// https://www.rfc-editor.org/rfc/rfc1035#section-3.2.1 // https://tools.ietf.org/html/rfc1035#section-3.2.1
// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.3 // https://tools.ietf.org/html/rfc1035#section-4.1.3
// The answer, authority, and additional sections all share the same // The answer, authority, and additional sections all share the same
// format: a variable number of resource records, where the number of // format: a variable number of resource records, where the number of
// records is specified in the corresponding count field in the header. // records is specified in the corresponding count field in the header.
@ -353,7 +357,7 @@ type RR struct {
RDLENGTH uint16 RDLENGTH uint16
RDATA []byte RDATA []byte
IP netip.Addr IP string
} }
// NewRR returns a new dns rr. // NewRR returns a new dns rr.
@ -368,17 +372,20 @@ func (rr *RR) MarshalTo(w io.Writer) (n int, err error) {
return return
} }
if err = binary.Write(w, binary.BigEndian, rr.TYPE); err != nil { err = binary.Write(w, binary.BigEndian, rr.TYPE)
if err != nil {
return return
} }
n += 2 n += 2
if err = binary.Write(w, binary.BigEndian, rr.CLASS); err != nil { err = binary.Write(w, binary.BigEndian, rr.CLASS)
if err != nil {
return return
} }
n += 2 n += 2
if err = binary.Write(w, binary.BigEndian, rr.TTL); err != nil { err = binary.Write(w, binary.BigEndian, rr.TTL)
if err != nil {
return return
} }
n += 4 n += 4
@ -389,7 +396,8 @@ func (rr *RR) MarshalTo(w io.Writer) (n int, err error) {
} }
n += 2 n += 2
if _, err = w.Write(rr.RDATA); err != nil { _, err = w.Write(rr.RDATA)
if err != nil {
return return
} }
n += len(rr.RDATA) n += len(rr.RDATA)
@ -430,9 +438,9 @@ func (m *Message) UnmarshalRR(start int, rr *RR) (n int, err error) {
rr.RDATA = p[n+10 : n+10+int(rr.RDLENGTH)] rr.RDATA = p[n+10 : n+10+int(rr.RDLENGTH)]
if rr.TYPE == QTypeA { if rr.TYPE == QTypeA {
rr.IP = netip.AddrFrom4(*(*[4]byte)(rr.RDATA[:4])) rr.IP = net.IP(rr.RDATA[:net.IPv4len]).String()
} else if rr.TYPE == QTypeAAAA { } else if rr.TYPE == QTypeAAAA {
rr.IP = netip.AddrFrom16(*(*[16]byte)(rr.RDATA[:16])) rr.IP = net.IP(rr.RDATA[:net.IPv6len]).String()
} }
n = n + 10 + int(rr.RDLENGTH) n = n + 10 + int(rr.RDLENGTH)
@ -471,7 +479,7 @@ func (m *Message) UnmarshalDomainTo(sb *strings.Builder, b []byte) (int, error)
var idx, size int var idx, size int
for len(b[idx:]) != 0 { for len(b[idx:]) != 0 {
// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.4 // https://tools.ietf.org/html/rfc1035#section-4.1.4
// "Message compression", // "Message compression",
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | 1 1| OFFSET | // | 1 1| OFFSET |
@ -482,7 +490,8 @@ func (m *Message) UnmarshalDomainTo(sb *strings.Builder, b []byte) (int, error)
} }
offset := binary.BigEndian.Uint16(b[idx : idx+2]) offset := binary.BigEndian.Uint16(b[idx : idx+2])
if err := m.UnmarshalDomainPointTo(sb, int(offset&0x3FFF)); err != nil { err := m.UnmarshalDomainPointTo(sb, int(offset&0x3FFF))
if err != nil {
return 0, err return 0, err
} }

View File

@ -7,8 +7,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/pool" "github.com/nadoo/glider/pool"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
) )
@ -80,13 +80,13 @@ func (s *Server) ServePacket(pc net.PacketConn, caddr net.Addr, reqBytes []byte)
}() }()
if err != nil { if err != nil {
log.F("[dns] error in exchange for %s: %s", caddr, err) log.F("[dns] error in exchange: %s", err)
return return
} }
_, err = pc.WriteTo(respBytes, caddr) _, err = pc.WriteTo(respBytes, caddr)
if err != nil { if err != nil {
log.F("[dns] error in local write to %s: %s", caddr, err) log.F("[dns] error in local write: %s", err)
return return
} }
} }

View File

@ -1,9 +1,6 @@
package dns package dns
import ( import "sync/atomic"
"net"
"sync/atomic"
)
// UPStream is a dns upstream. // UPStream is a dns upstream.
type UPStream struct { type UPStream struct {
@ -13,12 +10,6 @@ type UPStream struct {
// NewUPStream returns a new UpStream. // NewUPStream returns a new UpStream.
func NewUPStream(servers []string) *UPStream { func NewUPStream(servers []string) *UPStream {
// default port for dns upstream servers
for i, server := range servers {
if _, port, _ := net.SplitHostPort(server); port == "" {
servers[i] = net.JoinHostPort(server, "53")
}
}
return &UPStream{servers: servers} return &UPStream{servers: servers}
} }

View File

@ -9,9 +9,7 @@ import (
_ "github.com/nadoo/glider/proxy/kcp" _ "github.com/nadoo/glider/proxy/kcp"
_ "github.com/nadoo/glider/proxy/mixed" _ "github.com/nadoo/glider/proxy/mixed"
_ "github.com/nadoo/glider/proxy/obfs" _ "github.com/nadoo/glider/proxy/obfs"
_ "github.com/nadoo/glider/proxy/pxyproto"
_ "github.com/nadoo/glider/proxy/reject" _ "github.com/nadoo/glider/proxy/reject"
_ "github.com/nadoo/glider/proxy/smux"
_ "github.com/nadoo/glider/proxy/socks4" _ "github.com/nadoo/glider/proxy/socks4"
_ "github.com/nadoo/glider/proxy/socks5" _ "github.com/nadoo/glider/proxy/socks5"
_ "github.com/nadoo/glider/proxy/ss" _ "github.com/nadoo/glider/proxy/ss"

View File

@ -6,7 +6,5 @@ import (
// comment out the protocols you don't need to make the compiled binary smaller. // comment out the protocols you don't need to make the compiled binary smaller.
_ "github.com/nadoo/glider/proxy/redir" _ "github.com/nadoo/glider/proxy/redir"
_ "github.com/nadoo/glider/proxy/tproxy"
_ "github.com/nadoo/glider/proxy/unix" _ "github.com/nadoo/glider/proxy/unix"
_ "github.com/nadoo/glider/proxy/vsock"
) )

38
go.mod
View File

@ -1,29 +1,29 @@
module github.com/nadoo/glider module github.com/nadoo/glider
go 1.24 go 1.15
require ( require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 // indirect
github.com/nadoo/conflag v0.3.1 github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8
github.com/nadoo/ipset v0.5.0 github.com/mmcloughlin/avo v0.0.0-20201130012700-45c8ae10fd12 // indirect
github.com/xtaci/kcp-go/v5 v5.6.18 github.com/nadoo/conflag v0.2.3
golang.org/x/crypto v0.35.0 github.com/nadoo/ipset v0.3.0
golang.org/x/sys v0.30.0 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/xtaci/kcp-go/v5 v5.6.1
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c
golang.org/x/mod v0.4.0 // indirect
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb // indirect
golang.org/x/sys v0.0.0-20201202213521-69691e467435 // indirect
golang.org/x/tools v0.0.0-20201204062850-545788942d5f // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
) )
require ( // Replace dependency modules with local developing copy
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 // indirect // use `go list -m all` to confirm the final module used
github.com/klauspost/cpuid/v2 v2.2.10 // indirect // replace (
github.com/klauspost/reedsolomon v1.12.4 // indirect // github.com/nadoo/conflag => ../conflag
github.com/pierrec/lz4/v4 v4.1.22 // indirect // )
github.com/pkg/errors v0.9.1 // indirect
github.com/templexxx/cpu v0.1.1 // indirect
github.com/templexxx/xorsimd v0.4.3 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
golang.org/x/net v0.35.0 // indirect
)

215
go.sum
View File

@ -1,10 +1,5 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d h1:CPqTNIigGweVPT4CYb+OO2E6XyRKFOmvTHwWRLgCAlE= github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d h1:CPqTNIigGweVPT4CYb+OO2E6XyRKFOmvTHwWRLgCAlE=
@ -15,115 +10,151 @@ github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 h1:ED31mPIxDJnrLt9W
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152/go.mod h1:I9fhc/EvSg88cDxmfQ47v35Ssz9rlFunL/KY0A1JAYI= github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152/go.mod h1:I9fhc/EvSg88cDxmfQ47v35Ssz9rlFunL/KY0A1JAYI=
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 h1:fBHFH+Y/GPGFGo7LIrErQc3p2MeAhoIQNgaxPWYsSxk= github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 h1:fBHFH+Y/GPGFGo7LIrErQc3p2MeAhoIQNgaxPWYsSxk=
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:ucvhdsUCE3TH0LoLRb6ShHiJl8e39dGlx6A4g/ujlow= github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:ucvhdsUCE3TH0LoLRb6ShHiJl8e39dGlx6A4g/ujlow=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8 h1:R1oP0/QEyvaL7dm+mBQouQ9V1X6gqQr5taZA1yaq5zQ=
github.com/klauspost/reedsolomon v1.12.4 h1:5aDr3ZGoJbgu/8+j45KtUJxzYm8k08JGtB9Wx1VQ4OA= github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8/go.mod h1:TKl4jN3Voofo4UJIicyNhWGp/nlQqQkFxmwIFTvBkKI=
github.com/klauspost/reedsolomon v1.12.4/go.mod h1:d3CzOMOt0JXGIFZm1StgkyF14EYr3xneR2rNWo7NcMU= github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY= github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4= github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/nadoo/conflag v0.3.1 h1:4pHkLIz8PUsfg6ajNYRRSY3bt6m2LPsu6KOzn5uIXQw= github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/nadoo/conflag v0.3.1/go.mod h1:dzFfDUpXdr2uS2oV+udpy5N2vfNOu/bFzjhX1WI52co= github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc= github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ= github.com/klauspost/reedsolomon v1.9.9 h1:qCL7LZlv17xMixl55nq2/Oa1Y86nfO8EqDfv2GHND54=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/klauspost/reedsolomon v1.9.9/go.mod h1:O7yFFHiQwDR6b2t63KPUpccPtNdp5ADgh1gg4fd12wo=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE=
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104 h1:ULR/QWMgcgRiZLUjSSJMU+fW+RDMstRdmnDWj9Q+AsA=
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104/go.mod h1:wqKykBG2QzQDJEzvRkcS8x6MiSJkF52hXZsXcjaB3ls=
github.com/mmcloughlin/avo v0.0.0-20201130012700-45c8ae10fd12 h1:JJvkIBIdkzz71+2UD6CHfjDC2O3fCZJ98KUaB70gr00=
github.com/mmcloughlin/avo v0.0.0-20201130012700-45c8ae10fd12/go.mod h1:6aKT4zZIrpGqB3RpFU14ByCSSyKY6LfJz4J/JJChHfI=
github.com/nadoo/conflag v0.2.3 h1:/+rTaN0bHTIiQbPl1WZK78JRoqjlNqJ9Zf05ep0o5jI=
github.com/nadoo/conflag v0.2.3/go.mod h1:dzFfDUpXdr2uS2oV+udpy5N2vfNOu/bFzjhX1WI52co=
github.com/nadoo/ipset v0.3.0 h1:TgULgp4s2PI3ItoCykDzMp8R49fRhMUNoUUEahERr5o=
github.com/nadoo/ipset v0.3.0/go.mod h1:ugJe3mH5N1UNQbXayGJnLEMALeiwCJYo49Wg4MnZTHU=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/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 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/templexxx/cpu v0.1.1 h1:isxHaxBXpYFWnk2DReuKkigaZyrjs2+9ypIdGP4h+HI= github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/cpu v0.1.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= github.com/templexxx/cpu v0.0.7 h1:pUEZn8JBy/w5yzdYWgx+0m0xL9uk6j4K91C5kOViAzo=
github.com/templexxx/xorsimd v0.4.3 h1:9AQTFHd7Bhk3dIT7Al2XeBX5DWOvsUPZCuhyAtNbHjU= github.com/templexxx/cpu v0.0.7/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/xorsimd v0.4.3/go.mod h1:oZQcD6RFDisW2Am58dSAGwwL6rHjbzrlu25VDqfWkQg= github.com/templexxx/xorsimd v0.4.1 h1:iUZcywbOYDRAZUasAs2eSCUW8eobuZDy0I9FJiORkVg=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/templexxx/xorsimd v0.4.1/go.mod h1:W+ffZz8jJMH2SXwuKu9WhygqBMbFnp14G2fqEr8qaNo=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= github.com/u-root/u-root v7.0.0+incompatible h1:u+KSS04pSxJGI5E7WE4Bs9+Zd75QjFv+REkjy/aoAc8=
github.com/xtaci/kcp-go/v5 v5.6.18 h1:7oV4mc272pcnn39/13BB11Bx7hJM4ogMIEokJYVWn4g= github.com/u-root/u-root v7.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY=
github.com/xtaci/kcp-go/v5 v5.6.18/go.mod h1:75S1AKYYzNUSXIv30h+jPKJYZUwqpfvLshu63nCNSOM= 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/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/arch v0.0.0-20190909030613-46d78d1859ac/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c h1:9HhBz5L/UjnK9XLtiZhYAdue5BVKep3PMmS2LuPDt8k=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201202213521-69691e467435 h1:25AvDqqB9PrNqj1FLf2/70I4W0L19qqoaFq3gjNwbKk=
golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174 h1:0rx0F4EjJNbxTuzWe0KjKcIzs+3VEb/Mrs/d1ciNz1c=
golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201204062850-545788942d5f h1:xZehGf1UH366KQnttgsBQf+bkEpQSVfG4AJX7EIxXAY=
golang.org/x/tools v0.0.0-20201204062850-545788942d5f/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -1,7 +1,7 @@
package ipset package ipset
import ( import (
"net/netip" "errors"
"strings" "strings"
"sync" "sync"
@ -21,30 +21,32 @@ func NewManager(rules []*rule.Config) (*Manager, error) {
return nil, err return nil, err
} }
m := &Manager{} // create ipset, avoid redundant.
sets := make(map[string]struct{}) sets := make(map[string]struct{})
for _, r := range rules { for _, r := range rules {
if r.IPSet == "" { if r.IPSet != "" {
continue
}
if _, ok := sets[r.IPSet]; !ok {
sets[r.IPSet] = struct{}{} sets[r.IPSet] = struct{}{}
ipset.Create(r.IPSet) }
ipset.Flush(r.IPSet)
ipset.Create(r.IPSet+"6", ipset.OptIPv6())
ipset.Flush(r.IPSet + "6")
} }
for set := range sets {
ipset.Create(set)
ipset.Flush(set)
}
// init ipset
m := &Manager{}
for _, r := range rules {
if r.IPSet != "" {
for _, domain := range r.Domain { for _, domain := range r.Domain {
m.domainSet.Store(domain, r.IPSet) m.domainSet.Store(domain, r.IPSet)
} }
for _, ip := range r.IP { for _, ip := range r.IP {
addToSet(r.IPSet, ip) ipset.Add(r.IPSet, ip)
} }
for _, cidr := range r.CIDR { for _, cidr := range r.CIDR {
addToSet(r.IPSet, cidr) ipset.Add(r.IPSet, cidr)
}
} }
} }
@ -52,27 +54,18 @@ func NewManager(rules []*rule.Config) (*Manager, error) {
} }
// AddDomainIP implements the dns AnswerHandler function, used to update ipset according to domainSet rule. // AddDomainIP implements the dns AnswerHandler function, used to update ipset according to domainSet rule.
func (m *Manager) AddDomainIP(domain string, ip netip.Addr) error { func (m *Manager) AddDomainIP(domain, ip string) error {
if domain == "" || ip == "" {
return errors.New("please specify the domain and ip address")
}
domain = strings.ToLower(domain) domain = strings.ToLower(domain)
for i := len(domain); i != -1; { for i := len(domain); i != -1; {
i = strings.LastIndexByte(domain[:i], '.') i = strings.LastIndexByte(domain[:i], '.')
if setName, ok := m.domainSet.Load(domain[i+1:]); ok { if setName, ok := m.domainSet.Load(domain[i+1:]); ok {
addAddrToSet(setName.(string), ip) ipset.Add(setName.(string), ip)
} }
} }
return nil return nil
} }
func addToSet(s, item string) error {
if strings.IndexByte(item, '.') == -1 {
return ipset.Add(s+"6", item)
}
return ipset.Add(s, item)
}
func addAddrToSet(s string, ip netip.Addr) error {
if ip.Is4() {
return ipset.AddAddr(s, ip)
}
return ipset.AddAddr(s+"6", ip)
}

View File

@ -1,10 +1,9 @@
//go:build !linux // +build !linux
package ipset package ipset
import ( import (
"errors" "errors"
"net/netip"
"github.com/nadoo/glider/rule" "github.com/nadoo/glider/rule"
) )
@ -18,6 +17,6 @@ func NewManager(rules []*rule.Config) (*Manager, error) {
} }
// AddDomainIP implements the DNSAnswerHandler function // AddDomainIP implements the DNSAnswerHandler function
func (m *Manager) AddDomainIP(domain string, ip netip.Addr) error { func (m *Manager) AddDomainIP(domain, ip string) error {
return errors.New("ipset not supported on this os") return errors.New("ipset not supported on this os")
} }

35
log/log.go Normal file
View File

@ -0,0 +1,35 @@
package log
import (
"fmt"
stdlog "log"
)
// F is the main log function.
var F = func(string, ...interface{}) {}
// Debugf prints debug log.
func Debugf(format string, v ...interface{}) {
stdlog.SetFlags(stdlog.LstdFlags | stdlog.Lshortfile)
stdlog.Output(2, fmt.Sprintf(format, v...))
}
// Print prints log.
func Print(v ...interface{}) {
stdlog.Print(v...)
}
// Printf prints log.
func Printf(format string, v ...interface{}) {
stdlog.Printf(format, v...)
}
// Fatal log and exit.
func Fatal(v ...interface{}) {
stdlog.Fatal(v...)
}
// Fatalf log and exit.
func Fatalf(f string, v ...interface{}) {
stdlog.Fatalf(f, v...)
}

19
main.go
View File

@ -5,19 +5,20 @@ import (
"net" "net"
"os" "os"
"os/signal" "os/signal"
"strings"
"syscall" "syscall"
"time" "time"
"github.com/nadoo/glider/dns" "github.com/nadoo/glider/dns"
"github.com/nadoo/glider/ipset" "github.com/nadoo/glider/ipset"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/rule" "github.com/nadoo/glider/rule"
"github.com/nadoo/glider/service" "github.com/nadoo/glider/service"
) )
var ( var (
version = "0.17.0" version = "0.13.0"
config = parseConfig() config = parseConfig()
) )
@ -37,8 +38,8 @@ func main() {
// rules // rules
for _, r := range config.rules { for _, r := range config.rules {
if len(r.DNSServers) > 0 {
for _, domain := range r.Domain { for _, domain := range r.Domain {
if len(r.DNSServers) > 0 {
d.SetServers(domain, r.DNSServers) d.SetServers(domain, r.DNSServers)
} }
} }
@ -62,10 +63,6 @@ func main() {
} }
} }
for _, r := range config.rules {
r.IP, r.CIDR, r.Domain = nil, nil, nil
}
// enable checkers // enable checkers
pxy.Check() pxy.Check()
@ -75,16 +72,14 @@ func main() {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
go local.ListenAndServe() go local.ListenAndServe()
} }
// run services // run services
for _, s := range config.Services { for _, s := range config.Services {
service, err := service.New(s) args := strings.Split(s, ",")
if err != nil { go service.Run(args[0], args[1:]...)
log.Fatal(err)
}
go service.Run()
} }
sigCh := make(chan os.Signal, 1) sigCh := make(chan os.Signal, 1)

View File

@ -1,41 +0,0 @@
package log
import (
"fmt"
stdlog "log"
)
var enable = false
// Set sets the logger's verbose mode and output flags.
func Set(verbose bool, flag int) {
enable = verbose
stdlog.SetFlags(flag)
}
// F prints debug log.
func F(f string, v ...any) {
if enable {
stdlog.Output(2, fmt.Sprintf(f, v...))
}
}
// Print prints log.
func Print(v ...any) {
stdlog.Print(v...)
}
// Printf prints log.
func Printf(f string, v ...any) {
stdlog.Printf(f, v...)
}
// Fatal log and exit.
func Fatal(v ...any) {
stdlog.Fatal(v...)
}
// Fatalf log and exit.
func Fatalf(f string, v ...any) {
stdlog.Fatalf(f, v...)
}

View File

@ -1,106 +0,0 @@
// MIT License
//
// Copyright (c) 2016-2017 xtaci
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package smux
import (
"encoding/binary"
"fmt"
)
const ( // cmds
// protocol version 1:
cmdSYN byte = iota // stream open
cmdFIN // stream close, a.k.a EOF mark
cmdPSH // data push
cmdNOP // no operation
// protocol version 2 extra commands
// notify bytes consumed by remote peer-end
cmdUPD
)
const (
// data size of cmdUPD, format:
// |4B data consumed(ACK)| 4B window size(WINDOW) |
szCmdUPD = 8
)
const (
// initial peer window guess, a slow-start
initialPeerWindow = 262144
)
const (
sizeOfVer = 1
sizeOfCmd = 1
sizeOfLength = 2
sizeOfSid = 4
headerSize = sizeOfVer + sizeOfCmd + sizeOfSid + sizeOfLength
)
// Frame defines a packet from or to be multiplexed into a single connection
type Frame struct {
ver byte // version
cmd byte // command
sid uint32 // stream id
data []byte // payload
}
// newFrame creates a new frame with given version, command and stream id
func newFrame(version byte, cmd byte, sid uint32) Frame {
return Frame{ver: version, cmd: cmd, sid: sid}
}
// rawHeader is a byte array representation of Frame header
type rawHeader [headerSize]byte
func (h rawHeader) Version() byte {
return h[0]
}
func (h rawHeader) Cmd() byte {
return h[1]
}
func (h rawHeader) Length() uint16 {
return binary.LittleEndian.Uint16(h[2:])
}
func (h rawHeader) StreamID() uint32 {
return binary.LittleEndian.Uint32(h[4:])
}
func (h rawHeader) String() string {
return fmt.Sprintf("Version:%d Cmd:%d StreamID:%d Length:%d",
h.Version(), h.Cmd(), h.StreamID(), h.Length())
}
// updHeader is a byte array representation of cmdUPD
type updHeader [szCmdUPD]byte
func (h updHeader) Consumed() uint32 {
return binary.LittleEndian.Uint32(h[:])
}
func (h updHeader) Window() uint32 {
return binary.LittleEndian.Uint32(h[4:])
}

View File

@ -1,128 +0,0 @@
// MIT License
//
// Copyright (c) 2016-2017 xtaci
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package smux
import (
"errors"
"fmt"
"io"
"math"
"time"
)
// Config is used to tune the Smux session
type Config struct {
// SMUX Protocol version, support 1,2
Version int
// Disabled keepalive
KeepAliveDisabled bool
// KeepAliveInterval is how often to send a NOP command to the remote
KeepAliveInterval time.Duration
// KeepAliveTimeout is how long the session
// will be closed if no data has arrived
KeepAliveTimeout time.Duration
// MaxFrameSize is used to control the maximum
// frame size to sent to the remote
MaxFrameSize int
// MaxReceiveBuffer is used to control the maximum
// number of data in the buffer pool
MaxReceiveBuffer int
// MaxStreamBuffer is used to control the maximum
// number of data per stream
MaxStreamBuffer int
}
// DefaultConfig is used to return a default configuration
func DefaultConfig() *Config {
return &Config{
Version: 1,
KeepAliveInterval: 10 * time.Second,
KeepAliveTimeout: 30 * time.Second,
MaxFrameSize: 32768,
MaxReceiveBuffer: 4194304,
MaxStreamBuffer: 65536,
}
}
// VerifyConfig is used to verify the sanity of configuration
func VerifyConfig(config *Config) error {
if !(config.Version == 1 || config.Version == 2) {
return errors.New("unsupported protocol version")
}
if !config.KeepAliveDisabled {
if config.KeepAliveInterval == 0 {
return errors.New("keep-alive interval must be positive")
}
if config.KeepAliveTimeout < config.KeepAliveInterval {
return fmt.Errorf("keep-alive timeout must be larger than keep-alive interval")
}
}
if config.MaxFrameSize <= 0 {
return errors.New("max frame size must be positive")
}
if config.MaxFrameSize > 65535 {
return errors.New("max frame size must not be larger than 65535")
}
if config.MaxReceiveBuffer <= 0 {
return errors.New("max receive buffer must be positive")
}
if config.MaxStreamBuffer <= 0 {
return errors.New("max stream buffer must be positive")
}
if config.MaxStreamBuffer > config.MaxReceiveBuffer {
return errors.New("max stream buffer must not be larger than max receive buffer")
}
if config.MaxStreamBuffer > math.MaxInt32 {
return errors.New("max stream buffer cannot be larger than 2147483647")
}
return nil
}
// Server is used to initialize a new server-side connection.
func Server(conn io.ReadWriteCloser, config *Config) (*Session, error) {
if config == nil {
config = DefaultConfig()
}
if err := VerifyConfig(config); err != nil {
return nil, err
}
return newSession(config, conn, false), nil
}
// Client is used to initialize a new client-side connection.
func Client(conn io.ReadWriteCloser, config *Config) (*Session, error) {
if config == nil {
config = DefaultConfig()
}
if err := VerifyConfig(config); err != nil {
return nil, err
}
return newSession(config, conn, true), nil
}

View File

@ -1,621 +0,0 @@
// MIT License
//
// Copyright (c) 2016-2017 xtaci
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package smux
import (
"container/heap"
"encoding/binary"
"errors"
"io"
"net"
"runtime"
"sync"
"sync/atomic"
"time"
"github.com/nadoo/glider/pkg/pool"
)
const (
defaultAcceptBacklog = 1024
maxShaperSize = 1024
openCloseTimeout = 30 * time.Second // Timeout for opening/closing streams
)
// CLASSID represents the class of a frame
type CLASSID int
const (
CLSCTRL CLASSID = iota // prioritized control signal
CLSDATA
)
// timeoutError representing timeouts for operations such as accept, read and write
//
// To better cooperate with the standard library, timeoutError should implement the standard library's `net.Error`.
//
// For example, using smux to implement net.Listener and work with http.Server, the keep-alive connection (*smux.Stream) will be unexpectedly closed.
// For more details, see https://github.com/xtaci/smux/pull/99.
type timeoutError struct{}
func (timeoutError) Error() string { return "timeout" }
func (timeoutError) Temporary() bool { return true }
func (timeoutError) Timeout() bool { return true }
var (
ErrInvalidProtocol = errors.New("invalid protocol")
ErrConsumed = errors.New("peer consumed more than sent")
ErrGoAway = errors.New("stream id overflows, should start a new connection")
ErrTimeout net.Error = &timeoutError{}
ErrWouldBlock = errors.New("operation would block on IO")
)
// writeRequest represents a request to write a frame
type writeRequest struct {
class CLASSID
frame Frame
seq uint32
result chan writeResult
}
// writeResult represents the result of a write request
type writeResult struct {
n int
err error
}
// Session defines a multiplexed connection for streams
type Session struct {
conn io.ReadWriteCloser
config *Config
nextStreamID uint32 // next stream identifier
nextStreamIDLock sync.Mutex
bucket int32 // token bucket
bucketNotify chan struct{} // used for waiting for tokens
streams map[uint32]*stream // all streams in this session
streamLock sync.Mutex // locks streams
die chan struct{} // flag session has died
dieOnce sync.Once
// socket error handling
socketReadError atomic.Value
socketWriteError atomic.Value
chSocketReadError chan struct{}
chSocketWriteError chan struct{}
socketReadErrorOnce sync.Once
socketWriteErrorOnce sync.Once
// smux protocol errors
protoError atomic.Value
chProtoError chan struct{}
protoErrorOnce sync.Once
chAccepts chan *stream
dataReady int32 // flag data has arrived
goAway int32 // flag id exhausted
deadline atomic.Value
requestID uint32 // Monotonic increasing write request ID
shaper chan writeRequest // a shaper for writing
writes chan writeRequest
}
func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session {
s := new(Session)
s.die = make(chan struct{})
s.conn = conn
s.config = config
s.streams = make(map[uint32]*stream)
s.chAccepts = make(chan *stream, defaultAcceptBacklog)
s.bucket = int32(config.MaxReceiveBuffer)
s.bucketNotify = make(chan struct{}, 1)
s.shaper = make(chan writeRequest)
s.writes = make(chan writeRequest)
s.chSocketReadError = make(chan struct{})
s.chSocketWriteError = make(chan struct{})
s.chProtoError = make(chan struct{})
if client {
s.nextStreamID = 1
} else {
s.nextStreamID = 0
}
go s.shaperLoop()
go s.recvLoop()
go s.sendLoop()
if !config.KeepAliveDisabled {
go s.keepalive()
}
return s
}
// OpenStream is used to create a new stream
func (s *Session) OpenStream() (*Stream, error) {
if s.IsClosed() {
return nil, io.ErrClosedPipe
}
// generate stream id
s.nextStreamIDLock.Lock()
if s.goAway > 0 {
s.nextStreamIDLock.Unlock()
return nil, ErrGoAway
}
s.nextStreamID += 2
sid := s.nextStreamID
if sid == sid%2 { // stream-id overflows
s.goAway = 1
s.nextStreamIDLock.Unlock()
return nil, ErrGoAway
}
s.nextStreamIDLock.Unlock()
stream := newStream(sid, s.config.MaxFrameSize, s)
if _, err := s.writeControlFrame(newFrame(byte(s.config.Version), cmdSYN, sid)); err != nil {
return nil, err
}
s.streamLock.Lock()
defer s.streamLock.Unlock()
select {
case <-s.chSocketReadError:
return nil, s.socketReadError.Load().(error)
case <-s.chSocketWriteError:
return nil, s.socketWriteError.Load().(error)
case <-s.die:
return nil, io.ErrClosedPipe
default:
s.streams[sid] = stream
wrapper := &Stream{stream: stream}
// NOTE(x): disabled finalizer for issue #997
/*
runtime.SetFinalizer(wrapper, func(s *Stream) {
s.Close()
})
*/
return wrapper, nil
}
}
// Open returns a generic ReadWriteCloser
func (s *Session) Open() (io.ReadWriteCloser, error) {
return s.OpenStream()
}
// AcceptStream is used to block until the next available stream
// is ready to be accepted.
func (s *Session) AcceptStream() (*Stream, error) {
var deadline <-chan time.Time
if d, ok := s.deadline.Load().(time.Time); ok && !d.IsZero() {
timer := time.NewTimer(time.Until(d))
defer timer.Stop()
deadline = timer.C
}
select {
case stream := <-s.chAccepts:
wrapper := &Stream{stream: stream}
runtime.SetFinalizer(wrapper, func(s *Stream) {
s.Close()
})
return wrapper, nil
case <-deadline:
return nil, ErrTimeout
case <-s.chSocketReadError:
return nil, s.socketReadError.Load().(error)
case <-s.chProtoError:
return nil, s.protoError.Load().(error)
case <-s.die:
return nil, io.ErrClosedPipe
}
}
// Accept Returns a generic ReadWriteCloser instead of smux.Stream
func (s *Session) Accept() (io.ReadWriteCloser, error) {
return s.AcceptStream()
}
// Close is used to close the session and all streams.
func (s *Session) Close() error {
var once bool
s.dieOnce.Do(func() {
close(s.die)
once = true
})
if once {
s.streamLock.Lock()
for k := range s.streams {
s.streams[k].sessionClose()
}
s.streamLock.Unlock()
return s.conn.Close()
} else {
return io.ErrClosedPipe
}
}
// CloseChan can be used by someone who wants to be notified immediately when this
// session is closed
func (s *Session) CloseChan() <-chan struct{} {
return s.die
}
// notifyBucket notifies recvLoop that bucket is available
func (s *Session) notifyBucket() {
select {
case s.bucketNotify <- struct{}{}:
default:
}
}
func (s *Session) notifyReadError(err error) {
s.socketReadErrorOnce.Do(func() {
s.socketReadError.Store(err)
close(s.chSocketReadError)
})
}
func (s *Session) notifyWriteError(err error) {
s.socketWriteErrorOnce.Do(func() {
s.socketWriteError.Store(err)
close(s.chSocketWriteError)
})
}
func (s *Session) notifyProtoError(err error) {
s.protoErrorOnce.Do(func() {
s.protoError.Store(err)
close(s.chProtoError)
})
}
// IsClosed does a safe check to see if we have shutdown
func (s *Session) IsClosed() bool {
select {
case <-s.die:
return true
default:
return false
}
}
// NumStreams returns the number of currently open streams
func (s *Session) NumStreams() int {
if s.IsClosed() {
return 0
}
s.streamLock.Lock()
defer s.streamLock.Unlock()
return len(s.streams)
}
// SetDeadline sets a deadline used by Accept* calls.
// A zero time value disables the deadline.
func (s *Session) SetDeadline(t time.Time) error {
s.deadline.Store(t)
return nil
}
// LocalAddr satisfies net.Conn interface
func (s *Session) LocalAddr() net.Addr {
if ts, ok := s.conn.(interface {
LocalAddr() net.Addr
}); ok {
return ts.LocalAddr()
}
return nil
}
// RemoteAddr satisfies net.Conn interface
func (s *Session) RemoteAddr() net.Addr {
if ts, ok := s.conn.(interface {
RemoteAddr() net.Addr
}); ok {
return ts.RemoteAddr()
}
return nil
}
// notify the session that a stream has closed
func (s *Session) streamClosed(sid uint32) {
s.streamLock.Lock()
if stream, ok := s.streams[sid]; ok {
n := stream.recycleTokens()
if n > 0 { // return remaining tokens to the bucket
if atomic.AddInt32(&s.bucket, int32(n)) > 0 {
s.notifyBucket()
}
}
delete(s.streams, sid)
}
s.streamLock.Unlock()
}
// returnTokens is called by stream to return token after read
func (s *Session) returnTokens(n int) {
if atomic.AddInt32(&s.bucket, int32(n)) > 0 {
s.notifyBucket()
}
}
// recvLoop keeps on reading from underlying connection if tokens are available
func (s *Session) recvLoop() {
var hdr rawHeader
var updHdr updHeader
for {
for atomic.LoadInt32(&s.bucket) <= 0 && !s.IsClosed() {
select {
case <-s.bucketNotify:
case <-s.die:
return
}
}
// read header first
if _, err := io.ReadFull(s.conn, hdr[:]); err == nil {
atomic.StoreInt32(&s.dataReady, 1)
if hdr.Version() != byte(s.config.Version) {
s.notifyProtoError(ErrInvalidProtocol)
return
}
sid := hdr.StreamID()
switch hdr.Cmd() {
case cmdNOP:
case cmdSYN: // stream opening
s.streamLock.Lock()
if _, ok := s.streams[sid]; !ok {
stream := newStream(sid, s.config.MaxFrameSize, s)
s.streams[sid] = stream
select {
case s.chAccepts <- stream:
case <-s.die:
}
}
s.streamLock.Unlock()
case cmdFIN: // stream closing
s.streamLock.Lock()
if stream, ok := s.streams[sid]; ok {
stream.fin()
stream.notifyReadEvent()
}
s.streamLock.Unlock()
case cmdPSH: // data frame
if hdr.Length() > 0 {
newbuf := pool.GetBuffer(int(hdr.Length()))
if written, err := io.ReadFull(s.conn, newbuf); err == nil {
s.streamLock.Lock()
if stream, ok := s.streams[sid]; ok {
stream.pushBytes(newbuf)
// a stream used some token
atomic.AddInt32(&s.bucket, -int32(written))
stream.notifyReadEvent()
} else {
// data directed to a missing/closed stream, recycle the buffer immediately.
pool.PutBuffer(newbuf)
}
s.streamLock.Unlock()
} else {
s.notifyReadError(err)
return
}
}
case cmdUPD: // a window update signal
if _, err := io.ReadFull(s.conn, updHdr[:]); err == nil {
s.streamLock.Lock()
if stream, ok := s.streams[sid]; ok {
stream.update(updHdr.Consumed(), updHdr.Window())
}
s.streamLock.Unlock()
} else {
s.notifyReadError(err)
return
}
default:
s.notifyProtoError(ErrInvalidProtocol)
return
}
} else {
s.notifyReadError(err)
return
}
}
}
// keepalive sends NOP frame to peer to keep the connection alive, and detect dead peers
func (s *Session) keepalive() {
tickerPing := time.NewTicker(s.config.KeepAliveInterval)
tickerTimeout := time.NewTicker(s.config.KeepAliveTimeout)
defer tickerPing.Stop()
defer tickerTimeout.Stop()
for {
select {
case <-tickerPing.C:
s.writeFrameInternal(newFrame(byte(s.config.Version), cmdNOP, 0), tickerPing.C, CLSCTRL)
s.notifyBucket() // force a signal to the recvLoop
case <-tickerTimeout.C:
if !atomic.CompareAndSwapInt32(&s.dataReady, 1, 0) {
// recvLoop may block while bucket is 0, in this case,
// session should not be closed.
if atomic.LoadInt32(&s.bucket) > 0 {
s.Close()
return
}
}
case <-s.die:
return
}
}
}
// shaperLoop implements a priority queue for write requests,
// some control messages are prioritized over data messages
func (s *Session) shaperLoop() {
var reqs shaperHeap
var next writeRequest
var chWrite chan writeRequest
var chShaper chan writeRequest
for {
// chWrite is not available until it has packet to send
if len(reqs) > 0 {
chWrite = s.writes
next = heap.Pop(&reqs).(writeRequest)
} else {
chWrite = nil
}
// control heap size, chShaper is not available until packets are less than maximum allowed
if len(reqs) >= maxShaperSize {
chShaper = nil
} else {
chShaper = s.shaper
}
// assertion on non nil
if chShaper == nil && chWrite == nil {
panic("both channel are nil")
}
select {
case <-s.die:
return
case r := <-chShaper:
if chWrite != nil { // next is valid, reshape
heap.Push(&reqs, next)
}
heap.Push(&reqs, r)
case chWrite <- next:
}
}
}
// sendLoop sends frames to the underlying connection
func (s *Session) sendLoop() {
var buf []byte
var n int
var err error
var vec [][]byte // vector for writeBuffers
bw, ok := s.conn.(interface {
WriteBuffers(v [][]byte) (n int, err error)
})
if ok {
buf = make([]byte, headerSize)
vec = make([][]byte, 2)
} else {
buf = make([]byte, (1<<16)+headerSize)
}
for {
select {
case <-s.die:
return
case request := <-s.writes:
buf[0] = request.frame.ver
buf[1] = request.frame.cmd
binary.LittleEndian.PutUint16(buf[2:], uint16(len(request.frame.data)))
binary.LittleEndian.PutUint32(buf[4:], request.frame.sid)
// support for scatter-gather I/O
if len(vec) > 0 {
vec[0] = buf[:headerSize]
vec[1] = request.frame.data
n, err = bw.WriteBuffers(vec)
} else {
copy(buf[headerSize:], request.frame.data)
n, err = s.conn.Write(buf[:headerSize+len(request.frame.data)])
}
n -= headerSize
if n < 0 {
n = 0
}
result := writeResult{
n: n,
err: err,
}
request.result <- result
close(request.result)
// store conn error
if err != nil {
s.notifyWriteError(err)
return
}
}
}
}
// writeControlFrame writes the control frame to the underlying connection
// and returns the number of bytes written if successful
func (s *Session) writeControlFrame(f Frame) (n int, err error) {
timer := time.NewTimer(openCloseTimeout)
defer timer.Stop()
return s.writeFrameInternal(f, timer.C, CLSCTRL)
}
// internal writeFrame version to support deadline used in keepalive
func (s *Session) writeFrameInternal(f Frame, deadline <-chan time.Time, class CLASSID) (int, error) {
req := writeRequest{
class: class,
frame: f,
seq: atomic.AddUint32(&s.requestID, 1),
result: make(chan writeResult, 1),
}
select {
case s.shaper <- req:
case <-s.die:
return 0, io.ErrClosedPipe
case <-s.chSocketWriteError:
return 0, s.socketWriteError.Load().(error)
case <-deadline:
return 0, ErrTimeout
}
select {
case result := <-req.result:
return result.n, result.err
case <-s.die:
return 0, io.ErrClosedPipe
case <-s.chSocketWriteError:
return 0, s.socketWriteError.Load().(error)
case <-deadline:
return 0, ErrTimeout
}
}

View File

@ -1,56 +0,0 @@
// MIT License
//
// Copyright (c) 2016-2017 xtaci
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package smux
// _itimediff returns the time difference between two uint32 values.
// The result is a signed 32-bit integer representing the difference between 'later' and 'earlier'.
func _itimediff(later, earlier uint32) int32 {
return (int32)(later - earlier)
}
// shaperHeap is a min-heap of writeRequest.
// It orders writeRequests by class first, then by sequence number within the same class.
type shaperHeap []writeRequest
func (h shaperHeap) Len() int { return len(h) }
// Less determines the ordering of elements in the heap.
// Requests are ordered by their class first. If two requests have the same class,
// they are ordered by their sequence numbers.
func (h shaperHeap) Less(i, j int) bool {
if h[i].class != h[j].class {
return h[i].class < h[j].class
}
return _itimediff(h[j].seq, h[i].seq) > 0
}
func (h shaperHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *shaperHeap) Push(x interface{}) { *h = append(*h, x.(writeRequest)) }
func (h *shaperHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}

View File

@ -1,615 +0,0 @@
// MIT License
//
// Copyright (c) 2016-2017 xtaci
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package smux
import (
"encoding/binary"
"io"
"net"
"sync"
"sync/atomic"
"time"
"github.com/nadoo/glider/pkg/pool"
)
// wrapper for GC
type Stream struct {
*stream
}
// Stream implements net.Conn
type stream struct {
id uint32 // Stream identifier
sess *Session
buffers [][]byte // the sequential buffers of stream
heads [][]byte // slice heads of the buffers above, kept for recycle
bufferLock sync.Mutex // Mutex to protect access to buffers
frameSize int // Maximum frame size for the stream
// notify a read event
chReadEvent chan struct{}
// flag the stream has closed
die chan struct{}
dieOnce sync.Once // Ensures die channel is closed only once
// FIN command
chFinEvent chan struct{}
finEventOnce sync.Once // Ensures chFinEvent is closed only once
// deadlines
readDeadline atomic.Value
writeDeadline atomic.Value
// per stream sliding window control
numRead uint32 // count num of bytes read
numWritten uint32 // count num of bytes written
incr uint32 // bytes sent since last window update
// UPD command
peerConsumed uint32 // num of bytes the peer has consumed
peerWindow uint32 // peer window, initialized to 256KB, updated by peer
chUpdate chan struct{} // notify of remote data consuming and window update
}
// newStream initializes and returns a new Stream.
func newStream(id uint32, frameSize int, sess *Session) *stream {
s := new(stream)
s.id = id
s.chReadEvent = make(chan struct{}, 1)
s.chUpdate = make(chan struct{}, 1)
s.frameSize = frameSize
s.sess = sess
s.die = make(chan struct{})
s.chFinEvent = make(chan struct{})
s.peerWindow = initialPeerWindow // set to initial window size
return s
}
// ID returns the stream's unique identifier.
func (s *stream) ID() uint32 {
return s.id
}
// Read reads data from the stream into the provided buffer.
func (s *stream) Read(b []byte) (n int, err error) {
for {
n, err = s.tryRead(b)
if err == ErrWouldBlock {
if ew := s.waitRead(); ew != nil {
return 0, ew
}
} else {
return n, err
}
}
}
// tryRead attempts to read data from the stream without blocking.
func (s *stream) tryRead(b []byte) (n int, err error) {
if s.sess.config.Version == 2 {
return s.tryReadv2(b)
}
if len(b) == 0 {
return 0, nil
}
// A critical section to copy data from buffers to
s.bufferLock.Lock()
if len(s.buffers) > 0 {
n = copy(b, s.buffers[0])
s.buffers[0] = s.buffers[0][n:]
if len(s.buffers[0]) == 0 {
s.buffers[0] = nil
s.buffers = s.buffers[1:]
// full recycle
pool.PutBuffer(s.heads[0])
s.heads = s.heads[1:]
}
}
s.bufferLock.Unlock()
if n > 0 {
s.sess.returnTokens(n)
return n, nil
}
select {
case <-s.die:
return 0, io.EOF
default:
return 0, ErrWouldBlock
}
}
// tryReadv2 is the non-blocking version of Read for version 2 streams.
func (s *stream) tryReadv2(b []byte) (n int, err error) {
if len(b) == 0 {
return 0, nil
}
var notifyConsumed uint32
s.bufferLock.Lock()
if len(s.buffers) > 0 {
n = copy(b, s.buffers[0])
s.buffers[0] = s.buffers[0][n:]
if len(s.buffers[0]) == 0 {
s.buffers[0] = nil
s.buffers = s.buffers[1:]
// full recycle
pool.PutBuffer(s.heads[0])
s.heads = s.heads[1:]
}
}
// in an ideal environment:
// if more than half of buffer has consumed, send read ack to peer
// based on round-trip time of ACK, continous flowing data
// won't slow down due to waiting for ACK, as long as the
// consumer keeps on reading data.
//
// s.numRead == n implies that it's the initial reading
s.numRead += uint32(n)
s.incr += uint32(n)
// for initial reading, send window update
if s.incr >= uint32(s.sess.config.MaxStreamBuffer/2) || s.numRead == uint32(n) {
notifyConsumed = s.numRead
s.incr = 0 // reset couting for next window update
}
s.bufferLock.Unlock()
if n > 0 {
s.sess.returnTokens(n)
// send window update if necessary
if notifyConsumed > 0 {
err := s.sendWindowUpdate(notifyConsumed)
return n, err
} else {
return n, nil
}
}
select {
case <-s.die:
return 0, io.EOF
default:
return 0, ErrWouldBlock
}
}
// WriteTo implements io.WriteTo
// WriteTo writes data to w until there's no more data to write or when an error occurs.
// The return value n is the number of bytes written. Any error encountered during the write is also returned.
// WriteTo calls Write in a loop until there is no more data to write or when an error occurs.
// If the underlying stream is a v2 stream, it will send window update to peer when necessary.
// If the underlying stream is a v1 stream, it will not send window update to peer.
func (s *stream) WriteTo(w io.Writer) (n int64, err error) {
if s.sess.config.Version == 2 {
return s.writeTov2(w)
}
for {
var buf []byte
s.bufferLock.Lock()
if len(s.buffers) > 0 {
buf = s.buffers[0]
s.buffers = s.buffers[1:]
s.heads = s.heads[1:]
}
s.bufferLock.Unlock()
if buf != nil {
nw, ew := w.Write(buf)
// NOTE: WriteTo is a reader, so we need to return tokens here
s.sess.returnTokens(len(buf))
pool.PutBuffer(buf)
if nw > 0 {
n += int64(nw)
}
if ew != nil {
return n, ew
}
} else if ew := s.waitRead(); ew != nil {
return n, ew
}
}
}
// check comments in WriteTo
func (s *stream) writeTov2(w io.Writer) (n int64, err error) {
for {
var notifyConsumed uint32
var buf []byte
s.bufferLock.Lock()
if len(s.buffers) > 0 {
buf = s.buffers[0]
s.buffers = s.buffers[1:]
s.heads = s.heads[1:]
}
s.numRead += uint32(len(buf))
s.incr += uint32(len(buf))
if s.incr >= uint32(s.sess.config.MaxStreamBuffer/2) || s.numRead == uint32(len(buf)) {
notifyConsumed = s.numRead
s.incr = 0
}
s.bufferLock.Unlock()
if buf != nil {
nw, ew := w.Write(buf)
// NOTE: WriteTo is a reader, so we need to return tokens here
s.sess.returnTokens(len(buf))
pool.PutBuffer(buf)
if nw > 0 {
n += int64(nw)
}
if ew != nil {
return n, ew
}
if notifyConsumed > 0 {
if err := s.sendWindowUpdate(notifyConsumed); err != nil {
return n, err
}
}
} else if ew := s.waitRead(); ew != nil {
return n, ew
}
}
}
// sendWindowUpdate sends a window update frame to the peer.
func (s *stream) sendWindowUpdate(consumed uint32) error {
var timer *time.Timer
var deadline <-chan time.Time
if d, ok := s.readDeadline.Load().(time.Time); ok && !d.IsZero() {
timer = time.NewTimer(time.Until(d))
defer timer.Stop()
deadline = timer.C
}
frame := newFrame(byte(s.sess.config.Version), cmdUPD, s.id)
var hdr updHeader
binary.LittleEndian.PutUint32(hdr[:], consumed)
binary.LittleEndian.PutUint32(hdr[4:], uint32(s.sess.config.MaxStreamBuffer))
frame.data = hdr[:]
_, err := s.sess.writeFrameInternal(frame, deadline, CLSCTRL)
return err
}
// waitRead blocks until a read event occurs or a deadline is reached.
func (s *stream) waitRead() error {
var timer *time.Timer
var deadline <-chan time.Time
if d, ok := s.readDeadline.Load().(time.Time); ok && !d.IsZero() {
timer = time.NewTimer(time.Until(d))
defer timer.Stop()
deadline = timer.C
}
select {
case <-s.chReadEvent: // notify some data has arrived, or closed
return nil
case <-s.chFinEvent:
// BUGFIX(xtaci): Fix for https://github.com/xtaci/smux/issues/82
s.bufferLock.Lock()
defer s.bufferLock.Unlock()
if len(s.buffers) > 0 {
return nil
}
return io.EOF
case <-s.sess.chSocketReadError:
return s.sess.socketReadError.Load().(error)
case <-s.sess.chProtoError:
return s.sess.protoError.Load().(error)
case <-deadline:
return ErrTimeout
case <-s.die:
return io.ErrClosedPipe
}
}
// Write implements net.Conn
//
// Note that the behavior when multiple goroutines write concurrently is not deterministic,
// frames may interleave in random way.
func (s *stream) Write(b []byte) (n int, err error) {
if s.sess.config.Version == 2 {
return s.writeV2(b)
}
var deadline <-chan time.Time
if d, ok := s.writeDeadline.Load().(time.Time); ok && !d.IsZero() {
timer := time.NewTimer(time.Until(d))
defer timer.Stop()
deadline = timer.C
}
// check if stream has closed
select {
case <-s.chFinEvent: // passive closing
return 0, io.EOF
case <-s.die:
return 0, io.ErrClosedPipe
default:
}
// frame split and transmit
sent := 0
frame := newFrame(byte(s.sess.config.Version), cmdPSH, s.id)
bts := b
for len(bts) > 0 {
sz := len(bts)
if sz > s.frameSize {
sz = s.frameSize
}
frame.data = bts[:sz]
bts = bts[sz:]
n, err := s.sess.writeFrameInternal(frame, deadline, CLSDATA)
s.numWritten++
sent += n
if err != nil {
return sent, err
}
}
return sent, nil
}
// writeV2 writes data to the stream for version 2 streams.
func (s *stream) writeV2(b []byte) (n int, err error) {
// check empty input
if len(b) == 0 {
return 0, nil
}
// check if stream has closed
select {
case <-s.chFinEvent:
return 0, io.EOF
case <-s.die:
return 0, io.ErrClosedPipe
default:
}
// create write deadline timer
var deadline <-chan time.Time
if d, ok := s.writeDeadline.Load().(time.Time); ok && !d.IsZero() {
timer := time.NewTimer(time.Until(d))
defer timer.Stop()
deadline = timer.C
}
// frame split and transmit process
sent := 0
frame := newFrame(byte(s.sess.config.Version), cmdPSH, s.id)
for {
// per stream sliding window control
// [.... [consumed... numWritten] ... win... ]
// [.... [consumed...................+rmtwnd]]
var bts []byte
// note:
// even if uint32 overflow, this math still works:
// eg1: uint32(0) - uint32(math.MaxUint32) = 1
// eg2: int32(uint32(0) - uint32(1)) = -1
//
// basicially, you can take it as a MODULAR ARITHMETIC
inflight := int32(atomic.LoadUint32(&s.numWritten) - atomic.LoadUint32(&s.peerConsumed))
if inflight < 0 { // security check for malformed data
return 0, ErrConsumed
}
// make sure you understand 'win' is calculated in modular arithmetic(2^32(4GB))
win := int32(atomic.LoadUint32(&s.peerWindow)) - inflight
if win > 0 {
// determine how many bytes to send
if win > int32(len(b)) {
bts = b
b = nil
} else {
bts = b[:win]
b = b[win:]
}
// frame split and transmit
for len(bts) > 0 {
// splitting frame
sz := len(bts)
if sz > s.frameSize {
sz = s.frameSize
}
frame.data = bts[:sz]
bts = bts[sz:]
// transmit of frame
n, err := s.sess.writeFrameInternal(frame, deadline, CLSDATA)
atomic.AddUint32(&s.numWritten, uint32(sz))
sent += n
if err != nil {
return sent, err
}
}
}
// if there is any data left to be sent,
// wait until stream closes, window changes or deadline reached
// this blocking behavior will back propagate flow control to upper layer.
if len(b) > 0 {
select {
case <-s.chFinEvent:
return 0, io.EOF
case <-s.die:
return sent, io.ErrClosedPipe
case <-deadline:
return sent, ErrTimeout
case <-s.sess.chSocketWriteError:
return sent, s.sess.socketWriteError.Load().(error)
case <-s.chUpdate: // notify of remote data consuming and window update
continue
}
} else {
return sent, nil
}
}
}
// Close implements net.Conn
func (s *stream) Close() error {
var once bool
var err error
s.dieOnce.Do(func() {
close(s.die)
once = true
})
if once {
// send FIN in order
f := newFrame(byte(s.sess.config.Version), cmdFIN, s.id)
timer := time.NewTimer(openCloseTimeout)
defer timer.Stop()
_, err = s.sess.writeFrameInternal(f, timer.C, CLSDATA)
s.sess.streamClosed(s.id)
return err
} else {
return io.ErrClosedPipe
}
}
// GetDieCh returns a readonly chan which can be readable
// when the stream is to be closed.
func (s *stream) GetDieCh() <-chan struct{} {
return s.die
}
// SetReadDeadline sets the read deadline as defined by
// net.Conn.SetReadDeadline.
// A zero time value disables the deadline.
func (s *stream) SetReadDeadline(t time.Time) error {
s.readDeadline.Store(t)
s.notifyReadEvent()
return nil
}
// SetWriteDeadline sets the write deadline as defined by
// net.Conn.SetWriteDeadline.
// A zero time value disables the deadline.
func (s *stream) SetWriteDeadline(t time.Time) error {
s.writeDeadline.Store(t)
return nil
}
// SetDeadline sets both read and write deadlines as defined by
// net.Conn.SetDeadline.
// A zero time value disables the deadlines.
func (s *stream) SetDeadline(t time.Time) error {
if err := s.SetReadDeadline(t); err != nil {
return err
}
if err := s.SetWriteDeadline(t); err != nil {
return err
}
return nil
}
// session closes
func (s *stream) sessionClose() { s.dieOnce.Do(func() { close(s.die) }) }
// LocalAddr satisfies net.Conn interface
func (s *stream) LocalAddr() net.Addr {
if ts, ok := s.sess.conn.(interface {
LocalAddr() net.Addr
}); ok {
return ts.LocalAddr()
}
return nil
}
// RemoteAddr satisfies net.Conn interface
func (s *stream) RemoteAddr() net.Addr {
if ts, ok := s.sess.conn.(interface {
RemoteAddr() net.Addr
}); ok {
return ts.RemoteAddr()
}
return nil
}
// pushBytes append buf to buffers
func (s *stream) pushBytes(buf []byte) (written int, err error) {
s.bufferLock.Lock()
s.buffers = append(s.buffers, buf)
s.heads = append(s.heads, buf)
s.bufferLock.Unlock()
return
}
// recycleTokens transform remaining bytes to tokens(will truncate buffer)
func (s *stream) recycleTokens() (n int) {
s.bufferLock.Lock()
for k := range s.buffers {
n += len(s.buffers[k])
pool.PutBuffer(s.heads[k])
}
s.buffers = nil
s.heads = nil
s.bufferLock.Unlock()
return
}
// notify read event
func (s *stream) notifyReadEvent() {
select {
case s.chReadEvent <- struct{}{}:
default:
}
}
// update command
func (s *stream) update(consumed uint32, window uint32) {
atomic.StoreUint32(&s.peerConsumed, consumed)
atomic.StoreUint32(&s.peerWindow, window)
select {
case s.chUpdate <- struct{}{}:
default:
}
}
// mark this stream has been closed in protocol
func (s *stream) fin() {
s.finEventOnce.Do(func() {
close(s.chFinEvent)
})
}

View File

@ -1,31 +0,0 @@
package sockopt
import (
"net"
"syscall"
)
// Options is the options struct.
type Options struct {
bindIface *net.Interface
reuseAddr bool
}
// Option is the function paramater.
type Option func(opts *Options)
// Bind sets the bind interface option.
func Bind(intf *net.Interface) Option { return func(opts *Options) { opts.bindIface = intf } }
// ReuseAddr sets the reuse addr option.
func ReuseAddr() Option { return func(opts *Options) { opts.reuseAddr = true } }
// Control returns a control function for the net.Dialer and net.ListenConfig.
func Control(opts ...Option) func(network, address string, c syscall.RawConn) error {
option := &Options{}
for _, opt := range opts {
opt(option)
}
return control(option)
}

View File

@ -1,28 +0,0 @@
package sockopt
import (
"syscall"
"golang.org/x/sys/unix"
)
func control(opt *Options) func(network, address string, c syscall.RawConn) error {
return func(network, address string, c syscall.RawConn) (err error) {
return c.Control(func(fd uintptr) {
if opt.bindIface != nil {
switch network {
case "tcp4", "udp4":
unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, opt.bindIface.Index)
case "tcp6", "udp6":
unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, opt.bindIface.Index)
}
}
if opt.reuseAddr {
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
}
})
}
}

View File

@ -1,23 +0,0 @@
package sockopt
import (
"syscall"
"golang.org/x/sys/unix"
)
func control(opt *Options) func(network, address string, c syscall.RawConn) error {
return func(network, address string, c syscall.RawConn) (err error) {
return c.Control(func(fd uintptr) {
if opt.bindIface != nil {
unix.BindToDevice(int(fd), opt.bindIface.Name)
}
if opt.reuseAddr {
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
}
})
}
}

View File

@ -1,9 +0,0 @@
//go:build !linux && !darwin
package sockopt
import (
"syscall"
)
func control(opt *Options) func(string, string, syscall.RawConn) error { return nil }

View File

@ -3,7 +3,6 @@ package pool
import ( import (
"math/bits" "math/bits"
"sync" "sync"
"unsafe"
) )
const ( const (
@ -18,12 +17,11 @@ var (
) )
func init() { func init() {
for i := range num { for i := 0; i < num; i++ {
size := 1 << i size := 1 << i
sizes[i] = size sizes[i] = size
pools[i].New = func() any { pools[i].New = func() interface{} {
buf := make([]byte, size) return make([]byte, size)
return unsafe.SliceData(buf)
} }
} }
} }
@ -32,10 +30,11 @@ func init() {
// otherwise, this function will call make([]byte, size) directly. // otherwise, this function will call make([]byte, size) directly.
func GetBuffer(size int) []byte { func GetBuffer(size int) []byte {
if size >= 1 && size <= maxsize { if size >= 1 && size <= maxsize {
i := bits.Len32(uint32(size - 1)) i := bits.Len32(uint32(size)) - 1
if p := pools[i].Get().(*byte); p != nil { if sizes[i] < size {
return unsafe.Slice(p, 1<<i)[:size] i += 1
} }
return pools[i].Get().([]byte)[:size]
} }
return make([]byte, size) return make([]byte, size)
} }
@ -43,9 +42,9 @@ func GetBuffer(size int) []byte {
// PutBuffer puts a buffer into pool. // PutBuffer puts a buffer into pool.
func PutBuffer(buf []byte) { func PutBuffer(buf []byte) {
if size := cap(buf); size >= 1 && size <= maxsize { if size := cap(buf); size >= 1 && size <= maxsize {
i := bits.Len32(uint32(size - 1)) i := bits.Len32(uint32(size)) - 1
if sizes[i] == size { if sizes[i] == size {
pools[i].Put(unsafe.SliceData(buf)) pools[i].Put(buf)
} }
} }
} }

View File

@ -6,7 +6,7 @@ import (
) )
var bytesBufPool = sync.Pool{ var bytesBufPool = sync.Pool{
New: func() any { return &bytes.Buffer{} }, New: func() interface{} { return &bytes.Buffer{} },
} }
// GetBytesBuffer returns a bytes.buffer from pool. // GetBytesBuffer returns a bytes.buffer from pool.
@ -16,8 +16,10 @@ func GetBytesBuffer() *bytes.Buffer {
// PutBytesBuffer puts a bytes.buffer into pool. // PutBytesBuffer puts a bytes.buffer into pool.
func PutBytesBuffer(buf *bytes.Buffer) { func PutBytesBuffer(buf *bytes.Buffer) {
if buf.Cap() <= 64<<10 { if buf.Cap() > 64<<10 {
return
}
buf.Reset() buf.Reset()
bytesBufPool.Put(buf) bytesBufPool.Put(buf)
} }
}

View File

@ -10,15 +10,15 @@ import (
"sync" "sync"
"time" "time"
"github.com/nadoo/glider/pkg/pool" "github.com/nadoo/glider/pool"
) )
var ( const (
// TCPBufSize is the size of tcp buffer. // TCPBufSize is the size of tcp buffer.
TCPBufSize = 32 << 10 TCPBufSize = 32 << 10
// UDPBufSize is the size of udp buffer. // UDPBufSize is the size of udp buffer.
UDPBufSize = 2 << 10 UDPBufSize = 64 << 10
) )
// Conn is a connection with buffered reader. // Conn is a connection with buffered reader.
@ -45,12 +45,6 @@ func (c *Conn) Peek(n int) ([]byte, error) { return c.r.Peek(n) }
// WriteTo implements io.WriterTo. // WriteTo implements io.WriterTo.
func (c *Conn) WriteTo(w io.Writer) (n int64, err error) { return c.r.WriteTo(w) } func (c *Conn) WriteTo(w io.Writer) (n int64, err error) { return c.r.WriteTo(w) }
// Close closes the Conn.
func (c *Conn) Close() error {
pool.PutBufReader(c.r)
return c.Conn.Close()
}
// Relay relays between left and right. // Relay relays between left and right.
func Relay(left, right net.Conn) error { func Relay(left, right net.Conn) error {
var err, err1 error var err, err1 error
@ -79,6 +73,12 @@ func Relay(left, right net.Conn) error {
return nil return nil
} }
// Close closes the Conn.
func (c *Conn) Close() error {
pool.PutBufReader(c.r)
return c.Conn.Close()
}
// Copy copies from src to dst. // Copy copies from src to dst.
func Copy(dst io.Writer, src io.Reader) (written int64, err error) { func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
dst = underlyingWriter(dst) dst = underlyingWriter(dst)
@ -175,30 +175,19 @@ func CopyBuffer(dst io.Writer, src io.Reader) (written int64, err error) {
return written, err return written, err
} }
// CopyUDP copys from src to dst at target with read timeout. // RelayUDP copys from src to dst at target with read timeout.
// if step sets to non-zero value, func RelayUDP(dst net.PacketConn, target net.Addr, src net.PacketConn, timeout time.Duration) error {
// the read timeout will be increased from 0 to timeout by step in every read operation. b := pool.GetBuffer(UDPBufSize)
func CopyUDP(dst net.PacketConn, writeTo net.Addr, src net.PacketConn, timeout time.Duration, step time.Duration) error { defer pool.PutBuffer(b)
buf := pool.GetBuffer(UDPBufSize)
defer pool.PutBuffer(buf)
var t time.Duration
for { for {
if t += step; t == 0 || t > timeout { src.SetReadDeadline(time.Now().Add(timeout))
t = timeout n, _, err := src.ReadFrom(b)
}
src.SetReadDeadline(time.Now().Add(t))
n, addr, err := src.ReadFrom(buf)
if err != nil { if err != nil {
return err return err
} }
if writeTo != nil { _, err = dst.WriteTo(b[:n], target)
addr = writeTo
}
_, err = dst.WriteTo(buf[:n], addr)
if err != nil { if err != nil {
return err return err
} }

View File

@ -3,12 +3,10 @@ package proxy
import ( import (
"errors" "errors"
"net" "net"
"sort"
"strings" "strings"
) )
var ( var (
// ErrNotSupported indicates that the operation is not supported
ErrNotSupported = errors.New("not supported") ErrNotSupported = errors.New("not supported")
) )
@ -33,7 +31,7 @@ type UDPDialer interface {
Addr() string Addr() string
// DialUDP connects to the given address // DialUDP connects to the given address
DialUDP(network, addr string) (pc net.PacketConn, err error) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error)
} }
// DialerCreator is a function to create dialers. // DialerCreator is a function to create dialers.
@ -55,10 +53,6 @@ func DialerFromURL(s string, dialer Dialer) (Dialer, error) {
return nil, errors.New("DialerFromURL: dialer cannot be nil") return nil, errors.New("DialerFromURL: dialer cannot be nil")
} }
if !strings.Contains(s, "://") {
s = s + "://"
}
scheme := s[:strings.Index(s, ":")] scheme := s[:strings.Index(s, ":")]
c, ok := dialerCreators[strings.ToLower(scheme)] c, ok := dialerCreators[strings.ToLower(scheme)]
if ok { if ok {
@ -67,13 +61,3 @@ func DialerFromURL(s string, dialer Dialer) (Dialer, error) {
return nil, errors.New("unknown scheme '" + scheme + "'") return nil, errors.New("unknown scheme '" + scheme + "'")
} }
// DialerSchemes returns the registered dialer schemes.
func DialerSchemes() string {
s := make([]string, 0, len(dialerCreators))
for name := range dialerCreators {
s = append(s, name)
}
sort.Strings(s)
return strings.Join(s, " ")
}

View File

@ -1,13 +1,11 @@
package proxy package proxy
import ( import (
"context"
"errors" "errors"
"net" "net"
"net/netip"
"time" "time"
"github.com/nadoo/glider/pkg/sockopt" "github.com/nadoo/glider/log"
) )
// Direct proxy. // Direct proxy.
@ -18,17 +16,16 @@ type Direct struct {
relayTimeout time.Duration relayTimeout time.Duration
} }
func init() { // Default dialer.
RegisterDialer("direct", NewDirectDialer) var Default = &Direct{dialTimeout: time.Second * 3}
}
// NewDirect returns a Direct dialer. // NewDirect returns a Direct dialer.
func NewDirect(intface string, dialTimeout, relayTimeout time.Duration) (*Direct, error) { func NewDirect(intface string, dialTimeout, relayTimeout time.Duration) (*Direct, error) {
d := &Direct{dialTimeout: dialTimeout, relayTimeout: relayTimeout} d := &Direct{dialTimeout: dialTimeout, relayTimeout: relayTimeout}
if intface != "" { if intface != "" {
if addr, err := netip.ParseAddr(intface); err == nil { if ip := net.ParseIP(intface); ip != nil {
d.ip = addr.AsSlice() d.ip = net.ParseIP(intface)
} else { } else {
iface, err := net.InterfaceByName(intface) iface, err := net.InterfaceByName(intface)
if err != nil { if err != nil {
@ -41,14 +38,6 @@ func NewDirect(intface string, dialTimeout, relayTimeout time.Duration) (*Direct
return d, nil return d, nil
} }
// NewDirectDialer returns a direct dialer.
func NewDirectDialer(s string, d Dialer) (Dialer, error) {
if d == nil {
return NewDirect("", time.Duration(3)*time.Second, time.Duration(3)*time.Second)
}
return d, nil
}
// Addr returns forwarder's address. // Addr returns forwarder's address.
func (d *Direct) Addr() string { return "DIRECT" } func (d *Direct) Addr() string { return "DIRECT" }
@ -87,10 +76,6 @@ func (d *Direct) dial(network, addr string, localIP net.IP) (net.Conn, error) {
} }
dialer := &net.Dialer{LocalAddr: la, Timeout: d.dialTimeout} dialer := &net.Dialer{LocalAddr: la, Timeout: d.dialTimeout}
if d.iface != nil {
dialer.Control = sockopt.Control(sockopt.Bind(d.iface))
}
c, err := dialer.Dial(network, addr) c, err := dialer.Dial(network, addr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -108,44 +93,33 @@ func (d *Direct) dial(network, addr string, localIP net.IP) (net.Conn, error) {
} }
// DialUDP connects to the given address. // DialUDP connects to the given address.
func (d *Direct) DialUDP(network, addr string) (net.PacketConn, error) { func (d *Direct) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
// TODO: support specifying local interface
var la string var la string
if d.ip != nil { if d.ip != nil {
la = net.JoinHostPort(d.ip.String(), "0") la = net.JoinHostPort(d.ip.String(), "0")
} }
lc := &net.ListenConfig{} pc, err := net.ListenPacket(network, la)
if d.iface != nil { if err != nil {
lc.Control = sockopt.Control(sockopt.Bind(d.iface)) log.F("ListenPacket error: %s", err)
return nil, nil, err
} }
return lc.ListenPacket(context.Background(), network, la) uAddr, err := net.ResolveUDPAddr("udp", addr)
return pc, uAddr, err
} }
// IFaceIPs returns ip addresses according to the specified interface. // IFaceIPs returns ip addresses according to the specified interface.
func (d *Direct) IFaceIPs() (ips []net.IP) { func (d *Direct) IFaceIPs() (ips []net.IP) {
ipNets, err := d.iface.Addrs() ipnets, err := d.iface.Addrs()
if err != nil { if err != nil {
return return
} }
for _, ipNet := range ipNets {
ips = append(ips, ipNet.(*net.IPNet).IP) //!ip.IsLinkLocalUnicast() for _, ipnet := range ipnets {
ips = append(ips, ipnet.(*net.IPNet).IP) //!ip.IsLinkLocalUnicast()
} }
return return
} }
func init() {
AddUsage("direct", `
Direct scheme:
direct://
Only needed when you want to specify the outgoing interface:
glider -verbose -listen :8443 -forward direct://#interface=eth0
Or load balance multiple interfaces directly:
glider -verbose -listen :8443 -forward direct://#interface=eth0 -forward direct://#interface=eth1 -strategy rr
Or you can use the high availability mode:
glider -verbose -listen :8443 -forward direct://#interface=eth0&priority=100 -forward direct://#interface=eth1&priority=200 -strategy ha
`)
}

View File

@ -6,8 +6,8 @@ import (
"net" "net"
"net/textproto" "net/textproto"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/pool" "github.com/nadoo/glider/pool"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
) )
@ -33,8 +33,6 @@ func (s *HTTP) Dial(network, addr string) (net.Conn, error) {
} }
buf := pool.GetBytesBuffer() buf := pool.GetBytesBuffer()
defer pool.PutBytesBuffer(buf)
buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n") buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n")
buf.WriteString("Host: " + addr + "\r\n") buf.WriteString("Host: " + addr + "\r\n")
buf.WriteString("Proxy-Connection: Keep-Alive\r\n") buf.WriteString("Proxy-Connection: Keep-Alive\r\n")
@ -47,6 +45,7 @@ func (s *HTTP) Dial(network, addr string) (net.Conn, error) {
// header ended // header ended
buf.WriteString("\r\n") buf.WriteString("\r\n")
_, err = rc.Write(buf.Bytes()) _, err = rc.Write(buf.Bytes())
pool.PutBytesBuffer(buf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -77,6 +76,6 @@ func (s *HTTP) Dial(network, addr string) (net.Conn, error) {
} }
// DialUDP connects to the given address via the proxy. // DialUDP connects to the given address via the proxy.
func (s *HTTP) DialUDP(network, addr string) (pc net.PacketConn, err error) { func (s *HTTP) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
return nil, proxy.ErrNotSupported return nil, nil, proxy.ErrNotSupported
} }

View File

@ -11,7 +11,7 @@ import (
"net/url" "net/url"
"strings" "strings"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
) )
@ -112,10 +112,3 @@ func extractUserPass(auth string) (username, password string, ok bool) {
return s[:idx], s[idx+1:], true return s[:idx], s[idx+1:], true
} }
func init() {
proxy.AddUsage("http", `
Http scheme:
http://[user:pass@]host:port
`)
}

View File

@ -3,16 +3,16 @@ package http
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"fmt" "errors"
"net/textproto" "net/textproto"
"net/url" "net/url"
"strings" "strings"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
) )
// Methods are http methods from rfc. // Methods are http methods from rfc.
// https://www.rfc-editor.org/rfc/rfc2616, http methods must be uppercase // https://www.ietf.org/rfc/rfc2616.txt, http methods must be uppercase
var Methods = [...][]byte{ var Methods = [...][]byte{
[]byte("GET"), []byte("GET"),
[]byte("POST"), []byte("POST"),
@ -46,7 +46,7 @@ func parseRequest(r *bufio.Reader) (*request, error) {
method, uri, proto, ok := parseStartLine(line) method, uri, proto, ok := parseStartLine(line)
if !ok { if !ok {
return nil, fmt.Errorf("error in parseStartLine: %s", line) return nil, errors.New("error in parseStartLine")
} }
header, err := tpr.ReadMIMEHeader() header, err := tpr.ReadMIMEHeader()

View File

@ -8,8 +8,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/pool" "github.com/nadoo/glider/pool"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
) )
@ -22,7 +22,7 @@ func NewHTTPServer(s string, p proxy.Proxy) (proxy.Server, error) {
func (s *HTTP) ListenAndServe() { func (s *HTTP) ListenAndServe() {
l, err := net.Listen("tcp", s.addr) l, err := net.Listen("tcp", s.addr)
if err != nil { if err != nil {
log.Fatalf("[http] failed to listen on %s: %v", s.addr, err) log.F("[http] failed to listen on %s: %v", s.addr, err)
return return
} }
defer l.Close() defer l.Close()
@ -42,16 +42,16 @@ func (s *HTTP) ListenAndServe() {
// Serve serves a connection. // Serve serves a connection.
func (s *HTTP) Serve(cc net.Conn) { func (s *HTTP) Serve(cc net.Conn) {
defer cc.Close()
if c, ok := cc.(*net.TCPConn); ok { if c, ok := cc.(*net.TCPConn); ok {
c.SetKeepAlive(true) c.SetKeepAlive(true)
} }
c := proxy.NewConn(cc) c := proxy.NewConn(cc)
defer c.Close()
req, err := parseRequest(c.Reader()) req, err := parseRequest(c.Reader())
if err != nil { if err != nil {
log.F("[http] can not parse request from %s, error: %v", c.RemoteAddr(), err) log.F("[http] can not parse request from %s", c.RemoteAddr())
return return
} }

View File

@ -12,7 +12,7 @@ import (
kcp "github.com/xtaci/kcp-go/v5" kcp "github.com/xtaci/kcp-go/v5"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
) )
@ -143,14 +143,15 @@ func NewKCPDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
// NewKCPServer returns a kcp proxy server. // NewKCPServer returns a kcp proxy server.
func NewKCPServer(s string, p proxy.Proxy) (proxy.Server, error) { func NewKCPServer(s string, p proxy.Proxy) (proxy.Server, error) {
schemes := strings.SplitN(s, ",", 2) transport := strings.Split(s, ",")
k, err := NewKCP(schemes[0], nil, p)
k, err := NewKCP(transport[0], nil, p)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(schemes) > 1 { if len(transport) > 1 {
k.server, err = proxy.ServerFromURL(schemes[1], p) k.server, err = proxy.ServerFromURL(transport[1], p)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -163,7 +164,7 @@ func NewKCPServer(s string, p proxy.Proxy) (proxy.Server, error) {
func (s *KCP) ListenAndServe() { func (s *KCP) ListenAndServe() {
l, err := kcp.ListenWithOptions(s.addr, s.block, s.dataShards, s.parityShards) l, err := kcp.ListenWithOptions(s.addr, s.block, s.dataShards, s.parityShards)
if err != nil { if err != nil {
log.Fatalf("[kcp] failed to listen on %s: %v", s.addr, err) log.F("[kcp] failed to listen on %s: %v", s.addr, err)
return return
} }
defer l.Close() defer l.Close()
@ -239,8 +240,8 @@ func (s *KCP) Dial(network, addr string) (net.Conn, error) {
} }
// DialUDP connects to the given address via the proxy. // DialUDP connects to the given address via the proxy.
func (s *KCP) DialUDP(network, addr string) (net.PacketConn, error) { func (s *KCP) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
return nil, proxy.ErrNotSupported return nil, nil, proxy.ErrNotSupported
} }
func (s *KCP) setParams(c *kcp.UDPSession) { func (s *KCP) setParams(c *kcp.UDPSession) {
@ -266,16 +267,3 @@ func (s *KCP) setParams(c *kcp.UDPSession) {
c.SetMtu(1350) c.SetMtu(1350)
c.SetACKNoDelay(true) c.SetACKNoDelay(true)
} }
func init() {
proxy.AddUsage("kcp", `
KCP scheme:
kcp://CRYPT:KEY@host:port[?dataShards=NUM&parityShards=NUM&mode=MODE]
Available crypt types for KCP:
none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20
Available modes for KCP:
fast, fast2, fast3, normal, default: fast
`)
}

View File

@ -1,10 +1,11 @@
package mixed package mixed
import ( import (
"bytes"
"net" "net"
"net/url" "net/url"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/proxy/http" "github.com/nadoo/glider/proxy/http"
"github.com/nadoo/glider/proxy/socks5" "github.com/nadoo/glider/proxy/socks5"
@ -60,11 +61,11 @@ func (m *Mixed) ListenAndServe() {
l, err := net.Listen("tcp", m.addr) l, err := net.Listen("tcp", m.addr)
if err != nil { if err != nil {
log.Fatalf("[mixed] failed to listen on %s: %v", m.addr, err) log.F("[mixed] failed to listen on %s: %v", m.addr, err)
return return
} }
log.F("[mixed] http & socks5 server listening TCP on %s", m.addr) log.F("[mixed] listening TCP on %s", m.addr)
for { for {
c, err := l.Accept() c, err := l.Accept()
@ -79,12 +80,37 @@ func (m *Mixed) ListenAndServe() {
// Serve serves connections. // Serve serves connections.
func (m *Mixed) Serve(c net.Conn) { func (m *Mixed) Serve(c net.Conn) {
conn := proxy.NewConn(c) defer c.Close()
if head, err := conn.Peek(1); err == nil {
if c, ok := c.(*net.TCPConn); ok {
c.SetKeepAlive(true)
}
cc := proxy.NewConn(c)
head, err := cc.Peek(1)
if err != nil {
// log.F("[mixed] socks5 peek error: %s", err)
return
}
// check socks5, client send socksversion: 5 as the first byte
if head[0] == socks5.Version { if head[0] == socks5.Version {
m.socks5Server.Serve(conn) m.socks5Server.Serve(cc)
return
}
head, err = cc.Peek(8)
if err != nil {
log.F("[mixed] http peek error: %s", err)
return
}
for _, method := range http.Methods {
if bytes.HasPrefix(head, method) {
m.httpServer.Serve(cc)
return return
} }
} }
m.httpServer.Serve(conn)
log.F("[mixed] unknown request from %s, ignored", c.RemoteAddr())
} }

View File

@ -7,7 +7,7 @@ import (
"io" "io"
"net" "net"
"github.com/nadoo/glider/pkg/pool" "github.com/nadoo/glider/pool"
) )
// HTTPObfs struct // HTTPObfs struct

View File

@ -6,7 +6,7 @@ import (
"net" "net"
"net/url" "net/url"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
) )
@ -106,16 +106,6 @@ func (s *Obfs) Dial(network, addr string) (net.Conn, error) {
} }
// DialUDP connects to the given address via the proxy. // DialUDP connects to the given address via the proxy.
func (s *Obfs) DialUDP(network, addr string) (net.PacketConn, error) { func (s *Obfs) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
return nil, proxy.ErrNotSupported return nil, nil, proxy.ErrNotSupported
}
func init() {
proxy.AddUsage("simple-obfs", `
Simple-Obfs scheme:
simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA]
Available types for simple-obfs:
http, tls
`)
} }

View File

@ -1,4 +1,4 @@
// https://www.rfc-editor.org/rfc/rfc5246 // https://www.ietf.org/rfc/rfc5246.txt
// https://golang.org/src/crypto/tls/handshake_messages.go // https://golang.org/src/crypto/tls/handshake_messages.go
// NOTE: // NOTE:
@ -17,7 +17,7 @@ import (
"net" "net"
"time" "time"
"github.com/nadoo/glider/pkg/pool" "github.com/nadoo/glider/pool"
) )
const ( const (
@ -64,7 +64,10 @@ func (c *TLSObfsConn) Write(b []byte) (int, error) {
n := len(b) n := len(b)
for i := 0; i < n; i += chunkSize { for i := 0; i < n; i += chunkSize {
buf.Reset() buf.Reset()
end := min(i+chunkSize, n) end := i + chunkSize
if end > n {
end = n
}
buf.Write([]byte{0x17, 0x03, 0x03}) buf.Write([]byte{0x17, 0x03, 0x03})
binary.Write(buf, binary.BigEndian, uint16(len(b[i:end]))) binary.Write(buf, binary.BigEndian, uint16(len(b[i:end])))

View File

@ -1,9 +1,6 @@
package proxy package proxy
import ( import "net"
"net"
"strings"
)
// Proxy is a dialer manager. // Proxy is a dialer manager.
type Proxy interface { type Proxy interface {
@ -11,7 +8,7 @@ type Proxy interface {
Dial(network, addr string) (c net.Conn, dialer Dialer, err error) Dial(network, addr string) (c net.Conn, dialer Dialer, err error)
// DialUDP connects to the given address via the proxy. // DialUDP connects to the given address via the proxy.
DialUDP(network, addr string) (pc net.PacketConn, dialer UDPDialer, err error) DialUDP(network, addr string) (pc net.PacketConn, dialer UDPDialer, writeTo net.Addr, err error)
// Get the dialer by dstAddr. // Get the dialer by dstAddr.
NextDialer(dstAddr string) Dialer NextDialer(dstAddr string) Dialer
@ -19,28 +16,3 @@ type Proxy interface {
// Record records result while using the dialer from proxy. // Record records result while using the dialer from proxy.
Record(dialer Dialer, success bool) Record(dialer Dialer, success bool)
} }
var (
msg strings.Builder
usages = make(map[string]string)
)
// AddUsage adds help message for the named proxy.
func AddUsage(name, usage string) {
usages[name] = usage
msg.WriteString(usage)
msg.WriteString("\n--")
}
// Usage returns help message of the named proxy.
func Usage(name string) string {
if name == "all" {
return msg.String()
}
if usage, ok := usages[name]; ok {
return usage
}
return "can not find usage for: " + name
}

View File

@ -1,134 +0,0 @@
package pxyproto
import (
"errors"
"fmt"
"net"
"net/url"
"strings"
"github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/proxy"
)
func init() {
proxy.RegisterServer("pxyproto", NewPxyProtoServer)
}
// PxyProtoServer struct.
type PxyProtoServer struct {
addr string
proxy proxy.Proxy
server proxy.Server
}
// NewPxyProtoServer returns a PxyProtoServer struct.
func NewPxyProtoServer(s string, p proxy.Proxy) (proxy.Server, error) {
schemes := strings.SplitN(s, ",", 2)
u, err := url.Parse(schemes[0])
if err != nil {
log.F("[pxyproto] parse url err: %s", err)
return nil, err
}
t := &PxyProtoServer{proxy: p, addr: u.Host}
if len(schemes) < 2 {
return nil, errors.New("[pxyproto] you must use pxyproto with a proxy server, e.g: pxyproto://:1234,http://")
}
t.server, err = proxy.ServerFromURL(schemes[1], p)
if err != nil {
return nil, err
}
return t, nil
}
// ListenAndServe listens on server's addr and serves connections.
func (s *PxyProtoServer) ListenAndServe() {
l, err := net.Listen("tcp", s.addr)
if err != nil {
log.Fatalf("[pxyproto] failed to listen on %s: %v", s.addr, err)
return
}
defer l.Close()
log.F("[pxyproto] listening TCP on %s", s.addr)
for {
c, err := l.Accept()
if err != nil {
log.F("[pxyproto] failed to accept: %v", err)
continue
}
go s.Serve(c)
}
}
// Serve serves a connection.
func (s *PxyProtoServer) Serve(cc net.Conn) {
c, err := newServerConn(cc)
if err != nil {
log.F("[pxyproto] parse header failed, error: %v", err)
cc.Close()
return
}
// log.F("[pxyproto] %s <-> %s <-> %s <-> %s",
// c.RemoteAddr(), c.LocalAddr(), cc.RemoteAddr(), cc.LocalAddr())
if s.server != nil {
s.server.Serve(c)
return
}
}
type serverConn struct {
*proxy.Conn
src, dst net.Addr
}
func newServerConn(c net.Conn) (*serverConn, error) {
sc := &serverConn{
Conn: proxy.NewConn(c),
src: c.RemoteAddr(),
dst: c.LocalAddr(),
}
return sc, sc.parseHeader()
}
// "PROXY TCPx SRC_IP DST_IP SRC_PORT DST_PORT"
func (c *serverConn) parseHeader() error {
line, err := c.Conn.Reader().ReadString('\n')
if err != nil {
return err
}
line = strings.ReplaceAll(line, "\r\n", "")
// log.F("[pxyproto] req header: %s", line)
header := strings.Split(line, " ")
if len(header) != 6 {
return fmt.Errorf("invalid header: %s", line)
}
if header[0] != "PROXY" {
return fmt.Errorf("invalid header: %s", line)
}
c.src, err = net.ResolveTCPAddr("tcp", net.JoinHostPort(header[2], header[4]))
if err != nil {
return fmt.Errorf("parse header: %s, error: %v", line, err)
}
c.dst, err = net.ResolveTCPAddr("tcp", net.JoinHostPort(header[3], header[5]))
if err != nil {
return fmt.Errorf("parse header: %s, error: %v", line, err)
}
return nil
}
func (c *serverConn) LocalAddr() net.Addr { return c.dst }
func (c *serverConn) RemoteAddr() net.Addr { return c.src }

View File

@ -2,13 +2,12 @@ package redir
import ( import (
"net" "net"
"net/netip"
"net/url" "net/url"
"strings" "strings"
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
) )
@ -56,7 +55,7 @@ func NewRedir6Server(s string, p proxy.Proxy) (proxy.Server, error) {
func (s *RedirProxy) ListenAndServe() { func (s *RedirProxy) ListenAndServe() {
l, err := net.Listen("tcp", s.addr) l, err := net.Listen("tcp", s.addr)
if err != nil { if err != nil {
log.Fatalf("[redir] failed to listen on %s: %v", s.addr, err) log.F("[redir] failed to listen on %s: %v", s.addr, err)
return return
} }
@ -84,20 +83,19 @@ func (s *RedirProxy) Serve(cc net.Conn) {
} }
c.SetKeepAlive(true) c.SetKeepAlive(true)
tgtAddr, err := getOrigDst(c, s.ipv6) tgt, err := getOrigDst(c, s.ipv6)
if err != nil { if err != nil {
log.F("[redir] failed to get target address: %v", err) log.F("[redir] failed to get target address: %v", err)
return return
} }
tgt := tgtAddr.String()
// loop request // loop request
if c.LocalAddr().String() == tgt { if c.LocalAddr().String() == tgt.String() {
log.F("[redir] %s <-> %s, unallowed request to redir port", c.RemoteAddr(), tgt) log.F("[redir] %s <-> %s, unallowed request to redir port", c.RemoteAddr(), tgt)
return return
} }
rc, dialer, err := s.proxy.Dial("tcp", tgt) rc, dialer, err := s.proxy.Dial("tcp", tgt.String())
if err != nil { if err != nil {
log.F("[redir] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err) log.F("[redir] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
return return
@ -116,12 +114,12 @@ func (s *RedirProxy) Serve(cc net.Conn) {
} }
// Get the original destination of a TCP connection. // Get the original destination of a TCP connection.
func getOrigDst(c *net.TCPConn, ipv6 bool) (netip.AddrPort, error) { func getOrigDst(c *net.TCPConn, ipv6 bool) (*net.TCPAddr, error) {
rc, err := c.SyscallConn() rc, err := c.SyscallConn()
if err != nil { if err != nil {
return netip.AddrPort{}, err return nil, err
} }
var addr netip.AddrPort var addr *net.TCPAddr
rc.Control(func(fd uintptr) { rc.Control(func(fd uintptr) {
if ipv6 { if ipv6 {
addr, err = getorigdstIPv6(fd) addr, err = getorigdstIPv6(fd)
@ -133,29 +131,32 @@ func getOrigDst(c *net.TCPConn, ipv6 bool) (netip.AddrPort, error) {
} }
// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c // Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c
func getorigdst(fd uintptr) (netip.AddrPort, error) { func getorigdst(fd uintptr) (*net.TCPAddr, error) {
const _SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h const _SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h
var raw syscall.RawSockaddrInet4 var raw syscall.RawSockaddrInet4
siz := unsafe.Sizeof(raw) siz := unsafe.Sizeof(raw)
if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, _SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil { if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, _SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil {
return netip.AddrPort{}, err return nil, err
} }
// NOTE: raw.Port is big-endian, just change it to little-endian var addr net.TCPAddr
// TODO: improve here when we add big-endian $GOARCH support addr.IP = raw.Addr[:]
port := raw.Port<<8 | raw.Port>>8 port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // raw.Port is big-endian
return netip.AddrPortFrom(netip.AddrFrom4(raw.Addr), port), nil addr.Port = int(port[0])<<8 | int(port[1])
return &addr, nil
} }
// Call ipv6_getorigdst() from linux/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c // Call ipv6_getorigdst() from linux/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
func getorigdstIPv6(fd uintptr) (netip.AddrPort, error) { // NOTE: I haven't tried yet but it should work since Linux 3.8.
func getorigdstIPv6(fd uintptr) (*net.TCPAddr, error) {
const _IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h const _IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h
var raw syscall.RawSockaddrInet6 var raw syscall.RawSockaddrInet6
siz := unsafe.Sizeof(raw) siz := unsafe.Sizeof(raw)
if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IPV6, _IP6T_SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil { if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IPV6, _IP6T_SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil {
return netip.AddrPort{}, err return nil, err
} }
// NOTE: raw.Port is big-endian, just change it to little-endian var addr net.TCPAddr
// TODO: improve here when we add big-endian $GOARCH support addr.IP = raw.Addr[:]
port := raw.Port<<8 | raw.Port>>8 port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // raw.Port is big-endian
return netip.AddrPortFrom(netip.AddrFrom16(raw.Addr), port), nil addr.Port = int(port[0])<<8 | int(port[1])
return &addr, nil
} }

View File

@ -1,4 +1,4 @@
//go:build linux && !386 // +build linux,!386
package redir package redir

View File

@ -34,13 +34,6 @@ func (s *Reject) Dial(network, addr string) (net.Conn, error) {
} }
// DialUDP connects to the given address via the proxy. // DialUDP connects to the given address via the proxy.
func (s *Reject) DialUDP(network, addr string) (net.PacketConn, error) { func (s *Reject) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
return nil, errors.New("REJECT") return nil, nil, errors.New("REJECT")
}
func init() {
proxy.AddUsage("reject", `
Reject scheme:
reject://
`)
} }

View File

@ -3,7 +3,6 @@ package proxy
import ( import (
"errors" "errors"
"net" "net"
"sort"
"strings" "strings"
) )
@ -16,11 +15,6 @@ type Server interface {
Serve(c net.Conn) Serve(c net.Conn)
} }
// PacketServer interface.
type PacketServer interface {
ServePacket(pc net.PacketConn)
}
// ServerCreator is a function to create proxy servers. // ServerCreator is a function to create proxy servers.
type ServerCreator func(s string, proxy Proxy) (Server, error) type ServerCreator func(s string, proxy Proxy) (Server, error)
@ -34,9 +28,9 @@ func RegisterServer(name string, c ServerCreator) {
} }
// ServerFromURL calls the registered creator to create proxy servers. // ServerFromURL calls the registered creator to create proxy servers.
// proxy can not be nil. // dialer is the default upstream dialer so cannot be nil, we can use Default when calling this function.
func ServerFromURL(s string, proxy Proxy) (Server, error) { func ServerFromURL(s string, p Proxy) (Server, error) {
if proxy == nil { if p == nil {
return nil, errors.New("ServerFromURL: dialer cannot be nil") return nil, errors.New("ServerFromURL: dialer cannot be nil")
} }
@ -47,18 +41,8 @@ func ServerFromURL(s string, proxy Proxy) (Server, error) {
scheme := s[:strings.Index(s, ":")] scheme := s[:strings.Index(s, ":")]
c, ok := serverCreators[strings.ToLower(scheme)] c, ok := serverCreators[strings.ToLower(scheme)]
if ok { if ok {
return c(s, proxy) return c(s, p)
} }
return nil, errors.New("unknown scheme '" + scheme + "'") return nil, errors.New("unknown scheme '" + scheme + "'")
} }
// ServerSchemes returns the registered server schemes.
func ServerSchemes() string {
s := make([]string, 0, len(serverCreators))
for name := range serverCreators {
s = append(s, name)
}
sort.Strings(s)
return strings.Join(s, " ")
}

View File

@ -1,79 +0,0 @@
package smux
import (
"net"
"net/url"
"sync"
"github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/pkg/smux"
"github.com/nadoo/glider/proxy"
)
// SmuxClient struct.
type SmuxClient struct {
dialer proxy.Dialer
addr string
mu sync.Mutex
session *smux.Session
}
func init() {
proxy.RegisterDialer("smux", NewSmuxDialer)
}
// NewSmuxDialer returns a smux dialer.
func NewSmuxDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
u, err := url.Parse(s)
if err != nil {
log.F("[smux] parse url err: %s", err)
return nil, err
}
c := &SmuxClient{
dialer: d,
addr: u.Host,
}
return c, nil
}
// Addr returns forwarder's address.
func (s *SmuxClient) Addr() string {
if s.addr == "" {
return s.dialer.Addr()
}
return s.addr
}
// Dial connects to the address addr on the network net via the proxy.
func (s *SmuxClient) Dial(network, addr string) (net.Conn, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.session != nil {
if c, err := s.session.OpenStream(); err == nil {
return c, err
}
s.session.Close()
}
if err := s.initConn(); err != nil {
return nil, err
}
return s.session.OpenStream()
}
// DialUDP connects to the given address via the proxy.
func (s *SmuxClient) DialUDP(network, addr string) (net.PacketConn, error) {
return nil, proxy.ErrNotSupported
}
func (s *SmuxClient) initConn() error {
conn, err := s.dialer.Dial("tcp", s.addr)
if err != nil {
log.F("[smux] dial to %s error: %s", s.addr, err)
return err
}
s.session, err = smux.Client(conn, nil)
return err
}

View File

@ -1,119 +0,0 @@
package smux
import (
"net"
"net/url"
"strings"
"github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/pkg/smux"
"github.com/nadoo/glider/proxy"
)
// SmuxServer struct.
type SmuxServer struct {
proxy proxy.Proxy
addr string
server proxy.Server
}
func init() {
proxy.RegisterServer("smux", NewSmuxServer)
}
// NewSmuxServer returns a smux transport layer before the real server.
func NewSmuxServer(s string, p proxy.Proxy) (proxy.Server, error) {
schemes := strings.SplitN(s, ",", 2)
u, err := url.Parse(schemes[0])
if err != nil {
log.F("[smux] parse url err: %s", err)
return nil, err
}
m := &SmuxServer{
proxy: p,
addr: u.Host,
}
if len(schemes) > 1 {
m.server, err = proxy.ServerFromURL(schemes[1], p)
if err != nil {
return nil, err
}
}
return m, nil
}
// ListenAndServe listens on server's addr and serves connections.
func (s *SmuxServer) ListenAndServe() {
l, err := net.Listen("tcp", s.addr)
if err != nil {
log.Fatalf("[smux] failed to listen on %s: %v", s.addr, err)
return
}
defer l.Close()
log.F("[smux] listening mux on %s", s.addr)
for {
c, err := l.Accept()
if err != nil {
log.F("[smux] failed to accept: %v", err)
continue
}
go s.Serve(c)
}
}
// Serve serves a connection.
func (s *SmuxServer) Serve(c net.Conn) {
// we know the internal server will close the connection after serve
// defer c.Close()
session, err := smux.Server(c, nil)
if err != nil {
log.F("[smux] failed to create session: %v", err)
return
}
for {
// Accept a stream
stream, err := session.AcceptStream()
if err != nil {
session.Close()
break
}
go s.ServeStream(stream)
}
}
// ServeStream serves a smux stream.
func (s *SmuxServer) ServeStream(c *smux.Stream) {
if s.server != nil {
s.server.Serve(c)
return
}
defer c.Close()
rc, dialer, err := s.proxy.Dial("tcp", "")
if err != nil {
log.F("[smux] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), s.addr, dialer.Addr(), err)
s.proxy.Record(dialer, false)
return
}
defer rc.Close()
log.F("[smux] %s <-> %s", c.RemoteAddr(), dialer.Addr())
if err = proxy.Relay(c, rc); err != nil {
log.F("[smux] %s <-> %s, relay error: %v", c.RemoteAddr(), dialer.Addr(), err)
// record remote conn failure only
if !strings.Contains(err.Error(), s.addr) {
s.proxy.Record(dialer, false)
}
}
}

View File

@ -1,10 +0,0 @@
package smux
import "github.com/nadoo/glider/proxy"
func init() {
proxy.AddUsage("smux", `
Smux scheme:
smux://host:port
`)
}

View File

@ -4,7 +4,6 @@ import (
"errors" "errors"
"io" "io"
"net" "net"
"net/netip"
"strconv" "strconv"
) )
@ -68,12 +67,11 @@ func (a Addr) String() string {
return net.JoinHostPort(host, port) return net.JoinHostPort(host, port)
} }
// Network returns network name. Implements net.Addr interface. // ReadAddrBuf reads just enough bytes from r to get a valid Addr.
func (a Addr) Network() string { return "socks" } func ReadAddrBuf(r io.Reader, b []byte) (Addr, error) {
if len(b) < MaxAddrLen {
// ReadAddr reads just enough bytes from r to get a valid Addr. return nil, io.ErrShortBuffer
func ReadAddr(r io.Reader) (Addr, error) { }
b := make([]byte, MaxAddrLen)
_, err := io.ReadFull(r, b[:1]) // read 1st byte for address type _, err := io.ReadFull(r, b[:1]) // read 1st byte for address type
if err != nil { if err != nil {
return nil, err return nil, err
@ -98,6 +96,11 @@ func ReadAddr(r io.Reader) (Addr, error) {
return nil, Errors[8] return nil, Errors[8]
} }
// ReadAddr reads just enough bytes from r to get a valid Addr.
func ReadAddr(r io.Reader) (Addr, error) {
return ReadAddrBuf(r, make([]byte, MaxAddrLen))
}
// SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed. // SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed.
func SplitAddr(b []byte) Addr { func SplitAddr(b []byte) Addr {
addrLen := 1 addrLen := 1
@ -133,16 +136,16 @@ func ParseAddr(s string) Addr {
if err != nil { if err != nil {
return nil return nil
} }
if ip := net.ParseIP(host); ip != nil {
if ip, err := netip.ParseAddr(host); err == nil { if ip4 := ip.To4(); ip4 != nil {
if ip.Is4() {
addr = make([]byte, 1+net.IPv4len+2) addr = make([]byte, 1+net.IPv4len+2)
addr[0] = ATypIP4 addr[0] = ATypIP4
copy(addr[1:], ip4)
} else { } else {
addr = make([]byte, 1+net.IPv6len+2) addr = make([]byte, 1+net.IPv6len+2)
addr[0] = ATypIP6 addr[0] = ATypIP6
copy(addr[1:], ip)
} }
copy(addr[1:], ip.AsSlice())
} else { } else {
if len(host) > 255 { if len(host) > 255 {
return nil return nil

View File

@ -11,8 +11,8 @@ import (
"net/url" "net/url"
"strconv" "strconv"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/pool" "github.com/nadoo/glider/pool"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
) )
@ -27,12 +27,10 @@ const (
type SOCKS4 struct { type SOCKS4 struct {
dialer proxy.Dialer dialer proxy.Dialer
addr string addr string
socks4a bool
} }
func init() { func init() {
proxy.RegisterDialer("socks4", NewSocks4Dialer) proxy.RegisterDialer("socks4", NewSocks4Dialer)
proxy.RegisterDialer("socks4a", NewSocks4Dialer)
} }
// NewSOCKS4 returns a socks4 proxy. // NewSOCKS4 returns a socks4 proxy.
@ -46,7 +44,6 @@ func NewSOCKS4(s string, dialer proxy.Dialer) (*SOCKS4, error) {
h := &SOCKS4{ h := &SOCKS4{
dialer: dialer, dialer: dialer,
addr: u.Host, addr: u.Host,
socks4a: u.Scheme == "socks4a",
} }
return h, nil return h, nil
@ -88,8 +85,8 @@ func (s *SOCKS4) Dial(network, addr string) (net.Conn, error) {
} }
// DialUDP connects to the given address via the proxy. // DialUDP connects to the given address via the proxy.
func (s *SOCKS4) DialUDP(network, addr string) (pc net.PacketConn, err error) { func (s *SOCKS4) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
return nil, proxy.ErrNotSupported return nil, nil, proxy.ErrNotSupported
} }
func (s *SOCKS4) lookupIP(host string) (ip net.IP, err error) { func (s *SOCKS4) lookupIP(host string) (ip net.IP, err error) {
@ -118,46 +115,27 @@ func (s *SOCKS4) connect(conn net.Conn, target string) error {
return err return err
} }
port, err := strconv.ParseUint(portStr, 10, 16) port, err := strconv.Atoi(portStr)
if err != nil { if err != nil {
return errors.New("[socks4] failed to parse port number: " + portStr) return errors.New("[socks4] failed to parse port number: " + portStr)
} }
if port < 1 || port > 0xffff {
return errors.New("[socks4] port number out of range: " + portStr)
}
const baseBufSize = 8 + 1 // 1 is the len(userid) ip, err := s.lookupIP(host)
bufSize := baseBufSize
var ip net.IP
if ip = net.ParseIP(host); ip == nil {
if s.socks4a {
// The client should set the first three bytes of DSTIP to NULL
// and the last byte to a non-zero value.
ip = []byte{0, 0, 0, 1}
bufSize += len(host) + 1
} else {
ip, err = s.lookupIP(host)
if err != nil { if err != nil {
return err return err
} }
}
} else { // taken from https://github.com/h12w/socks/blob/master/socks.go
ip = ip.To4() buf := []byte{
if ip == nil {
return errors.New("[socks4] IPv6 is not supported by socks4")
}
}
// taken from https://github.com/h12w/socks/blob/master/socks.go and https://en.wikipedia.org/wiki/SOCKS
buf := pool.GetBuffer(bufSize)
defer pool.PutBuffer(buf)
copy(buf, []byte{
Version, Version,
ConnectCommand, ConnectCommand,
byte(port >> 8), // higher byte of destination port byte(port >> 8), // higher byte of destination port
byte(port), // lower byte of destination port (big endian) byte(port), // lower byte of destination port (big endian)
ip[0], ip[1], ip[2], ip[3], ip[0], ip[1], ip[2], ip[3],
0, // user id 0, // user id
})
if s.socks4a {
copy(buf[baseBufSize:], host)
buf[len(buf)-1] = 0
} }
resp := pool.GetBuffer(8) resp := pool.GetBuffer(8)
@ -186,10 +164,3 @@ func (s *SOCKS4) connect(conn net.Conn, target string) error {
return err return err
} }
func init() {
proxy.AddUsage("socks4", `
Socks4 scheme:
socks4://host:port
`)
}

View File

@ -4,19 +4,14 @@ import (
"errors" "errors"
"io" "io"
"net" "net"
"net/netip"
"strconv" "strconv"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/pool" "github.com/nadoo/glider/pool"
"github.com/nadoo/glider/pkg/socks"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/proxy/socks"
) )
func init() {
proxy.RegisterDialer("socks5", NewSocks5Dialer)
}
// NewSocks5Dialer returns a socks5 proxy dialer. // NewSocks5Dialer returns a socks5 proxy dialer.
func NewSocks5Dialer(s string, d proxy.Dialer) (proxy.Dialer, error) { func NewSocks5Dialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
return NewSocks5(s, d, nil) return NewSocks5(s, d, nil)
@ -32,21 +27,6 @@ func (s *Socks5) Addr() string {
// Dial connects to the address addr on the network net via the SOCKS5 proxy. // Dial connects to the address addr on the network net via the SOCKS5 proxy.
func (s *Socks5) Dial(network, addr string) (net.Conn, error) { func (s *Socks5) Dial(network, addr string) (net.Conn, error) {
c, err := s.dial(network, s.addr)
if err != nil {
log.F("[socks5]: dial to %s error: %s", s.addr, err)
return nil, err
}
if _, err := s.connect(c, addr, socks.CmdConnect); err != nil {
c.Close()
return nil, err
}
return c, nil
}
func (s *Socks5) dial(network, addr string) (net.Conn, error) {
switch network { switch network {
case "tcp", "tcp6", "tcp4": case "tcp", "tcp6", "tcp4":
default: default:
@ -59,59 +39,84 @@ func (s *Socks5) dial(network, addr string) (net.Conn, error) {
return nil, err return nil, err
} }
return c, nil if err := s.connect(c, addr); err != nil {
}
// DialUDP connects to the given address via the proxy.
func (s *Socks5) DialUDP(network, addr string) (pc net.PacketConn, err error) {
c, err := s.dial("tcp", s.addr)
if err != nil {
log.F("[socks5] dialudp dial tcp to %s error: %s", s.addr, err)
return nil, err
}
var uAddr socks.Addr
if uAddr, err = s.connect(c, addr, socks.CmdUDPAssociate); err != nil {
c.Close() c.Close()
return nil, err return nil, err
} }
return c, nil
}
// DialUDP connects to the given address via the proxy.
func (s *Socks5) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
c, err := s.dialer.Dial("tcp", s.addr)
if err != nil {
log.F("[socks5] dialudp dial tcp to %s error: %s", s.addr, err)
return nil, nil, err
}
// send VER, NMETHODS, METHODS
c.Write([]byte{Version, 1, 0})
buf := pool.GetBuffer(socks.MaxAddrLen) buf := pool.GetBuffer(socks.MaxAddrLen)
defer pool.PutBuffer(buf) defer pool.PutBuffer(buf)
uAddress := uAddr.String() // read VER METHOD
h, p, _ := net.SplitHostPort(uAddress) if _, err := io.ReadFull(c, buf[:2]); err != nil {
// if returned bind ip is unspecified return nil, nil, err
if ip, err := netip.ParseAddr(h); err == nil && ip.IsUnspecified() {
// indicate using conventional addr
h, _, _ = net.SplitHostPort(s.addr)
uAddress = net.JoinHostPort(h, p)
} }
pc, err = s.dialer.DialUDP(network, uAddress) dstAddr := socks.ParseAddr(addr)
// write VER CMD RSV ATYP DST.ADDR DST.PORT
c.Write(append([]byte{Version, socks.CmdUDPAssociate, 0}, dstAddr...))
// read VER REP RSV ATYP BND.ADDR BND.PORT
if _, err := io.ReadFull(c, buf[:3]); err != nil {
return nil, nil, err
}
rep := buf[1]
if rep != 0 {
log.F("[socks5] server reply: %d, not succeeded", rep)
return nil, nil, errors.New("server connect failed")
}
uAddr, err := socks.ReadAddrBuf(c, buf)
if err != nil { if err != nil {
log.F("[socks5] dialudp to %s error: %s", uAddress, err) return nil, nil, err
return nil, err
} }
writeTo, err := net.ResolveUDPAddr("udp", uAddress) pc, nextHop, err := s.dialer.DialUDP(network, uAddr.String())
if err != nil { if err != nil {
log.F("[socks5] resolve addr error: %s", err) log.F("[socks5] dialudp to %s error: %s", uAddr.String(), err)
return nil, err return nil, nil, err
} }
return NewPktConn(pc, writeTo, socks.ParseAddr(addr), c), err pkc := NewPktConn(pc, nextHop, dstAddr, true, c)
return pkc, nextHop, err
} }
// connect takes an existing connection to a socks5 proxy server, // connect takes an existing connection to a socks5 proxy server,
// and commands the server to extend that connection to target, // and commands the server to extend that connection to target,
// which must be a canonical address with a host and port. // which must be a canonical address with a host and port.
func (s *Socks5) connect(conn net.Conn, target string, cmd byte) (addr socks.Addr, err error) { func (s *Socks5) connect(conn net.Conn, target string) error {
// the size here is just an estimate host, portStr, err := net.SplitHostPort(target)
buf := pool.GetBuffer(socks.MaxAddrLen) if err != nil {
defer pool.PutBuffer(buf) return err
}
buf = append(buf[:0], Version) port, err := strconv.Atoi(portStr)
if err != nil {
return errors.New("proxy: failed to parse port number: " + portStr)
}
if port < 1 || port > 0xffff {
return errors.New("proxy: port number out of range: " + portStr)
}
// the size here is just an estimate
buf := make([]byte, 0, 6+len(host))
buf = append(buf, Version)
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 { if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
buf = append(buf, 2 /* num auth methods */, socks.AuthNone, socks.AuthPassword) buf = append(buf, 2 /* num auth methods */, socks.AuthNone, socks.AuthPassword)
} else { } else {
@ -119,17 +124,17 @@ func (s *Socks5) connect(conn net.Conn, target string, cmd byte) (addr socks.Add
} }
if _, err := conn.Write(buf); err != nil { if _, err := conn.Write(buf); err != nil {
return addr, errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error()) return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
} }
if _, err := io.ReadFull(conn, buf[:2]); err != nil { if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return addr, errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error()) return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
} }
if buf[0] != Version { if buf[0] != Version {
return addr, errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0]))) return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
} }
if buf[1] == 0xff { if buf[1] == 0xff {
return addr, errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication") return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
} }
if buf[1] == socks.AuthPassword { if buf[1] == socks.AuthPassword {
@ -141,29 +146,45 @@ func (s *Socks5) connect(conn net.Conn, target string, cmd byte) (addr socks.Add
buf = append(buf, s.password...) buf = append(buf, s.password...)
if _, err := conn.Write(buf); err != nil { if _, err := conn.Write(buf); err != nil {
return addr, errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
} }
if _, err := io.ReadFull(conn, buf[:2]); err != nil { if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return addr, errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
} }
if buf[1] != 0 { if buf[1] != 0 {
return addr, errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password") return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
} }
} }
buf = buf[:0] buf = buf[:0]
buf = append(buf, Version, cmd, 0 /* reserved */) buf = append(buf, Version, socks.CmdConnect, 0 /* reserved */)
buf = append(buf, socks.ParseAddr(target)...)
if ip := net.ParseIP(host); ip != nil {
if ip4 := ip.To4(); ip4 != nil {
buf = append(buf, socks.ATypIP4)
ip = ip4
} else {
buf = append(buf, socks.ATypIP6)
}
buf = append(buf, ip...)
} else {
if len(host) > 255 {
return errors.New("proxy: destination hostname too long: " + host)
}
buf = append(buf, socks.ATypDomain)
buf = append(buf, byte(len(host)))
buf = append(buf, host...)
}
buf = append(buf, byte(port>>8), byte(port))
if _, err := conn.Write(buf); err != nil { if _, err := conn.Write(buf); err != nil {
return addr, errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
} }
// read VER REP RSV if _, err := io.ReadFull(conn, buf[:4]); err != nil {
if _, err := io.ReadFull(conn, buf[:3]); err != nil { return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
return addr, errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
} }
failure := "unknown error" failure := "unknown error"
@ -172,8 +193,38 @@ func (s *Socks5) connect(conn net.Conn, target string, cmd byte) (addr socks.Add
} }
if len(failure) > 0 { if len(failure) > 0 {
return addr, errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure) return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
} }
return socks.ReadAddr(conn) bytesToDiscard := 0
switch buf[3] {
case socks.ATypIP4:
bytesToDiscard = net.IPv4len
case socks.ATypIP6:
bytesToDiscard = net.IPv6len
case socks.ATypDomain:
_, err := io.ReadFull(conn, buf[:1])
if err != nil {
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
bytesToDiscard = int(buf[0])
default:
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
}
if cap(buf) < bytesToDiscard {
buf = make([]byte, bytesToDiscard)
} else {
buf = buf[:bytesToDiscard]
}
if _, err := io.ReadFull(conn, buf); err != nil {
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
// Also need to discard the port number
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
return nil
} }

View File

@ -4,24 +4,29 @@ import (
"errors" "errors"
"net" "net"
"github.com/nadoo/glider/pkg/pool" "github.com/nadoo/glider/pool"
"github.com/nadoo/glider/pkg/socks" "github.com/nadoo/glider/proxy/socks"
) )
// PktConn . // PktConn .
type PktConn struct { type PktConn struct {
net.PacketConn net.PacketConn
writeAddr net.Addr // write to and read from addr
tgtAddr socks.Addr
tgtHeader bool
ctrlConn net.Conn // tcp control conn ctrlConn net.Conn // tcp control conn
writeTo net.Addr // write to and read from addr
target socks.Addr
} }
// NewPktConn returns a PktConn, the writeAddr must be *net.UDPAddr or *net.UnixAddr. // NewPktConn returns a PktConn.
func NewPktConn(c net.PacketConn, writeAddr net.Addr, targetAddr socks.Addr, ctrlConn net.Conn) *PktConn { func NewPktConn(c net.PacketConn, writeAddr net.Addr, tgtAddr socks.Addr, tgtHeader bool, ctrlConn net.Conn) *PktConn {
pc := &PktConn{ pc := &PktConn{
PacketConn: c, PacketConn: c,
writeTo: writeAddr, writeAddr: writeAddr,
target: targetAddr, tgtAddr: tgtAddr,
tgtHeader: tgtHeader,
ctrlConn: ctrlConn, ctrlConn: ctrlConn,
} }
@ -45,24 +50,23 @@ func NewPktConn(c net.PacketConn, writeAddr net.Addr, targetAddr socks.Addr, ctr
// ReadFrom overrides the original function from net.PacketConn. // ReadFrom overrides the original function from net.PacketConn.
func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) { func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, _, target, err := pc.readFrom(b) if !pc.tgtHeader {
return n, target, err return pc.PacketConn.ReadFrom(b)
} }
func (pc *PktConn) readFrom(b []byte) (int, net.Addr, net.Addr, error) {
buf := pool.GetBuffer(len(b)) buf := pool.GetBuffer(len(b))
defer pool.PutBuffer(buf) defer pool.PutBuffer(buf)
n, raddr, err := pc.PacketConn.ReadFrom(buf) n, raddr, err := pc.PacketConn.ReadFrom(buf)
if err != nil { if err != nil {
return n, raddr, nil, err return n, raddr, err
} }
if n < 3 { if n < 3 {
return n, raddr, nil, errors.New("not enough size to get addr") return n, raddr, errors.New("not enough size to get addr")
} }
// https://www.rfc-editor.org/rfc/rfc1928#section-7 // https://tools.ietf.org/html/rfc1928#section-7
// +----+------+------+----------+----------+----------+ // +----+------+------+----------+----------+----------+
// |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
// +----+------+------+----------+----------+----------+ // +----+------+------+----------+----------+----------+
@ -70,46 +74,38 @@ func (pc *PktConn) readFrom(b []byte) (int, net.Addr, net.Addr, error) {
// +----+------+------+----------+----------+----------+ // +----+------+------+----------+----------+----------+
tgtAddr := socks.SplitAddr(buf[3:n]) tgtAddr := socks.SplitAddr(buf[3:n])
if tgtAddr == nil { if tgtAddr == nil {
return n, raddr, nil, errors.New("can not get target addr") return n, raddr, errors.New("can not get addr")
}
target, err := net.ResolveUDPAddr("udp", tgtAddr.String())
if err != nil {
return n, raddr, nil, errors.New("wrong target addr")
}
if pc.writeTo == nil {
pc.writeTo = raddr
}
if pc.target == nil {
pc.target = make([]byte, len(tgtAddr))
copy(pc.target, tgtAddr)
} }
n = copy(b, buf[3+len(tgtAddr):n]) n = copy(b, buf[3+len(tgtAddr):n])
return n, raddr, target, err
//test
if pc.writeAddr == nil {
pc.writeAddr = raddr
}
if pc.tgtAddr == nil {
pc.tgtAddr = make([]byte, len(tgtAddr))
copy(pc.tgtAddr, tgtAddr)
}
return n, raddr, err
} }
// WriteTo overrides the original function from net.PacketConn. // WriteTo overrides the original function from net.PacketConn.
func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) { func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) {
target := pc.target if !pc.tgtHeader {
if addr != nil { return pc.PacketConn.WriteTo(b, addr)
target = socks.ParseAddr(addr.String())
}
if target == nil {
return 0, errors.New("invalid addr")
} }
buf := pool.GetBytesBuffer() buf := pool.GetBytesBuffer()
defer pool.PutBytesBuffer(buf) defer pool.PutBytesBuffer(buf)
buf.Write([]byte{0, 0, 0}) buf.Write([]byte{0, 0, 0})
tgtLen, _ := buf.Write(target) tgtLen, _ := buf.Write(pc.tgtAddr)
buf.Write(b) buf.Write(b)
n, err := pc.PacketConn.WriteTo(buf.Bytes(), pc.writeTo) n, err := pc.PacketConn.WriteTo(buf.Bytes(), pc.writeAddr)
if n > tgtLen+3 { if n > tgtLen+3 {
return n - tgtLen - 3, err return n - tgtLen - 3, err
} }

View File

@ -8,18 +8,12 @@ import (
"sync" "sync"
"time" "time"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/pool" "github.com/nadoo/glider/pool"
"github.com/nadoo/glider/pkg/socks"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/proxy/socks"
) )
var nm sync.Map
func init() {
proxy.RegisterServer("socks5", NewSocks5Server)
}
// NewSocks5Server returns a socks5 proxy server. // NewSocks5Server returns a socks5 proxy server.
func NewSocks5Server(s string, p proxy.Proxy) (proxy.Server, error) { func NewSocks5Server(s string, p proxy.Proxy) (proxy.Server, error) {
return NewSocks5(s, nil, p) return NewSocks5(s, nil, p)
@ -35,7 +29,7 @@ func (s *Socks5) ListenAndServe() {
func (s *Socks5) ListenAndServeTCP() { func (s *Socks5) ListenAndServeTCP() {
l, err := net.Listen("tcp", s.addr) l, err := net.Listen("tcp", s.addr)
if err != nil { if err != nil {
log.Fatalf("[socks5] failed to listen on %s: %v", s.addr, err) log.F("[socks5] failed to listen on %s: %v", s.addr, err)
return return
} }
@ -103,119 +97,87 @@ func (s *Socks5) Serve(c net.Conn) {
func (s *Socks5) ListenAndServeUDP() { func (s *Socks5) ListenAndServeUDP() {
lc, err := net.ListenPacket("udp", s.addr) lc, err := net.ListenPacket("udp", s.addr)
if err != nil { if err != nil {
log.Fatalf("[socks5] failed to listen on UDP %s: %v", s.addr, err) log.F("[socks5] failed to listen on UDP %s: %v", s.addr, err)
return return
} }
defer lc.Close() defer lc.Close()
log.F("[socks5] listening UDP on %s", s.addr) log.F("[socks5] listening UDP on %s", s.addr)
s.ServePacket(lc) var nm sync.Map
} buf := make([]byte, proxy.UDPBufSize)
// ServePacket implements proxy.PacketServer.
func (s *Socks5) ServePacket(pc net.PacketConn) {
for { for {
c := NewPktConn(pc, nil, nil, nil) c := NewPktConn(lc, nil, nil, true, nil)
buf := pool.GetBuffer(proxy.UDPBufSize)
n, srcAddr, dstAddr, err := c.readFrom(buf) n, raddr, err := c.ReadFrom(buf)
if err != nil { if err != nil {
log.F("[socks5u] remote read error: %v", err) log.F("[socks5u] remote read error: %v", err)
continue continue
} }
var session *Session var pc *PktConn
sessionKey := srcAddr.String() v, ok := nm.Load(raddr.String())
if !ok && v == nil {
v, ok := nm.Load(sessionKey) if c.tgtAddr == nil {
if !ok || v == nil { log.F("[socks5u] can not get target address, not a valid request")
session = newSession(sessionKey, srcAddr, dstAddr, c) continue
nm.Store(sessionKey, session)
go s.serveSession(session)
} else {
session = v.(*Session)
} }
session.msgCh <- message{dstAddr, buf[:n]} lpc, dialer, nextHop, err := s.proxy.DialUDP("udp", c.tgtAddr.String())
}
}
func (s *Socks5) serveSession(session *Session) {
dstPC, dialer, err := s.proxy.DialUDP("udp", session.srcPC.target.String())
if err != nil { if err != nil {
log.F("[socks5u] remote dial error: %v", err) log.F("[socks5u] remote dial error: %v", err)
nm.Delete(session.key) continue
return
} }
defer dstPC.Close()
pc = NewPktConn(lpc, nextHop, nil, false, nil)
nm.Store(raddr.String(), pc)
go func() { go func() {
proxy.CopyUDP(session.srcPC, nil, dstPC, 2*time.Minute, 5*time.Second) proxy.RelayUDP(c, raddr, pc, 2*time.Minute)
nm.Delete(session.key) pc.Close()
close(session.finCh) nm.Delete(raddr.String())
}() }()
log.F("[socks5u] %s <-> %s via %s", session.src, session.srcPC.target, dialer.Addr()) log.F("[socks5u] %s <-> %s via %s", raddr, c.tgtAddr, dialer.Addr())
for { } else {
select { pc = v.(*PktConn)
case msg := <-session.msgCh: }
_, err = dstPC.WriteTo(msg.msg, msg.dst)
_, err = pc.WriteTo(buf[:n], pc.writeAddr)
if err != nil { if err != nil {
log.F("[socks5u] writeTo %s error: %v", msg.dst, err) log.F("[socks5u] remote write error: %v", err)
} continue
pool.PutBuffer(msg.msg)
msg.msg = nil
case <-session.finCh:
return
}
}
} }
type message struct { // log.F("[socks5u] %s <-> %s", raddr, c.tgtAddr)
dst net.Addr
msg []byte
} }
// Session is a udp session
type Session struct {
key string
src net.Addr
dst net.Addr
srcPC *PktConn
msgCh chan message
finCh chan struct{}
}
func newSession(key string, src, dst net.Addr, srcPC *PktConn) *Session {
return &Session{key, src, dst, srcPC, make(chan message, 32), make(chan struct{})}
} }
// Handshake fast-tracks SOCKS initialization to get target address to connect. // Handshake fast-tracks SOCKS initialization to get target address to connect.
func (s *Socks5) handshake(c net.Conn) (socks.Addr, error) { func (s *Socks5) handshake(rw io.ReadWriter) (socks.Addr, error) {
// Read RFC 1928 for request and reply structure and sizes // Read RFC 1928 for request and reply structure and sizes
buf := pool.GetBuffer(socks.MaxAddrLen) buf := make([]byte, socks.MaxAddrLen)
defer pool.PutBuffer(buf)
// read VER, NMETHODS, METHODS // read VER, NMETHODS, METHODS
if _, err := io.ReadFull(c, buf[:2]); err != nil { if _, err := io.ReadFull(rw, buf[:2]); err != nil {
return nil, err return nil, err
} }
nmethods := buf[1] nmethods := buf[1]
if _, err := io.ReadFull(c, buf[:nmethods]); err != nil { if _, err := io.ReadFull(rw, buf[:nmethods]); err != nil {
return nil, err return nil, err
} }
// write VER METHOD // write VER METHOD
if s.user != "" && s.password != "" { if s.user != "" && s.password != "" {
_, err := c.Write([]byte{Version, socks.AuthPassword}) _, err := rw.Write([]byte{Version, socks.AuthPassword})
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = io.ReadFull(c, buf[:2]) _, err = io.ReadFull(rw, buf[:2])
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -223,28 +185,28 @@ func (s *Socks5) handshake(c net.Conn) (socks.Addr, error) {
// Get username // Get username
userLen := int(buf[1]) userLen := int(buf[1])
if userLen <= 0 { if userLen <= 0 {
c.Write([]byte{1, 1}) rw.Write([]byte{1, 1})
return nil, errors.New("auth failed: wrong username length") return nil, errors.New("auth failed: wrong username length")
} }
if _, err := io.ReadFull(c, buf[:userLen]); err != nil { if _, err := io.ReadFull(rw, buf[:userLen]); err != nil {
return nil, errors.New("auth failed: cannot get username") return nil, errors.New("auth failed: cannot get username")
} }
user := string(buf[:userLen]) user := string(buf[:userLen])
// Get password // Get password
_, err = c.Read(buf[:1]) _, err = rw.Read(buf[:1])
if err != nil { if err != nil {
return nil, errors.New("auth failed: cannot get password len") return nil, errors.New("auth failed: cannot get password len")
} }
passLen := int(buf[0]) passLen := int(buf[0])
if passLen <= 0 { if passLen <= 0 {
c.Write([]byte{1, 1}) rw.Write([]byte{1, 1})
return nil, errors.New("auth failed: wrong password length") return nil, errors.New("auth failed: wrong password length")
} }
_, err = io.ReadFull(c, buf[:passLen]) _, err = io.ReadFull(rw, buf[:passLen])
if err != nil { if err != nil {
return nil, errors.New("auth failed: cannot get password") return nil, errors.New("auth failed: cannot get password")
} }
@ -252,7 +214,7 @@ func (s *Socks5) handshake(c net.Conn) (socks.Addr, error) {
// Verify // Verify
if user != s.user || pass != s.password { if user != s.user || pass != s.password {
_, err = c.Write([]byte{1, 1}) _, err = rw.Write([]byte{1, 1})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -260,33 +222,30 @@ func (s *Socks5) handshake(c net.Conn) (socks.Addr, error) {
} }
// Response auth state // Response auth state
_, err = c.Write([]byte{1, 0}) _, err = rw.Write([]byte{1, 0})
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else if _, err := c.Write([]byte{Version, socks.AuthNone}); err != nil { } else if _, err := rw.Write([]byte{Version, socks.AuthNone}); err != nil {
return nil, err return nil, err
} }
// read VER CMD RSV ATYP DST.ADDR DST.PORT // read VER CMD RSV ATYP DST.ADDR DST.PORT
if _, err := io.ReadFull(c, buf[:3]); err != nil { if _, err := io.ReadFull(rw, buf[:3]); err != nil {
return nil, err return nil, err
} }
cmd := buf[1] cmd := buf[1]
addr, err := socks.ReadAddr(c) addr, err := socks.ReadAddrBuf(rw, buf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
switch cmd { switch cmd {
case socks.CmdConnect: case socks.CmdConnect:
_, err = c.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) // SOCKS v5, reply succeeded _, err = rw.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) // SOCKS v5, reply succeeded
case socks.CmdUDPAssociate: case socks.CmdUDPAssociate:
listenAddr := socks.ParseAddr(c.LocalAddr().String()) listenAddr := socks.ParseAddr(rw.(net.Conn).LocalAddr().String())
if listenAddr == nil { // maybe it's unix socket _, err = rw.Write(append([]byte{5, 0, 0}, listenAddr...)) // SOCKS v5, reply succeeded
listenAddr = socks.ParseAddr("127.0.0.1:0")
}
_, err = c.Write(append([]byte{5, 0, 0}, listenAddr...)) // SOCKS v5, reply succeeded
if err != nil { if err != nil {
return nil, socks.Errors[7] return nil, socks.Errors[7]
} }

View File

@ -1,4 +1,4 @@
// https://www.rfc-editor.org/rfc/rfc1928 // https://tools.ietf.org/html/rfc1928
// socks5 client: // socks5 client:
// https://github.com/golang/net/tree/master/proxy // https://github.com/golang/net/tree/master/proxy
@ -12,7 +12,7 @@ package socks5
import ( import (
"net/url" "net/url"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
) )
@ -28,6 +28,11 @@ type Socks5 struct {
password string password string
} }
func init() {
proxy.RegisterDialer("socks5", NewSocks5Dialer)
proxy.RegisterServer("socks5", NewSocks5Server)
}
// NewSocks5 returns a Proxy that makes SOCKS v5 connections to the given address. // NewSocks5 returns a Proxy that makes SOCKS v5 connections to the given address.
// with an optional username and password. (RFC 1928) // with an optional username and password. (RFC 1928)
func NewSocks5(s string, d proxy.Dialer, p proxy.Proxy) (*Socks5, error) { func NewSocks5(s string, d proxy.Dialer, p proxy.Proxy) (*Socks5, error) {
@ -51,10 +56,3 @@ func NewSocks5(s string, d proxy.Dialer, p proxy.Proxy) (*Socks5, error) {
return h, nil return h, nil
} }
func init() {
proxy.AddUsage("socks5", `
Socks5 scheme:
socks5://[user:pass@]host:port
`)
}

View File

@ -6,8 +6,8 @@ import (
"net" "net"
"strings" "strings"
"github.com/nadoo/glider/proxy/ss/cipher/shadowaead" "github.com/nadoo/glider/proxy/ss/cipher/internal/shadowaead"
"github.com/nadoo/glider/proxy/ss/cipher/shadowstream" "github.com/nadoo/glider/proxy/ss/cipher/internal/shadowstream"
) )
// Cipher interface. // Cipher interface.

View File

@ -10,7 +10,7 @@ import (
"encoding/binary" "encoding/binary"
"io" "io"
"github.com/nadoo/glider/pkg/pool" "github.com/nadoo/glider/pool"
) )
const ( const (
@ -25,6 +25,7 @@ type writer struct {
io.Writer io.Writer
cipher.AEAD cipher.AEAD
nonce [32]byte nonce [32]byte
buf []byte
} }
// NewWriter wraps an io.Writer with AEAD encryption. // NewWriter wraps an io.Writer with AEAD encryption.

View File

@ -4,7 +4,7 @@ import (
"crypto/cipher" "crypto/cipher"
"io" "io"
"github.com/nadoo/glider/pkg/pool" "github.com/nadoo/glider/pool"
) )
const bufSize = 32 * 1024 const bufSize = 32 * 1024

View File

@ -4,9 +4,9 @@ import (
"errors" "errors"
"net" "net"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/socks"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/proxy/socks"
) )
// NewSSDialer returns a ss proxy dialer. // NewSSDialer returns a ss proxy dialer.
@ -45,19 +45,13 @@ func (s *SS) Dial(network, addr string) (net.Conn, error) {
} }
// DialUDP connects to the given address via the proxy. // DialUDP connects to the given address via the proxy.
func (s *SS) DialUDP(network, addr string) (net.PacketConn, error) { func (s *SS) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
pc, err := s.dialer.DialUDP(network, s.addr) pc, nextHop, err := s.dialer.DialUDP(network, s.addr)
if err != nil { if err != nil {
log.F("[ss] dialudp to %s error: %s", s.addr, err) log.F("[ss] dialudp to %s error: %s", s.addr, err)
return nil, err return nil, nil, err
} }
writeTo, err := net.ResolveUDPAddr("udp", s.addr) pkc := NewPktConn(s.PacketConn(pc), nextHop, socks.ParseAddr(addr), true)
if err != nil { return pkc, nextHop, err
log.F("[ss] resolve addr error: %s", err)
return nil, err
}
pkc := NewPktConn(s.PacketConn(pc), writeTo, socks.ParseAddr(addr))
return pkc, nil
} }

View File

@ -4,78 +4,77 @@ import (
"errors" "errors"
"net" "net"
"github.com/nadoo/glider/pkg/pool" "github.com/nadoo/glider/pool"
"github.com/nadoo/glider/pkg/socks" "github.com/nadoo/glider/proxy/socks"
) )
// PktConn . // PktConn .
type PktConn struct { type PktConn struct {
net.PacketConn net.PacketConn
writeTo net.Addr
target socks.Addr // if target is not nil, it may be a tunnel writeAddr net.Addr // write to and read from addr
tgtAddr socks.Addr
tgtHeader bool
} }
// NewPktConn returns a PktConn, the writeAddr must be *net.UDPAddr or *net.UnixAddr. // NewPktConn returns a PktConn
func NewPktConn(c net.PacketConn, writeAddr net.Addr, targetAddr socks.Addr) *PktConn { func NewPktConn(c net.PacketConn, writeAddr net.Addr, tgtAddr socks.Addr, tgtHeader bool) *PktConn {
return &PktConn{PacketConn: c, writeTo: writeAddr, target: targetAddr} pc := &PktConn{
PacketConn: c,
writeAddr: writeAddr,
tgtAddr: tgtAddr,
tgtHeader: tgtHeader}
return pc
} }
// ReadFrom overrides the original function from net.PacketConn. // ReadFrom overrides the original function from net.PacketConn
func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) { func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, _, target, err := pc.readFrom(b) if !pc.tgtHeader {
return n, target, err return pc.PacketConn.ReadFrom(b)
} }
func (pc *PktConn) readFrom(b []byte) (int, net.Addr, net.Addr, error) {
buf := pool.GetBuffer(len(b)) buf := pool.GetBuffer(len(b))
defer pool.PutBuffer(buf) defer pool.PutBuffer(buf)
n, raddr, err := pc.PacketConn.ReadFrom(buf) n, raddr, err := pc.PacketConn.ReadFrom(buf)
if err != nil { if err != nil {
return n, raddr, nil, err return n, raddr, err
} }
tgtAddr := socks.SplitAddr(buf[:n]) tgtAddr := socks.SplitAddr(buf[:n])
if tgtAddr == nil { if tgtAddr == nil {
return n, raddr, nil, errors.New("can not get target addr") return n, raddr, errors.New("can not get addr")
}
target, err := net.ResolveUDPAddr("udp", tgtAddr.String())
if err != nil {
return n, raddr, nil, errors.New("wrong target addr")
}
if pc.writeTo == nil {
pc.writeTo = raddr
}
if pc.target == nil {
pc.target = make([]byte, len(tgtAddr))
copy(pc.target, tgtAddr)
} }
n = copy(b, buf[len(tgtAddr):n]) n = copy(b, buf[len(tgtAddr):n])
return n, raddr, target, err
//test
if pc.writeAddr == nil {
pc.writeAddr = raddr
}
if pc.tgtAddr == nil {
pc.tgtAddr = make([]byte, len(tgtAddr))
copy(pc.tgtAddr, tgtAddr)
}
return n, raddr, err
} }
// WriteTo overrides the original function from net.PacketConn // WriteTo overrides the original function from net.PacketConn
func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) { func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) {
target := pc.target if !pc.tgtHeader {
if addr != nil { return pc.PacketConn.WriteTo(b, addr)
target = socks.ParseAddr(addr.String())
}
if target == nil {
return 0, errors.New("invalid addr")
} }
buf := pool.GetBytesBuffer() buf := pool.GetBytesBuffer()
defer pool.PutBytesBuffer(buf) defer pool.PutBytesBuffer(buf)
tgtLen, _ := buf.Write(target) tgtLen, _ := buf.Write(pc.tgtAddr)
buf.Write(b) buf.Write(b)
n, err := pc.PacketConn.WriteTo(buf.Bytes(), pc.writeTo) n, err := pc.PacketConn.WriteTo(buf.Bytes(), pc.writeAddr)
if n > tgtLen { if n > tgtLen {
return n - tgtLen, err return n - tgtLen, err
} }

View File

@ -1,20 +1,16 @@
package ss package ss
import ( import (
"io"
"net" "net"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/pool"
"github.com/nadoo/glider/pkg/socks"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/proxy/socks"
) )
var nm sync.Map
// NewSSServer returns a ss proxy server. // NewSSServer returns a ss proxy server.
func NewSSServer(s string, p proxy.Proxy) (proxy.Server, error) { func NewSSServer(s string, p proxy.Proxy) (proxy.Server, error) {
return NewSS(s, nil, p) return NewSS(s, nil, p)
@ -30,7 +26,7 @@ func (s *SS) ListenAndServe() {
func (s *SS) ListenAndServeTCP() { func (s *SS) ListenAndServeTCP() {
l, err := net.Listen("tcp", s.addr) l, err := net.Listen("tcp", s.addr)
if err != nil { if err != nil {
log.Fatalf("[ss] failed to listen on %s: %v", s.addr, err) log.F("[ss] failed to listen on %s: %v", s.addr, err)
return return
} }
@ -55,17 +51,18 @@ func (s *SS) Serve(c net.Conn) {
c.SetKeepAlive(true) c.SetKeepAlive(true)
} }
sc := s.StreamConn(c) c = s.StreamConn(c)
tgt, err := socks.ReadAddr(sc) tgt, err := socks.ReadAddr(c)
if err != nil { if err != nil {
log.F("[ss] %s <-> target error: %v", c.RemoteAddr(), err) log.F("[ss] failed to get target address: %v", err)
proxy.Copy(io.Discard, c) // https://github.com/nadoo/glider/issues/180
return return
} }
network := "tcp"
dialer := s.proxy.NextDialer(tgt.String()) dialer := s.proxy.NextDialer(tgt.String())
rc, err := dialer.Dial("tcp", tgt.String())
rc, err := dialer.Dial(network, tgt.String())
if err != nil { if err != nil {
log.F("[ss] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err) log.F("[ss] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
return return
@ -74,7 +71,7 @@ func (s *SS) Serve(c net.Conn) {
log.F("[ss] %s <-> %s via %s", c.RemoteAddr(), tgt, dialer.Addr()) log.F("[ss] %s <-> %s via %s", c.RemoteAddr(), tgt, dialer.Addr())
if err = proxy.Relay(sc, rc); err != nil { if err = proxy.Relay(c, rc); err != nil {
log.F("[ss] %s <-> %s via %s, relay error: %v", c.RemoteAddr(), tgt, dialer.Addr(), err) log.F("[ss] %s <-> %s via %s, relay error: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
// record remote conn failure only // record remote conn failure only
if !strings.Contains(err.Error(), s.addr) { if !strings.Contains(err.Error(), s.addr) {
@ -83,96 +80,61 @@ func (s *SS) Serve(c net.Conn) {
} }
} }
// ListenAndServeUDP serves udp requests. // ListenAndServeUDP serves udp ss requests.
func (s *SS) ListenAndServeUDP() { func (s *SS) ListenAndServeUDP() {
lc, err := net.ListenPacket("udp", s.addr) lc, err := net.ListenPacket("udp", s.addr)
if err != nil { if err != nil {
log.Fatalf("[ss] failed to listen on UDP %s: %v", s.addr, err) log.F("[ss] failed to listen on UDP %s: %v", s.addr, err)
return return
} }
defer lc.Close() defer lc.Close()
lc = s.PacketConn(lc)
log.F("[ss] listening UDP on %s", s.addr) log.F("[ss] listening UDP on %s", s.addr)
s.ServePacket(lc) var nm sync.Map
} buf := make([]byte, proxy.UDPBufSize)
// ServePacket implements proxy.PacketServer.
func (s *SS) ServePacket(pc net.PacketConn) {
lc := s.PacketConn(pc)
for { for {
c := NewPktConn(lc, nil, nil) c := NewPktConn(lc, nil, nil, true)
buf := pool.GetBuffer(proxy.UDPBufSize)
n, srcAddr, dstAddr, err := c.readFrom(buf) n, raddr, err := c.ReadFrom(buf)
if err != nil { if err != nil {
log.F("[ssu] remote read error: %v", err) log.F("[ssu] remote read error: %v", err)
continue continue
} }
var session *Session var pc *PktConn
sessionKey := srcAddr.String() v, ok := nm.Load(raddr.String())
if !ok && v == nil {
v, ok := nm.Load(sessionKey) lpc, dialer, nextHop, err := s.proxy.DialUDP("udp", c.tgtAddr.String())
if !ok || v == nil {
session = newSession(sessionKey, srcAddr, dstAddr, c)
nm.Store(sessionKey, session)
go s.serveSession(session)
} else {
session = v.(*Session)
}
session.msgCh <- message{dstAddr, buf[:n]}
}
}
func (s *SS) serveSession(session *Session) {
dstPC, dialer, err := s.proxy.DialUDP("udp", session.dst.String())
if err != nil { if err != nil {
log.F("[ssu] remote dial error: %v", err) log.F("[ssu] remote dial error: %v", err)
nm.Delete(session.key) continue
return
} }
defer dstPC.Close()
pc = NewPktConn(lpc, nextHop, nil, false)
nm.Store(raddr.String(), pc)
go func() { go func() {
proxy.CopyUDP(session.srcPC, nil, dstPC, 2*time.Minute, 5*time.Second) proxy.RelayUDP(c, raddr, pc, 2*time.Minute)
nm.Delete(session.key) pc.Close()
close(session.finCh) nm.Delete(raddr.String())
}() }()
log.F("[ssu] %s <-> %s via %s", session.src, session.dst, dialer.Addr()) log.F("[ssu] %s <-> %s via %s", raddr, c.tgtAddr, dialer.Addr())
for { } else {
select { pc = v.(*PktConn)
case msg := <-session.msgCh: }
_, err = dstPC.WriteTo(msg.msg, msg.dst)
_, err = pc.WriteTo(buf[:n], pc.writeAddr)
if err != nil { if err != nil {
log.F("[ssu] writeTo %s error: %v", msg.dst, err) log.F("[ssu] remote write error: %v", err)
} continue
pool.PutBuffer(msg.msg)
msg.msg = nil
case <-session.finCh:
return
}
}
} }
type message struct { // log.F("[ssu] %s <-> %s", raddr, c.tgtAddr)
dst net.Addr
msg []byte
} }
// Session is a udp session
type Session struct {
key string
src net.Addr
dst net.Addr
srcPC *PktConn
msgCh chan message
finCh chan struct{}
}
func newSession(key string, src, dst net.Addr, srcPC *PktConn) *Session {
return &Session{key, src, dst, srcPC, make(chan message, 32), make(chan struct{})}
} }

View File

@ -3,7 +3,7 @@ package ss
import ( import (
"net/url" "net/url"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/proxy/ss/cipher" "github.com/nadoo/glider/proxy/ss/cipher"
) )
@ -48,19 +48,3 @@ func NewSS(s string, d proxy.Dialer, p proxy.Proxy) (*SS, error) {
return ss, nil return ss, nil
} }
func init() {
proxy.AddUsage("ss", `
SS scheme:
ss://method:pass@host:port
Available methods for ss:
AEAD Ciphers:
AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AEAD_XCHACHA20_POLY1305
Stream Ciphers:
AES-128-CFB AES-128-CTR AES-192-CFB AES-192-CTR AES-256-CFB AES-256-CTR CHACHA20-IETF XCHACHA20 CHACHA20 RC4-MD5
Alias:
chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305
Plain: NONE
`)
}

View File

@ -1,17 +1,14 @@
package ssh package ssh
import ( import (
"errors" "io/ioutil"
"net" "net"
"net/url" "net/url"
"os"
"strconv"
"sync"
"time" "time"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
) )
@ -20,13 +17,7 @@ type SSH struct {
dialer proxy.Dialer dialer proxy.Dialer
proxy proxy.Proxy proxy proxy.Proxy
addr string addr string
conn net.Conn
client *ssh.Client
config *ssh.ClientConfig config *ssh.ClientConfig
once sync.Once
mutex sync.RWMutex
} }
func init() { func init() {
@ -37,7 +28,7 @@ func init() {
func NewSSH(s string, d proxy.Dialer, p proxy.Proxy) (*SSH, error) { func NewSSH(s string, d proxy.Dialer, p proxy.Proxy) (*SSH, error) {
u, err := url.Parse(s) u, err := url.Parse(s)
if err != nil { if err != nil {
log.F("[ssh] parse err: %s", err) log.F("parse err: %s", err)
return nil, err return nil, err
} }
@ -48,15 +39,17 @@ func NewSSH(s string, d proxy.Dialer, p proxy.Proxy) (*SSH, error) {
config := &ssh.ClientConfig{ config := &ssh.ClientConfig{
User: user, User: user,
HostKeyCallback: ssh.InsecureIgnoreHostKey(), Timeout: time.Second * 3,
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
} }
if pass, _ := u.User.Password(); pass != "" { if pass, _ := u.User.Password(); pass != "" {
config.Auth = []ssh.AuthMethod{ssh.Password(pass)} config.Auth = []ssh.AuthMethod{ssh.Password(pass)}
} }
query := u.Query() if key := u.Query().Get("key"); key != "" {
if key := query.Get("key"); key != "" {
keyAuth, err := privateKeyAuth(key) keyAuth, err := privateKeyAuth(key)
if err != nil { if err != nil {
log.F("[ssh] read key file error: %s", err) log.F("[ssh] read key file error: %s", err)
@ -65,30 +58,14 @@ func NewSSH(s string, d proxy.Dialer, p proxy.Proxy) (*SSH, error) {
config.Auth = append(config.Auth, keyAuth) config.Auth = append(config.Auth, keyAuth)
} }
// timeout of ssh handshake and channel operation ssh := &SSH{
qtimeout := query.Get("timeout")
if qtimeout == "" {
qtimeout = "5" // default timeout
}
timeout, err := strconv.ParseUint(qtimeout, 10, 32)
if err != nil {
log.F("[ssh] parse timeout err: %s", err)
return nil, err
}
config.Timeout = time.Second * time.Duration(timeout)
t := &SSH{
dialer: d, dialer: d,
proxy: p, proxy: p,
addr: u.Host, addr: u.Host,
config: config, config: config,
} }
if _, port, _ := net.SplitHostPort(t.addr); port == "" { return ssh, nil
t.addr = net.JoinHostPort(t.addr, "22")
}
return t, nil
} }
// NewSSHDialer returns a ssh proxy dialer. // NewSSHDialer returns a ssh proxy dialer.
@ -106,78 +83,28 @@ func (s *SSH) Addr() string {
// Dial connects to the address addr on the network net via the proxy. // Dial connects to the address addr on the network net via the proxy.
func (s *SSH) Dial(network, addr string) (net.Conn, error) { func (s *SSH) Dial(network, addr string) (net.Conn, error) {
s.once.Do(func() { go s.keepConn(s.initConn() == nil) }) c, err := s.dialer.Dial(network, s.addr)
s.mutex.RLock()
defer s.mutex.RUnlock()
if s.client == nil {
return nil, errors.New("ssh client is nil")
}
return s.dial(network, addr)
}
func (s *SSH) dial(network, addr string) (net.Conn, error) {
s.conn.SetDeadline(time.Now().Add(s.config.Timeout))
c, err := s.client.Dial(network, addr)
s.conn.SetDeadline(time.Time{})
return c, err
}
func (s *SSH) initConn() error {
s.mutex.Lock()
defer s.mutex.Unlock()
log.F("[ssh] connecting to %s", s.addr)
c, err := s.dialer.Dial("tcp", s.addr)
if err != nil { if err != nil {
log.F("[ssh] dial connection to %s error: %s", s.addr, err) log.F("[ssh]: dial to %s error: %s", s.addr, err)
return err return nil, err
} }
c.SetDeadline(time.Now().Add(s.config.Timeout)) sshc, ch, req, err := ssh.NewClientConn(c, s.addr, s.config)
conn, ch, req, err := ssh.NewClientConn(c, s.addr, s.config)
if err != nil { if err != nil {
log.F("[ssh] initial connection to %s error: %s", s.addr, err) log.F("[ssh]: initial connection to %s error: %s", s.addr, err)
c.Close() return nil, err
return err
}
c.SetDeadline(time.Time{})
s.conn = c
s.client = ssh.NewClient(conn, ch, req)
return nil
} }
func (s *SSH) keepConn(connected bool) { return ssh.NewClient(sshc, ch, req).Dial(network, addr)
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. // DialUDP connects to the given address via the proxy.
func (s *SSH) DialUDP(network, addr string) (pc net.PacketConn, err error) { func (s *SSH) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
return nil, proxy.ErrNotSupported return nil, nil, proxy.ErrNotSupported
} }
func privateKeyAuth(file string) (ssh.AuthMethod, error) { func privateKeyAuth(file string) (ssh.AuthMethod, error) {
buffer, err := os.ReadFile(file) buffer, err := ioutil.ReadFile(file)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -189,11 +116,3 @@ func privateKeyAuth(file string) (ssh.AuthMethod, error) {
return ssh.PublicKeys(key), nil return ssh.PublicKeys(key), nil
} }
func init() {
proxy.AddUsage("ssh", `
SSH scheme:
ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS]
timeout: timeout of ssh handshake and channel operation, default: 5
`)
}

View File

@ -5,10 +5,10 @@ import (
"crypto/cipher" "crypto/cipher"
"crypto/des" "crypto/des"
"crypto/md5" "crypto/md5"
"crypto/rand"
"crypto/rc4" "crypto/rc4"
"encoding/binary" "encoding/binary"
"errors" "errors"
"math/rand"
"github.com/aead/chacha20" "github.com/aead/chacha20"
"github.com/dgryski/go-camellia" "github.com/dgryski/go-camellia"
@ -18,7 +18,7 @@ import (
"golang.org/x/crypto/cast5" "golang.org/x/crypto/cast5"
"golang.org/x/crypto/salsa20/salsa" "golang.org/x/crypto/salsa20/salsa"
"github.com/nadoo/glider/pkg/pool" "github.com/nadoo/glider/pool"
"github.com/nadoo/glider/proxy/ssr/internal/tools" "github.com/nadoo/glider/proxy/ssr/internal/tools"
) )

View File

@ -8,16 +8,22 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"math/rand"
"net" "net"
"time"
"github.com/nadoo/glider/pkg/pool" "github.com/nadoo/glider/pool"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/proxy/ssr/internal/cipher" "github.com/nadoo/glider/proxy/ssr/internal/cipher"
"github.com/nadoo/glider/proxy/ssr/internal/obfs" "github.com/nadoo/glider/proxy/ssr/internal/obfs"
"github.com/nadoo/glider/proxy/ssr/internal/protocol" "github.com/nadoo/glider/proxy/ssr/internal/protocol"
) )
var bufSize = proxy.TCPBufSize const bufSize = proxy.TCPBufSize
func init() {
rand.Seed(time.Now().UnixNano())
}
// SSTCPConn the struct that override the net.Conn methods // SSTCPConn the struct that override the net.Conn methods
type SSTCPConn struct { type SSTCPConn struct {

View File

@ -17,8 +17,8 @@ type IObfs interface {
GetServerInfo() (s *ssr.ServerInfo) GetServerInfo() (s *ssr.ServerInfo)
Encode(data []byte) (encodedData []byte, err error) Encode(data []byte) (encodedData []byte, err error)
Decode(data []byte) (decodedData []byte, needSendBack bool, err error) Decode(data []byte) (decodedData []byte, needSendBack bool, err error)
SetData(data any) SetData(data interface{})
GetData() any GetData() interface{}
GetOverhead() int GetOverhead() int
} }

View File

@ -1,7 +1,7 @@
package obfs package obfs
import ( import (
"math/rand/v2" "math/rand"
) )
func init() { func init() {
@ -13,7 +13,7 @@ func newHttpPost() IObfs {
// newHttpSimple create a http_simple object // newHttpSimple create a http_simple object
t := &httpSimplePost{ t := &httpSimplePost{
userAgentIndex: rand.IntN(len(requestUserAgent)), userAgentIndex: rand.Intn(len(requestUserAgent)),
methodGet: false, methodGet: false,
} }
return t return t

View File

@ -4,7 +4,7 @@ import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"math/rand/v2" "math/rand"
"strings" "strings"
"github.com/nadoo/glider/proxy/ssr/internal/ssr" "github.com/nadoo/glider/proxy/ssr/internal/ssr"
@ -55,7 +55,7 @@ func newHttpSimple() IObfs {
t := &httpSimplePost{ t := &httpSimplePost{
rawTransSent: false, rawTransSent: false,
rawTransReceived: false, rawTransReceived: false,
userAgentIndex: rand.IntN(len(requestUserAgent)), userAgentIndex: rand.Intn(len(requestUserAgent)),
methodGet: true, methodGet: true,
} }
return t return t
@ -69,25 +69,25 @@ func (t *httpSimplePost) GetServerInfo() (s *ssr.ServerInfo) {
return &t.ServerInfo return &t.ServerInfo
} }
func (t *httpSimplePost) SetData(data any) { func (t *httpSimplePost) SetData(data interface{}) {
} }
func (t *httpSimplePost) GetData() any { func (t *httpSimplePost) GetData() interface{} {
return nil return nil
} }
func (t *httpSimplePost) boundary() (ret string) { func (t *httpSimplePost) boundary() (ret string) {
set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
for range 32 { for i := 0; i < 32; i++ {
ret = fmt.Sprintf("%s%c", ret, set[rand.IntN(len(set))]) ret = fmt.Sprintf("%s%c", ret, set[rand.Intn(len(set))])
} }
return return
} }
func (t *httpSimplePost) data2URLEncode(data []byte) (ret string) { func (t *httpSimplePost) data2URLEncode(data []byte) (ret string) {
for i := range data { for i := 0; i < len(data); i++ {
ret = fmt.Sprintf("%s%%%s", ret, hex.EncodeToString([]byte{data[i]})) ret = fmt.Sprintf("%s%%%s", ret, hex.EncodeToString([]byte{data[i]}))
} }
return return
@ -101,12 +101,12 @@ func (t *httpSimplePost) Encode(data []byte) (encodedData []byte, err error) {
dataLength := len(data) dataLength := len(data)
var headData []byte var headData []byte
if headSize := t.IVLen + t.HeadLen; dataLength-headSize > 64 { if headSize := t.IVLen + t.HeadLen; dataLength-headSize > 64 {
headData = make([]byte, headSize+rand.IntN(64)) headData = make([]byte, headSize+rand.Intn(64))
} else { } else {
headData = make([]byte, dataLength) headData = make([]byte, dataLength)
} }
copy(headData, data[0:len(headData)]) copy(headData, data[0:len(headData)])
requestPathIndex := rand.IntN(len(requestPath)/2) * 2 requestPathIndex := rand.Intn(len(requestPath)/2) * 2
host := t.Host host := t.Host
var customHead string var customHead string
@ -122,7 +122,7 @@ func (t *httpSimplePost) Encode(data []byte) (encodedData []byte, err error) {
} }
hosts := strings.Split(param, ",") hosts := strings.Split(param, ",")
if len(hosts) > 0 { if len(hosts) > 0 {
host = strings.TrimSpace(hosts[rand.IntN(len(hosts))]) host = strings.TrimSpace(hosts[rand.Intn(len(hosts))])
} }
} }
method := "GET /" method := "GET /"

View File

@ -33,11 +33,11 @@ func (p *plain) Decode(data []byte) (decodedData []byte, needSendBack bool, err
return data, false, nil return data, false, nil
} }
func (p *plain) SetData(data any) { func (p *plain) SetData(data interface{}) {
} }
func (p *plain) GetData() any { func (p *plain) GetData() interface{} {
return nil return nil
} }

View File

@ -1,8 +1,7 @@
package obfs package obfs
import ( import (
crand "crypto/rand" "math/rand"
"math/rand/v2"
"github.com/nadoo/glider/proxy/ssr/internal/ssr" "github.com/nadoo/glider/proxy/ssr/internal/ssr"
) )
@ -32,11 +31,11 @@ func (r *randomHead) GetServerInfo() (s *ssr.ServerInfo) {
return &r.ServerInfo return &r.ServerInfo
} }
func (r *randomHead) SetData(data any) { func (r *randomHead) SetData(data interface{}) {
} }
func (r *randomHead) GetData() any { func (r *randomHead) GetData() interface{} {
return nil return nil
} }
@ -58,9 +57,9 @@ func (r *randomHead) Encode(data []byte) (encodedData []byte, err error) {
r.rawTransSent = true r.rawTransSent = true
} }
} else { } else {
size := rand.IntN(96) + 8 size := rand.Intn(96) + 8
encodedData = make([]byte, size) encodedData = make([]byte, size)
crand.Read(encodedData) rand.Read(encodedData)
ssr.SetCRC32(encodedData, size) ssr.SetCRC32(encodedData, size)
d := make([]byte, dataLength) d := make([]byte, dataLength)

View File

@ -3,11 +3,10 @@ package obfs
import ( import (
"bytes" "bytes"
"crypto/hmac" "crypto/hmac"
crand "crypto/rand"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"log" "log"
"math/rand/v2" "math/rand"
"strings" "strings"
"time" "time"
@ -54,18 +53,18 @@ func (t *tls12TicketAuth) GetServerInfo() (s *ssr.ServerInfo) {
return &t.ServerInfo return &t.ServerInfo
} }
func (t *tls12TicketAuth) SetData(data any) { func (t *tls12TicketAuth) SetData(data interface{}) {
if auth, ok := data.(*tlsAuthData); ok { if auth, ok := data.(*tlsAuthData); ok {
t.data = auth t.data = auth
} }
} }
func (t *tls12TicketAuth) GetData() any { func (t *tls12TicketAuth) GetData() interface{} {
if t.data == nil { if t.data == nil {
t.data = &tlsAuthData{} t.data = &tlsAuthData{}
b := make([]byte, 32) b := make([]byte, 32)
crand.Read(b) rand.Read(b)
copy(t.data.localClientID[:], b) copy(t.data.localClientID[:], b)
} }
return t.data return t.data
@ -77,7 +76,7 @@ func (t *tls12TicketAuth) getHost() string {
hosts := strings.Split(t.Param, ",") hosts := strings.Split(t.Param, ",")
if len(hosts) > 0 { if len(hosts) > 0 {
host = hosts[rand.IntN(len(hosts))] host = hosts[rand.Intn(len(hosts))]
host = strings.TrimSpace(host) host = strings.TrimSpace(host)
} }
} }
@ -97,6 +96,7 @@ func packData(prefixData []byte, suffixData []byte) (outData []byte) {
func (t *tls12TicketAuth) Encode(data []byte) (encodedData []byte, err error) { func (t *tls12TicketAuth) Encode(data []byte) (encodedData []byte, err error) {
encodedData = make([]byte, 0) encodedData = make([]byte, 0)
rand.Seed(time.Now().UnixNano())
switch t.handshakeStatus { switch t.handshakeStatus {
case 8: case 8:
if len(data) < 1024 { if len(data) < 1024 {
@ -108,7 +108,7 @@ func (t *tls12TicketAuth) Encode(data []byte) (encodedData []byte, err error) {
start := 0 start := 0
var l int var l int
for len(data)-start > 2048 { for len(data)-start > 2048 {
l = rand.IntN(4096) + 100 l = rand.Intn(4096) + 100
if l > len(data)-start { if l > len(data)-start {
l = len(data) - start l = len(data) - start
} }
@ -129,7 +129,7 @@ func (t *tls12TicketAuth) Encode(data []byte) (encodedData []byte, err error) {
start := 0 start := 0
var l int var l int
for len(data)-start > 2048 { for len(data)-start > 2048 {
l = rand.IntN(4096) + 100 l = rand.Intn(4096) + 100
if l > len(data)-start { if l > len(data)-start {
l = len(data) - start l = len(data) - start
} }
@ -148,7 +148,7 @@ func (t *tls12TicketAuth) Encode(data []byte) (encodedData []byte, err error) {
hmacData := make([]byte, 43) hmacData := make([]byte, 43)
handshakeFinish := []byte("\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00\x20") handshakeFinish := []byte("\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00\x20")
copy(hmacData, handshakeFinish) copy(hmacData, handshakeFinish)
crand.Read(hmacData[11:33]) rand.Read(hmacData[11:33])
h := t.hmacSHA1(hmacData[:33]) h := t.hmacSHA1(hmacData[:33])
copy(hmacData[33:], h) copy(hmacData[33:], h)
encodedData = append(hmacData, t.sendSaver...) encodedData = append(hmacData, t.sendSaver...)
@ -169,11 +169,11 @@ func (t *tls12TicketAuth) Encode(data []byte) (encodedData []byte, err error) {
tlsDataLen += len(sni) tlsDataLen += len(sni)
copy(tlsData[tlsDataLen:], tlsData2) copy(tlsData[tlsDataLen:], tlsData2)
tlsDataLen += len(tlsData2) tlsDataLen += len(tlsData2)
ticketLen := rand.IntN(164)*2 + 64 ticketLen := rand.Intn(164)*2 + 64
tlsData[tlsDataLen-1] = uint8(ticketLen & 0xff) tlsData[tlsDataLen-1] = uint8(ticketLen & 0xff)
tlsData[tlsDataLen-2] = uint8(ticketLen >> 8) tlsData[tlsDataLen-2] = uint8(ticketLen >> 8)
//ticketLen := 208 //ticketLen := 208
crand.Read(tlsData[tlsDataLen : tlsDataLen+ticketLen]) rand.Read(tlsData[tlsDataLen : tlsDataLen+ticketLen])
tlsDataLen += ticketLen tlsDataLen += ticketLen
copy(tlsData[tlsDataLen:], tlsData3) copy(tlsData[tlsDataLen:], tlsData3)
tlsDataLen += len(tlsData3) tlsDataLen += len(tlsData3)
@ -278,7 +278,7 @@ func (t *tls12TicketAuth) packAuthData() (outData []byte) {
now := time.Now().Unix() now := time.Now().Unix()
binary.BigEndian.PutUint32(outData[0:4], uint32(now)) binary.BigEndian.PutUint32(outData[0:4], uint32(now))
crand.Read(outData[4 : 4+18]) rand.Read(outData[4 : 4+18])
hash := t.hmacSHA1(outData[:outSize-ssr.ObfsHMACSHA1Len]) hash := t.hmacSHA1(outData[:outSize-ssr.ObfsHMACSHA1Len])
copy(outData[outSize-ssr.ObfsHMACSHA1Len:], hash) copy(outData[outSize-ssr.ObfsHMACSHA1Len:], hash)

View File

@ -4,10 +4,9 @@ import (
"bytes" "bytes"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
crand "crypto/rand"
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
"math/rand/v2" "math/rand"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -59,13 +58,13 @@ func (a *authAES128) GetServerInfo() (s *ssr.ServerInfo) {
return &a.ServerInfo return &a.ServerInfo
} }
func (a *authAES128) SetData(data any) { func (a *authAES128) SetData(data interface{}) {
if auth, ok := data.(*AuthData); ok { if auth, ok := data.(*AuthData); ok {
a.data = auth a.data = auth
} }
} }
func (a *authAES128) GetData() any { func (a *authAES128) GetData() interface{} {
if a.data == nil { if a.data == nil {
a.data = &AuthData{} a.data = &AuthData{}
} }
@ -75,14 +74,15 @@ func (a *authAES128) GetData() any {
func (a *authAES128) packData(data []byte) (outData []byte) { func (a *authAES128) packData(data []byte) (outData []byte) {
dataLength := len(data) dataLength := len(data)
randLength := 1 randLength := 1
rand.Seed(time.Now().UnixNano())
if dataLength <= 1200 { if dataLength <= 1200 {
if a.packID > 4 { if a.packID > 4 {
randLength += rand.IntN(32) randLength += rand.Intn(32)
} else { } else {
if dataLength > 900 { if dataLength > 900 {
randLength += rand.IntN(128) randLength += rand.Intn(128)
} else { } else {
randLength += rand.IntN(512) randLength += rand.Intn(512)
} }
} }
} }
@ -98,7 +98,7 @@ func (a *authAES128) packData(data []byte) (outData []byte) {
h := a.hmac(key, outData[0:2]) h := a.hmac(key, outData[0:2])
copy(outData[2:4], h[:2]) copy(outData[2:4], h[:2])
// 4~rand length+4, rand number // 4~rand length+4, rand number
crand.Read(outData[4 : 4+randLength]) rand.Read(outData[4 : 4+randLength])
// 4, rand length // 4, rand length
if randLength < 128 { if randLength < 128 {
outData[4] = byte(randLength & 0xFF) outData[4] = byte(randLength & 0xFF)
@ -121,10 +121,11 @@ func (a *authAES128) packData(data []byte) (outData []byte) {
func (a *authAES128) packAuthData(data []byte) (outData []byte) { func (a *authAES128) packAuthData(data []byte) (outData []byte) {
dataLength := len(data) dataLength := len(data)
var randLength int var randLength int
rand.Seed(time.Now().UnixNano())
if dataLength > 400 { if dataLength > 400 {
randLength = rand.IntN(512) randLength = rand.Intn(512)
} else { } else {
randLength = rand.IntN(1024) randLength = rand.Intn(1024)
} }
dataOffset := randLength + 16 + 4 + 4 + 7 dataOffset := randLength + 16 + 4 + 4 + 7
@ -135,7 +136,7 @@ func (a *authAES128) packAuthData(data []byte) (outData []byte) {
copy(key, a.IV) copy(key, a.IV)
copy(key[a.IVLen:], a.Key) copy(key[a.IVLen:], a.Key)
crand.Read(outData[dataOffset-randLength:]) rand.Read(outData[dataOffset-randLength:])
a.data.mutex.Lock() a.data.mutex.Lock()
a.data.connectionID++ a.data.connectionID++
if a.data.connectionID > 0xFF000000 { if a.data.connectionID > 0xFF000000 {
@ -143,9 +144,9 @@ func (a *authAES128) packAuthData(data []byte) (outData []byte) {
} }
if len(a.data.clientID) == 0 { if len(a.data.clientID) == 0 {
a.data.clientID = make([]byte, 8) a.data.clientID = make([]byte, 8)
crand.Read(a.data.clientID) rand.Read(a.data.clientID)
b := make([]byte, 4) b := make([]byte, 4)
crand.Read(b) rand.Read(b)
a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
} }
copy(encrypt[4:], a.data.clientID) copy(encrypt[4:], a.data.clientID)
@ -162,13 +163,13 @@ func (a *authAES128) packAuthData(data []byte) (outData []byte) {
uid := make([]byte, 4) uid := make([]byte, 4)
if len(params) >= 2 { if len(params) >= 2 {
if userID, err := strconv.ParseUint(params[0], 10, 32); err != nil { if userID, err := strconv.ParseUint(params[0], 10, 32); err != nil {
crand.Read(uid) rand.Read(uid)
} else { } else {
binary.LittleEndian.PutUint32(uid, uint32(userID)) binary.LittleEndian.PutUint32(uid, uint32(userID))
a.userKey = a.hashDigest([]byte(params[1])) a.userKey = a.hashDigest([]byte(params[1]))
} }
} else { } else {
crand.Read(uid) rand.Read(uid)
} }
if a.userKey == nil { if a.userKey == nil {
@ -195,7 +196,7 @@ func (a *authAES128) packAuthData(data []byte) (outData []byte) {
h := a.hmac(key, encrypt[0:20]) h := a.hmac(key, encrypt[0:20])
copy(encrypt[20:], h[:4]) copy(encrypt[20:], h[:4])
crand.Read(outData[0:1]) rand.Read(outData[0:1])
h = a.hmac(key, outData[0:1]) h = a.hmac(key, outData[0:1])
copy(outData[1:], h[0:7-1]) copy(outData[1:], h[0:7-1])

View File

@ -6,9 +6,9 @@ import (
"bytes" "bytes"
"crypto/aes" "crypto/aes"
stdCipher "crypto/cipher" stdCipher "crypto/cipher"
"crypto/rand"
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
"math/rand"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -68,13 +68,13 @@ func (a *authChainA) GetServerInfo() (s *ssr.ServerInfo) {
return &a.ServerInfo return &a.ServerInfo
} }
func (a *authChainA) SetData(data any) { func (a *authChainA) SetData(data interface{}) {
if auth, ok := data.(*AuthData); ok { if auth, ok := data.(*AuthData); ok {
a.data = auth a.data = auth
} }
} }
func (a *authChainA) GetData() any { func (a *authChainA) GetData() interface{} {
if a.data == nil { if a.data == nil {
a.data = &AuthData{} a.data = &AuthData{}
} }
@ -100,7 +100,7 @@ func authChainAGetRandLen(dataLength int, random *tools.Shift128plusContext, las
func getRandStartPos(random *tools.Shift128plusContext, randLength int) int { func getRandStartPos(random *tools.Shift128plusContext, randLength int) int {
if randLength > 0 { if randLength > 0 {
return int(int64(random.Next()%8589934609) % int64(randLength)) return int(random.Next() % 8589934609 % uint64(randLength))
} }
return 0 return 0
} }
@ -194,7 +194,7 @@ func (a *authChainA) packAuthData(data []byte) (outData []byte) {
copy(a.userKey, a.Key) copy(a.userKey, a.Key)
} }
} }
for i := range 4 { for i := 0; i < 4; i++ {
uid[i] = a.uid[i] ^ a.lastClientHash[8+i] uid[i] = a.uid[i] ^ a.lastClientHash[8+i]
} }
base64UserKey = base64.StdEncoding.EncodeToString(a.userKey) base64UserKey = base64.StdEncoding.EncodeToString(a.userKey)

View File

@ -34,14 +34,14 @@ func (a *authChainA) authChainBInitDataSize() {
random.InitFromBin(a.Key) random.InitFromBin(a.Key)
length := random.Next()%8 + 4 length := random.Next()%8 + 4
a.dataSizeList = make([]int, length) a.dataSizeList = make([]int, length)
for i := range int(length) { for i := 0; i < int(length); i++ {
a.dataSizeList[i] = int(random.Next() % 2340 % 2040 % 1440) a.dataSizeList[i] = int(random.Next() % 2340 % 2040 % 1440)
} }
sort.Ints(a.dataSizeList) sort.Ints(a.dataSizeList)
length = random.Next()%16 + 8 length = random.Next()%16 + 8
a.dataSizeList2 = make([]int, length) a.dataSizeList2 = make([]int, length)
for i := range int(length) { for i := 0; i < int(length); i++ {
a.dataSizeList2[i] = int(random.Next() % 2340 % 2040 % 1440) a.dataSizeList2[i] = int(random.Next() % 2340 % 2040 % 1440)
} }
sort.Ints(a.dataSizeList2) sort.Ints(a.dataSizeList2)

View File

@ -2,9 +2,8 @@ package protocol
import ( import (
"bytes" "bytes"
crand "crypto/rand"
"encoding/binary" "encoding/binary"
"math/rand/v2" "math/rand"
"time" "time"
"github.com/nadoo/glider/proxy/ssr/internal/ssr" "github.com/nadoo/glider/proxy/ssr/internal/ssr"
@ -35,13 +34,13 @@ func (a *authSHA1v4) GetServerInfo() (s *ssr.ServerInfo) {
return &a.ServerInfo return &a.ServerInfo
} }
func (a *authSHA1v4) SetData(data any) { func (a *authSHA1v4) SetData(data interface{}) {
if auth, ok := data.(*AuthData); ok { if auth, ok := data.(*AuthData); ok {
a.data = auth a.data = auth
} }
} }
func (a *authSHA1v4) GetData() any { func (a *authSHA1v4) GetData() interface{} {
if a.data == nil { if a.data == nil {
a.data = &AuthData{} a.data = &AuthData{}
} }
@ -54,9 +53,9 @@ func (a *authSHA1v4) packData(data []byte) (outData []byte) {
if dataLength <= 1300 { if dataLength <= 1300 {
if dataLength > 400 { if dataLength > 400 {
randLength += rand.IntN(128) randLength += rand.Intn(128)
} else { } else {
randLength += rand.IntN(1024) randLength += rand.Intn(1024)
} }
} }
@ -91,9 +90,9 @@ func (a *authSHA1v4) packAuthData(data []byte) (outData []byte) {
randLength := 1 randLength := 1
if dataLength <= 1300 { if dataLength <= 1300 {
if dataLength > 400 { if dataLength > 400 {
randLength += rand.IntN(128) randLength += rand.Intn(128)
} else { } else {
randLength += rand.IntN(1024) randLength += rand.Intn(1024)
} }
} }
dataOffset := randLength + 4 + 2 dataOffset := randLength + 4 + 2
@ -105,9 +104,9 @@ func (a *authSHA1v4) packAuthData(data []byte) (outData []byte) {
} }
if len(a.data.clientID) == 0 { if len(a.data.clientID) == 0 {
a.data.clientID = make([]byte, 8) a.data.clientID = make([]byte, 8)
crand.Read(a.data.clientID) rand.Read(a.data.clientID)
b := make([]byte, 4) b := make([]byte, 4)
crand.Read(b) rand.Read(b)
a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
} }
// 0-1, out length // 0-1, out length
@ -123,7 +122,7 @@ func (a *authSHA1v4) packAuthData(data []byte) (outData []byte) {
// 2~6, crc of out length+salt+key // 2~6, crc of out length+salt+key
binary.LittleEndian.PutUint32(outData[2:], crc32) binary.LittleEndian.PutUint32(outData[2:], crc32)
// 6~rand length+6, rand numbers // 6~rand length+6, rand numbers
crand.Read(outData[dataOffset-randLength : dataOffset]) rand.Read(outData[dataOffset-randLength : dataOffset])
// 6, rand length // 6, rand length
if randLength < 128 { if randLength < 128 {
outData[6] = byte(randLength & 0xFF) outData[6] = byte(randLength & 0xFF)

View File

@ -23,8 +23,8 @@ type IProtocol interface {
GetServerInfo() *ssr.ServerInfo GetServerInfo() *ssr.ServerInfo
PreEncrypt(data []byte) ([]byte, error) PreEncrypt(data []byte) ([]byte, error)
PostDecrypt(data []byte) ([]byte, int, error) PostDecrypt(data []byte) ([]byte, int, error)
SetData(data any) SetData(data interface{})
GetData() any GetData() interface{}
GetOverhead() int GetOverhead() int
} }

View File

@ -33,11 +33,11 @@ func (o *origin) PostDecrypt(data []byte) ([]byte, int, error) {
return data, len(data), nil return data, len(data), nil
} }
func (o *origin) SetData(data any) { func (o *origin) SetData(data interface{}) {
} }
func (o *origin) GetData() any { func (o *origin) GetData() interface{} {
return nil return nil
} }

View File

@ -63,11 +63,11 @@ func (v *verifySHA1) GetServerInfo() (s *ssr.ServerInfo) {
return &v.ServerInfo return &v.ServerInfo
} }
func (v *verifySHA1) SetData(data any) { func (v *verifySHA1) SetData(data interface{}) {
} }
func (v *verifySHA1) GetData() any { func (v *verifySHA1) GetData() interface{} {
return nil return nil
} }

View File

@ -11,7 +11,7 @@ func init() {
} }
func createCRC32Table() { func createCRC32Table() {
for i := range 256 { for i := 0; i < 256; i++ {
crc := uint32(i) crc := uint32(i)
for j := 8; j > 0; j-- { for j := 8; j > 0; j-- {
if crc&1 == 1 { if crc&1 == 1 {

View File

@ -37,7 +37,7 @@ func (ctx *Shift128plusContext) InitFromBinDatalen(bin []byte, datalen int) {
ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8]) ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8])
ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:]) ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:])
for range 4 { for i := 0; i < 4; i++ {
ctx.Next() ctx.Next()
} }
} }

View File

@ -7,9 +7,9 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/socks"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/proxy/socks"
"github.com/nadoo/glider/proxy/ssr/internal" "github.com/nadoo/glider/proxy/ssr/internal"
"github.com/nadoo/glider/proxy/ssr/internal/cipher" "github.com/nadoo/glider/proxy/ssr/internal/cipher"
@ -31,10 +31,10 @@ type SSR struct {
EncryptPassword string EncryptPassword string
Obfs string Obfs string
ObfsParam string ObfsParam string
ObfsData any ObfsData interface{}
Protocol string Protocol string
ProtocolParam string ProtocolParam string
ProtocolData any ProtocolData interface{}
} }
// NewSSR returns a shadowsocksr proxy, ssr://method:pass@host:port/query // NewSSR returns a shadowsocksr proxy, ssr://method:pass@host:port/query
@ -152,13 +152,6 @@ func (s *SSR) Dial(network, addr string) (net.Conn, error) {
} }
// DialUDP connects to the given address via the proxy. // DialUDP connects to the given address via the proxy.
func (s *SSR) DialUDP(network, addr string) (net.PacketConn, error) { func (s *SSR) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
return nil, proxy.ErrNotSupported return nil, nil, proxy.ErrNotSupported
}
func init() {
proxy.AddUsage("ssr", `
SSR scheme:
ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz
`)
} }

Some files were not shown because too many files have changed in this diff Show More