Compare commits

...

88 Commits

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

https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels

https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#Advanced_Vector_Extensions_2
2022-04-19 21:43:35 +08:00
nadoo
e862ba79f1 docker: add tzdata to set the timezone when running container
e.g:
-e "TZ=Europe/London"
2022-04-19 19:09:20 +08:00
nadoo
a153c34e37 vsock: update dependency to fix building error on riscv64 (#295) 2022-04-06 18:47:04 +08:00
nadoo
730e9c765e proxy: added vsock support (#295) 2022-04-05 15:22:12 +08:00
nadoo
ed7ee6bcd4 chore: update doc and change package path 2022-04-05 09:30:04 +08:00
nadoo
b0584be6cc chore: fix typo 2022-03-16 18:49:51 +08:00
nadoo
7c18fd7d42 ci: change to Go1.18 2022-03-16 12:01:09 +08:00
nadoo
3740375569 chore: doc updates 2022-03-14 13:06:26 +08:00
nadoo
420ec26368 proxy: move usage func to proxy.go 2022-03-13 18:03:10 +08:00
nadoo
a98995e2cb proxy: nat fullcone support for tproxy, trojan, ss, socks5 (fix #253) 2022-03-11 19:08:07 +08:00
nadoo
d68f361c35 ci: fix build error (#314) 2022-03-11 13:10:59 +08:00
Juan Calderon-Perez
1b972af52c
docker: Upgrade base alpine packages, run glider as non-root (#314) 2022-03-11 11:48:22 +08:00
nadoo
c9c2ce995f proxy: delete session when dial failed 2022-03-06 13:09:37 +08:00
nadoo
fc3a21617e chore: code tidy and doc update 2022-03-06 12:58:20 +08:00
nadoo
2ad3498abd proxy: optimize timeout handling in udp copy 2022-02-26 22:32:11 +08:00
nadoo
4b313a3fe1 dhcpd: fix build error 2022-02-24 18:44:52 +08:00
nadoo
9f6e5ebb98 dhcpd: fixed a bug in discovery 2022-02-24 18:41:03 +08:00
nadoo
c261e5989c dhcpd: added dhcpd-failover service, only keep linux support 2022-02-23 22:20:06 +08:00
nadoo
1f196c9cf5 dhcpd: check server existence on more platforms 2022-02-23 17:48:43 +08:00
nadoo
f96ad73c7d check: set checklatencysamples default to 10 (#282) 2022-02-20 21:37:36 +08:00
nadoo
fa97a44e8d check: added new config checklatencysamples to support average latency calculating (#282) 2022-02-20 19:50:23 +08:00
nadoo
cb698713ee dns: log error when adding an invalid record 2022-02-20 01:07:51 +08:00
nadoo
813c5fef94 config: added new flag example to print usage examples 2022-02-16 23:37:25 +08:00
nadoo
5cfb20562a doc: update documents 2022-02-16 00:25:14 +08:00
nadoo
cc63a59f1e proxy: support registerable usage info 2022-02-15 21:34:55 +08:00
nadoo
d05d71e591 ci: uload artifacts after building 2022-02-14 12:12:37 +08:00
nadoo
f358a1e877 config: support environment variable parsing (#311) 2022-02-08 15:39:18 +08:00
nadoo
7f925fb711 dns, ipset, rule: change dns AnswerHandler to use netip instead of string 2022-02-03 00:04:17 +08:00
nadoo
fdb32370e9 ci: combine build and docker process 2022-01-31 11:51:32 +08:00
nadoo
6cfbfff75f config: optimize the parsing speed of rule config file (#306) 2022-01-30 12:34:14 +08:00
nadoo
f2eb638b91 chore: optimize dhcpd and dns and more 2022-01-29 21:10:09 +08:00
nadoo
7e7c7553cc proxy: improve addr handling 2022-01-28 23:35:29 +08:00
nadoo
e12642b47a chore: small optimizations 2022-01-28 15:12:02 +08:00
nadoo
a814f8c545 chore: add cache for github actions 2022-01-27 17:15:04 +08:00
nadoo
9b2f00f4c8 proxy: add new env: FORWARDER_URL to script health check (#310) 2022-01-27 12:42:49 +08:00
nadoo
fac4b86f60 chore: use go1.18beta1 2022-01-26 23:40:49 +08:00
nadoo
755a8ca565 chore: document updates 2022-01-26 22:31:56 +08:00
nadoo
b0b043a280 tproxy: setsockopt in listener to suuport ipv6 2022-01-25 11:16:24 +08:00
nadoo
792b244c59 tproxy: try to fix ipv6 support (#290) 2022-01-24 22:56:59 +08:00
nadoo
d3fbef02bb dns: just return the request packet back when dnsnoaaaa enabled 2022-01-23 11:09:35 +08:00
nadoo
773d5d3b9d dns: fixed an issue 2022-01-23 00:42:54 +08:00
nadoo
b21ce3394d ipset: optimize codes 2022-01-23 00:13:49 +08:00
nadoo
faae2a9e22 ipset: support ipv6 2022-01-22 23:33:08 +08:00
nadoo
a919ac3469 dns: send nil response instead of dropping it when dnsnoaaaa is enabled 2022-01-22 13:13:20 +08:00
nadoo
2754fdeb60 dns: added new config dnsnoaaaa to filter AAAA response, default to false 2022-01-20 21:59:40 +08:00
nadoo
27de61a59d unix: use net.Dial to avoid interface binding Influence 2022-01-19 23:32:44 +08:00
nadoo
720f12aa0a socks5: support protocol chain with unix socket(#291) 2022-01-19 23:25:58 +08:00
nadoo
cf1a4e3817 proxy: added PackerServer interface 2022-01-16 23:15:18 +08:00
Koen Serry
253e5008c4
Added debian/ubuntu package (#307) 2022-01-13 18:46:21 +08:00
nadoo
0238b3fcce chore: build goarch riscv64 2022-01-10 20:09:26 +08:00
nadoo
a8d4e8d7ca chore: update documents 2022-01-08 23:57:30 +08:00
nadoo
1fd59a1677 chore: move some packages to pkg folder 2022-01-08 15:05:55 +08:00
nadoo
a529261893 vmess: support zero security 2022-01-01 00:44:31 +08:00
nadoo
39ae201afe proxy: improve interface binding (by setsockopt) on linux 2021-12-28 20:11:28 +08:00
150 changed files with 3194 additions and 3098 deletions

38
.Dockerfile Normal file
View File

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

View File

@ -1,5 +1,17 @@
name: Build
on: [push, pull_request]
on:
push:
branches:
- "dev"
tags:
- "*"
pull_request:
env:
APP_NAME: glider
DOCKERHUB_REPO: nadoo/glider
GHCR_REPO: ghcr.io/nadoo/glider
PLATFORMS: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/riscv64
jobs:
build:
@ -7,25 +19,85 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get Go version
- name: Set Vars
run: |
echo "GO_VERSION=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | grep $(grep -P "go \d+\." go.mod | cut -d " " -f2) | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')" >> $GITHUB_ENV
echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Set up Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION}}
check-latest: true
go-version-file: "go.mod"
cache: true
- name: Test
run: go test -v .
run: go test -v ./...
- name: Build
run: go build -v .
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
if: startsWith(github.ref, 'refs/tags/')
uses: goreleaser/goreleaser-action@v6
if: "!startsWith(github.ref, 'refs/tags/')"
with:
version: latest
args: release --rm-dist
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 }}

View File

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

View File

@ -7,7 +7,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
- 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

2
.gitignore vendored
View File

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

View File

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

View File

@ -1,24 +1,14 @@
# Build Stage
FROM golang:1.17-alpine AS build-env
FROM golang:1.24-alpine AS build-env
ADD . /src
RUN apk --no-cache add build-base git gcc \
RUN apk --no-cache add git \
&& cd /src && go build -v -ldflags "-s -w"
# Final Stage
FROM alpine
COPY --from=build-env /src/glider /app/
RUN apk -U upgrade --no-cache \
&& apk --no-cache add bind-tools ca-certificates shadow \
&& groupadd -g 1000 glider \
&& useradd -r -u 1000 -g glider glider \
&& apk --no-cache del shadow \
&& chown -R glider:glider /app
WORKDIR /app
USER glider
RUN apk -U upgrade --no-cache \
&& apk --no-cache add ca-certificates
USER 1000
ENTRYPOINT ["./glider"]

442
README.md
View File

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

346
config.go
View File

@ -8,7 +8,7 @@ import (
"github.com/nadoo/conflag"
"github.com/nadoo/glider/dns"
"github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/rule"
)
@ -43,18 +43,30 @@ func parseConfig() *Config {
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.IntVar(&conf.LogFlags, "logflags", 19, "log flags, do not change it if you do not know what it is, ref: https://pkg.go.dev/log#pkg-constants")
flag.IntVar(&conf.LogFlags, "logflags", 19, "do not change it if you do not know what it is, ref: https://pkg.go.dev/log#pkg-constants")
flag.IntVar(&conf.TCPBufSize, "tcpbufsize", 32768, "tcp buffer size in Bytes")
flag.IntVar(&conf.UDPBufSize, "udpbufsize", 2048, "udp buffer size in Bytes")
flag.StringSliceUniqVar(&conf.Listens, "listen", nil, "listen url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS")
flag.StringSliceUniqVar(&conf.Listens, "listen", nil, "listen url, see the URL section below")
flag.StringSliceVar(&conf.Forwards, "forward", nil, "forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]")
flag.StringVar(&conf.Strategy.Strategy, "strategy", "rr", "forward strategy, default: rr")
flag.StringVar(&conf.Strategy.Check, "check", "http://www.msftconnecttest.com/connecttest.txt#expect=200", "check=tcp[://HOST:PORT]: tcp port connect check\ncheck=http://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]\ncheck=https://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]\ncheck=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR\ncheck=disable: disable health check")
flag.StringSliceVar(&conf.Forwards, "forward", nil, "forward url, see the URL section below")
flag.StringVar(&conf.Strategy.Strategy, "strategy", "rr", `rr: Round Robin mode
ha: High Availability mode
lha: Latency based High Availability mode
dh: Destination Hashing mode`)
flag.StringVar(&conf.Strategy.Check, "check", "http://www.msftconnecttest.com/connecttest.txt#expect=200",
`check=tcp[://HOST:PORT]: tcp port connect check
check=http://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
check=https://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, env vars: FORWARDER_ADDR,FORWARDER_URL
check=disable: disable health check`)
flag.IntVar(&conf.Strategy.CheckInterval, "checkinterval", 30, "fowarder check interval(seconds)")
flag.IntVar(&conf.Strategy.CheckTimeout, "checktimeout", 10, "fowarder check timeout(seconds)")
flag.IntVar(&conf.Strategy.CheckTolerance, "checktolerance", 0, "fowarder check tolerance(ms), switch only when new_latency < old_latency - tolerance, only used in lha mode")
flag.IntVar(&conf.Strategy.CheckLatencySamples, "checklatencysamples", 10, "use the average latency of the latest N checks")
flag.BoolVar(&conf.Strategy.CheckDisabledOnly, "checkdisabledonly", false, "check disabled fowarders only")
flag.IntVar(&conf.Strategy.MaxFailures, "maxfailures", 3, "max failures to change forwarder status to disabled")
flag.IntVar(&conf.Strategy.DialTimeout, "dialtimeout", 3, "dial timeout(seconds)")
@ -71,27 +83,34 @@ func parseConfig() *Config {
flag.IntVar(&conf.DNSConfig.Timeout, "dnstimeout", 3, "timeout value used in multiple dnsservers switch(seconds)")
flag.IntVar(&conf.DNSConfig.MaxTTL, "dnsmaxttl", 1800, "maximum TTL value for entries in the CACHE(seconds)")
flag.IntVar(&conf.DNSConfig.MinTTL, "dnsminttl", 0, "minimum TTL value for entries in the CACHE(seconds)")
flag.IntVar(&conf.DNSConfig.CacheSize, "dnscachesize", 4096, "size of CACHE")
flag.IntVar(&conf.DNSConfig.CacheSize, "dnscachesize", 4096, "max number of dns response in CACHE")
flag.BoolVar(&conf.DNSConfig.CacheLog, "dnscachelog", false, "show query log of dns cache")
flag.BoolVar(&conf.DNSConfig.NoAAAA, "dnsnoaaaa", false, "disable AAAA query")
flag.StringSliceUniqVar(&conf.DNSConfig.Records, "dnsrecord", nil, "custom dns record, format: domain/ip")
// service configs
flag.StringSliceUniqVar(&conf.Services, "service", nil, "run specified services, format: SERVICE_NAME[,SERVICE_CONFIG]")
flag.Usage = usage
err := flag.Parse()
if err != nil {
if err := flag.Parse(); err != nil {
// flag.Usage()
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
os.Exit(-1)
}
// setup a log func
if conf.Verbose {
log.SetFlags(conf.LogFlags)
log.F = log.Debugf
if *scheme != "" {
fmt.Fprint(flag.Output(), proxy.Usage(*scheme))
os.Exit(0)
}
if *example {
fmt.Fprint(flag.Output(), examples)
os.Exit(0)
}
// setup logger
log.Set(conf.Verbose, conf.LogFlags)
if len(conf.Listens) == 0 && conf.DNS == "" && len(conf.Services) == 0 {
// flag.Usage()
fmt.Fprintf(os.Stderr, "ERROR: listen url must be specified.\n")
@ -108,6 +127,11 @@ func parseConfig() *Config {
proxy.UDPBufSize = conf.UDPBufSize
}
loadRules(conf)
return conf
}
func loadRules(conf *Config) {
// rulefiles
for _, ruleFile := range conf.RuleFiles {
if !path.IsAbs(ruleFile) {
@ -136,217 +160,95 @@ func parseConfig() *Config {
conf.rules = append(conf.rules, rule)
}
}
return conf
}
func usage() {
app := os.Args[0]
w := flag.Output()
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "%s %s usage:\n", app, version)
fmt.Fprint(flag.Output(), usage1)
flag.PrintDefaults()
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Available schemes:\n")
fmt.Fprintf(w, " listen: mixed ss socks5 http vless trojan trojanc redir redir6 tproxy tcp udp tls ws wss unix smux kcp pxyproto\n")
fmt.Fprintf(w, " forward: direct reject ss socks4 socks5 http ssr ssh vless vmess trojan trojanc tcp udp tls ws wss unix smux 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&timeout=SECONDS]\n")
fmt.Fprintf(w, " timeout: timeout of ssh handshake and channel operation, default: 5\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, " if alterID=0 or not set, VMessAEAD will be enabled\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, "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][&cert=PATH]\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, "TLS client scheme:\n")
fmt.Fprintf(w, " tls://host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&alpn=proto1][&alpn=proto2]\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[&alpn=proto1][&alpn=proto2]\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, " wss://host:port[/path][?serverName=SERVERNAME][&skipVerify=true][&cert=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, " wss://:port[/path]?cert=PATH&key=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, "\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, "Smux scheme:\n")
fmt.Fprintf(w, " smux://host:port\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,LEASE_MINUTES[,MAC=IP,MAC=IP...]\n")
fmt.Fprintf(w, " e.g.,service=dhcpd,eth1,192.168.1.100,192.168.1.199,720\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_AES_128_GCM:pass@:8443 -verbose\n")
fmt.Fprintf(w, " -listen on 0.0.0.0:8443 as a ss server.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " "+app+" -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose\n")
fmt.Fprintf(w, " -listen on :443 as a https(http over tls) proxy server.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " "+app+" -listen http://:8080 -forward socks5://127.0.0.1:1080\n")
fmt.Fprintf(w, " -listen on :8080 as a http proxy server, forward all requests via socks5 server.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " "+app+" -listen socks5://:1080 -forward \"tls://abc.com:443,vmess://security:uuid@?alterID=10\"\n")
fmt.Fprintf(w, " -listen on :1080 as a socks5 server, forward all requests via remote tls+vmess server.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " "+app+" -listen socks5://:1080 -forward ss://method:pass@server1:port1 -forward ss://method:pass@server2:port2 -strategy rr\n")
fmt.Fprintf(w, " -listen on :1080 as socks5 server, forward requests via server1 and server2 in round robin mode.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " "+app+" -listen tcp://:80 -forward tcp://2.2.2.2:80\n")
fmt.Fprintf(w, " -tcp tunnel: listen on :80 and forward all requests to 2.2.2.2:80.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " "+app+" -listen udp://:53 -forward ss://method:pass@1.1.1.1:8443,udp://8.8.8.8:53\n")
fmt.Fprintf(w, " -listen on :53 and forward all udp requests to 8.8.8.8:53 via remote ss server.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " "+app+" -listen socks5://:1080 -listen http://:8080 -forward ss://method:pass@1.1.1.1:8443\n")
fmt.Fprintf(w, " -listen on :1080 as socks5 server, :8080 as http proxy server, forward all requests via remote ss server.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " "+app+" -verbose -listen -dns=:53 -dnsserver=8.8.8.8:53 -forward ss://method:pass@server:port -dnsrecord=www.example.com/1.2.3.4\n")
fmt.Fprintf(w, " -listen on :53 as dns server, forward to 8.8.8.8:53 via ss server.\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(flag.Output(), usage2, proxy.ServerSchemes(), proxy.DialerSchemes(), version)
}
var usage1 = `
Usage: glider [-listen URL]... [-forward URL]... [OPTION]...
e.g. glider -config /etc/glider/glider.conf
glider -listen :8443 -forward socks5://serverA:1080 -forward socks5://serverB:1080 -verbose
OPTION:
`
var usage2 = `
URL:
proxy: SCHEME://[USER:PASS@][HOST]:PORT
chain: proxy,proxy[,proxy]...
e.g. -listen socks5://:1080
-listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// (protocol chain)
e.g. -forward socks5://server:1080
-forward tls://server.com:443,http:// (protocol chain)
-forward socks5://serverA:1080,socks5://serverB:1080 (proxy chain)
SCHEME:
listen : %s
forward: %s
Note: use 'glider -scheme all' or 'glider -scheme SCHEME' to see help info for the scheme.
--
Forwarder Options: FORWARD_URL#OPTIONS
priority : the priority of that forwarder, the larger the higher, default: 0
interface: the local interface or ip address used to connect remote server.
e.g. -forward socks5://server:1080#priority=100
-forward socks5://server:1080#interface=eth0
-forward socks5://server:1080#priority=100&interface=192.168.1.99
Services:
dhcpd: service=dhcpd,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
service=dhcpd-failover,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
e.g. service=dhcpd,eth1,192.168.1.100,192.168.1.199,720
--
Help:
glider -help
glider -scheme all
glider -example
see README.md and glider.conf.example for more details.
--
glider %s, https://github.com/nadoo/glider (glider.proxy@gmail.com)
`
var examples = `
Examples:
glider -config glider.conf
-run glider with specified config file.
glider -listen :8443 -verbose
-listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode.
glider -listen socks5://:1080 -listen http://:8080 -verbose
-multiple listeners: listen on :1080 as socks5 proxy server, and on :8080 as http proxy server.
glider -listen :8443 -forward direct://#interface=eth0 -forward direct://#interface=eth1
-multiple forwarders: listen on 8443 and forward requests via interface eth0 and eth1 in round robin mode.
glider -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose
-protocol chain: listen on :443 as a https(http over tls) proxy server.
glider -listen http://:8080 -forward socks5://serverA:1080,socks5://serverB:1080
-proxy chain: listen on :8080 as a http proxy server, forward all requests via forward chain.
glider -listen :8443 -forward socks5://serverA:1080 -forward socks5://serverB:1080#priority=10 -forward socks5://serverC:1080#priority=10
-forwarder priority: serverA will only be used when serverB and serverC are not available.
glider -listen tcp://:80 -forward tcp://serverA:80
-tcp tunnel: listen on :80 and forward all requests to serverA:80.
glider -listen udp://:53 -forward socks5://serverA:1080,udp://8.8.8.8:53
-udp tunnel: listen on :53 and forward all udp requests to 8.8.8.8:53 via remote socks5 server.
glider -verbose -dns=:53 -dnsserver=8.8.8.8:53 -forward socks5://serverA:1080 -dnsrecord=abc.com/1.2.3.4
-dns over proxy: listen on :53 as dns server, forward to 8.8.8.8:53 via socks5 server.
`

View File

@ -31,7 +31,7 @@ forward=ss://method:pass@1.1.1.1:8443
# upstream forward proxy (forward chain)
forward=http://1.1.1.1:8080,socks5://2.2.2.2:1080
# multiple upstream proxies forwad strategy
# multiple upstream proxies forward strategy
strategy=rr
# forwarder health check

View File

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

View File

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

View File

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

View File

@ -32,16 +32,17 @@ verbose=True
# different protocols.
# listen on 8443, serve as http/socks5 proxy on the same port.
listen=:8443
# listen=:8443
listen=127.0.0.1:8443
# listen on 8448 as a ss server.
# listen=ss://AEAD_CHACHA20_POLY1305:pass@:8448
# listen on 8080 as a http proxy server.
listen=http://:8080
# listen=http://:8080
# listen on 1080 as a socks5 proxy server.
listen=socks5://:1080
# listen=socks5://:1080
# listen on 1234 as vless proxy server.
# listen=vless://uuid@:1234
@ -61,7 +62,10 @@ listen=socks5://:1080
# listen=tls://:443?cert=crtFilePath&key=keyFilePath,ss://AEAD_CHACHA20_POLY1305:pass@
# socks5 over unix domain socket
# listen=unix:///tmp/glider.socket,socks5://
# listen=unix:///dev/shm/socket,socks5://
# socks5 over vm socket
# listen=vsock://:1234,socks5://
# socks5 over kcp
# listen=kcp://aes:key@127.0.0.1:8444?dataShards=10&parityShards=3&mode=fast,socks5://
@ -76,10 +80,10 @@ listen=socks5://:1080
# listen=ws://:1234/path?host=domain.com,vless://707f20ea-d4b8-4d1d-8e2e-2c86cb2ed97a@?fallback=127.0.0.1:80
# trojan server
# listen=trojan://PASSWORD:1234?cert=/path/to/cert&key=/path/to/key&fallback=127.0.0.1
# listen=trojan://PASSWORD@:1234?cert=/path/to/cert&key=/path/to/key&fallback=127.0.0.1
# trojanc server (trojan without tls)
# listen=trojanc://PASSWORD:1234?fallback=127.0.0.1
# listen=trojanc://PASSWORD@:1234?fallback=127.0.0.1
# FORWARDERS
# ----------
@ -136,7 +140,7 @@ listen=socks5://:1080
# forward=tls://server.com:443,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98
# vmess over websocket
# forward=ws://1.1.1.1:80/path?host=server.com,vmess://chacha20-poly1305:5a146038-0b56-4e95-b1dc-5c6f5a32cd98
# forward=ws://1.1.1.1:80/path?host=server.com,vmess://chacha20-poly1305:5a146038-0b56-4e95-b1dc-5c6f5a32cd98@
# vmess over ws over tls
# forward=tls://server.com:443,ws://,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98
@ -152,7 +156,7 @@ listen=socks5://:1080
# forward=simple-obfs://1.1.1.1:443?type=tls&host=apple.com,ss://AEAD_CHACHA20_POLY1305:pass@
# socks5 over unix domain socket
# forward=unix:///tmp/glider.socket,socks5://
# forward=unix:///dev/shm/socket,socks5://
# FORWARDER CHAIN
# ---------------
@ -195,7 +199,7 @@ maxfailures=3
# check=http://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
# check=https://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
# e.g. check=https://www.netflix.com/title/81215567#expect=301|404
# check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR
# check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR,FORWARDER_URL
# check=disable: disable health check
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
@ -208,6 +212,9 @@ checktimeout=10
# switch forwarder only when new_latency < old_latency - tolerance, used in lha mode
checktolerance=100
# use the average latency of the latest N checks
checklatencysamples=10
# check disabled fowarders only
checkdisabledonly=false
@ -216,7 +223,7 @@ checkdisabledonly=false
# we can specify different upstream dns server in rule file for different destinations.
# Setup a dns forwarding server
dns=:53
# dns=:53
# global remote dns server (you can specify different dns server in rule file)
dnsserver=8.8.8.8:53
@ -242,36 +249,49 @@ dnscachesize=4096
# show query log of dns cache
dnscachelog=True
# disable AAAA queries
# dnsnoaaaa=True
# custom records
dnsrecord=www.example.com/1.2.3.4
dnsrecord=www.example.com/2606:2800:220:1:248:1893:25c8:1946
# SERVICES
# service=dhcpd,INTERFACE,START_IP,END_IP,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
# service=dhcpd,eth2,192.168.2.100,192.168.2.199,720,fc:23:34:9e:25:01=192.168.2.101,fc:23:34:9e:25:02=192.168.2.102
# INTERFACE SPECIFIC
# ------------------
# Specify the outbound ip/interface.
# Specify global outbound ip/interface.
#
# interface=""
# interface="192.168.1.100"
# interface="eth0"
#
# Specify interface for a forwarder:
# forward=socks5://192.168.1.10:1080#priority=100&interface=eth0
# forward=socks5://192.168.1.10:1080#priority=100&interface=192.168.1.100
# RULE FILES
# ----------
# Specify additional forward rules.
#
# specify rules folder, so all *.rule files under this folder will be parsed as rule file
rules-dir=rules.d
# rules-dir=rules.d
#
# specify a rule file
#rulefile=office.rule
#rulefile=home.rule
# INCLUDE MORE CONFIG FILES
# INCLUDE CONFIG FILES
# ----------
#include=dnsrecord.inc.conf
#include=more.conf
# ENVIRONMENT VARIABLES
# ----------
# use {$ENV_VAR_NAME} in VALUE to get the Environment Variable value.
# forward=socks5://{$USER_NAME}:{$USER_PASS}@:1080

View File

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

View File

@ -5,17 +5,18 @@ import (
"errors"
"io"
"net"
"net/netip"
"strconv"
"strings"
"time"
"github.com/nadoo/glider/log"
"github.com/nadoo/glider/pool"
"github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/pkg/pool"
"github.com/nadoo/glider/proxy"
)
// AnswerHandler function handles the dns TypeA or TypeAAAA answer.
type AnswerHandler func(domain, ip string) error
type AnswerHandler func(domain string, ip netip.Addr) error
// Config for dns.
type Config struct {
@ -27,6 +28,7 @@ type Config struct {
AlwaysTCP bool
CacheSize int
CacheLog bool
NoAAAA bool
}
// Client is a dns client struct.
@ -51,7 +53,9 @@ func NewClient(proxy proxy.Proxy, config *Config) (*Client, error) {
// custom records
for _, record := range config.Records {
c.AddRecord(record)
if err := c.AddRecord(record); err != nil {
log.F("[dns] add record '%s' error: %s", record, err)
}
}
return c, nil
@ -65,6 +69,12 @@ func (c *Client) Exchange(reqBytes []byte, clientAddr string, preferTCP bool) ([
return nil, err
}
if c.config.NoAAAA && req.Question.QTYPE == QTypeAAAA {
respBytes := valCopy(reqBytes)
respBytes[2] |= uint8(ResponseMsg) << 7
return respBytes, nil
}
if req.Question.QTYPE == QTypeA || req.Question.QTYPE == QTypeAAAA {
if v, expired := c.cache.Get(qKey(req.Question)); len(v) > 2 {
v = valCopy(v)
@ -131,11 +141,11 @@ func (c *Client) extractAnswer(resp *Message) ([]string, int) {
ttl := c.config.MinTTL
for _, answer := range resp.Answers {
if answer.TYPE == QTypeA || answer.TYPE == QTypeAAAA {
for _, h := range c.handlers {
h(resp.Question.QNAME, answer.IP)
}
if answer.IP != "" {
ips = append(ips, answer.IP)
if answer.IP.IsValid() && !answer.IP.IsUnspecified() {
for _, h := range c.handlers {
h(resp.Question.QNAME, answer.IP)
}
ips = append(ips, answer.IP.String())
}
if answer.TTL != 0 {
ttl = int(answer.TTL)
@ -169,7 +179,7 @@ func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) (
ups := c.UpStream(qname)
server = ups.Server()
for i := 0; i < ups.Len(); i++ {
for range ups.Len() {
var rc net.Conn
rc, err = dialer.Dial(network, server)
if err != nil {
@ -276,10 +286,13 @@ func (c *Client) AddHandler(h AnswerHandler) {
// AddRecord adds custom record to dns cache, format:
// www.example.com/1.2.3.4 or www.example.com/2606:2800:220:1:248:1893:25c8:1946
func (c *Client) AddRecord(record string) error {
r := strings.Split(record, "/")
domain, ip := r[0], r[1]
m, err := c.MakeResponse(domain, ip, uint32(c.config.MaxTTL))
domain, ip, found := strings.Cut(record, "/")
if !found {
return errors.New("wrong record format, must contain '/'")
}
m, err := MakeResponse(domain, ip, uint32(c.config.MaxTTL))
if err != nil {
log.F("[dns] add custom record error: %s", err)
return err
}
@ -298,27 +311,21 @@ func (c *Client) AddRecord(record string) error {
// 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, ip string, ttl uint32) (*Message, error) {
ipb := net.ParseIP(ip)
if ipb == nil {
return nil, errors.New("MakeResponse: invalid ip format")
func MakeResponse(domain, ip string, ttl uint32) (*Message, error) {
addr, err := netip.ParseAddr(ip)
if err != nil {
return nil, err
}
var rdata []byte
var qtype, rdlen uint16
if rdata = ipb.To4(); rdata != nil {
qtype = QTypeA
rdlen = net.IPv4len
} else {
qtype = QTypeAAAA
rdlen = net.IPv6len
rdata = ipb
var qtype, rdlen uint16 = QTypeA, net.IPv4len
if addr.Is6() {
qtype, rdlen = QTypeAAAA, net.IPv6len
}
m := NewMessage(0, Response)
m := NewMessage(0, ResponseMsg)
m.SetQuestion(NewQuestion(qtype, domain))
rr := &RR{NAME: domain, TYPE: qtype, CLASS: ClassINET,
TTL: ttl, RDLENGTH: rdlen, RDATA: rdata}
TTL: ttl, RDLENGTH: rdlen, RDATA: addr.AsSlice()}
m.AddAnswer(rr)
return m, nil

View File

@ -5,25 +5,25 @@ import (
"encoding/binary"
"errors"
"io"
"math/rand"
"net"
"math/rand/v2"
"net/netip"
"strings"
)
// UDPMaxLen is the max size of udp dns request.
// https://www.rfc-editor.org/rfc/rfc1035#section-4.2.1
// Messages carried by UDP are restricted to 512 bytes (not counting the IP
// or UDP headers). Longer messages are truncated and the TC bit is set in
// the header.
const UDPMaxLen = 512
// https://www.dnsflagday.net/2020/
const UDPMaxLen = 1232
// HeaderLen is the length of dns msg header.
const HeaderLen = 12
// MsgType is the dns Message type.
type MsgType byte
// Message types.
const (
Query = 0
Response = 1
QueryMsg MsgType = 0
ResponseMsg MsgType = 1
)
// Query types.
@ -41,16 +41,16 @@ const ClassINET uint16 = 1
// format called a message. The top level format of message is divided
// into 5 sections (some of which are empty in certain cases) shown below:
//
// +---------------------+
// | Header |
// +---------------------+
// | Question | the question for the name server
// +---------------------+
// | Answer | RRs answering the question
// +---------------------+
// | Authority | RRs pointing toward an authority
// +---------------------+
// | Additional | RRs holding additional information
// +---------------------+
// | Header |
// +---------------------+
// | Question | the question for the name server
// +---------------------+
// | Answer | RRs answering the question
// +---------------------+
// | Authority | RRs pointing toward an authority
// +---------------------+
// | Additional | RRs holding additional information
type Message struct {
Header
// most dns implementation only support 1 question
@ -64,7 +64,7 @@ type Message struct {
}
// NewMessage returns a new message.
func NewMessage(id uint16, msgType int) *Message {
func NewMessage(id uint16, msgType MsgType) *Message {
if id == 0 {
id = uint16(rand.Uint32())
}
@ -91,8 +91,7 @@ func (m *Message) AddAnswer(rr *RR) error {
// Marshal marshals message struct to []byte.
func (m *Message) Marshal() ([]byte, error) {
buf := &bytes.Buffer{}
_, err := m.MarshalTo(buf)
if err != nil {
if _, err := m.MarshalTo(buf); err != nil {
return nil, err
}
return buf.Bytes(), nil
@ -134,8 +133,7 @@ func UnmarshalMessage(b []byte) (*Message, error) {
}
m := &Message{unMarshaled: b}
err := UnmarshalHeader(b[:HeaderLen], &m.Header)
if err != nil {
if err := UnmarshalHeader(b[:HeaderLen], &m.Header); err != nil {
return nil, err
}
@ -148,7 +146,7 @@ func UnmarshalMessage(b []byte) (*Message, error) {
// resp answers
rrIdx := HeaderLen + qLen
for i := 0; i < int(m.Header.ANCOUNT); i++ {
for range int(m.Header.ANCOUNT) {
rr := &RR{}
rrLen, err := m.UnmarshalRR(rrIdx, rr)
if err != nil {
@ -168,22 +166,21 @@ func UnmarshalMessage(b []byte) (*Message, error) {
// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.1
// The header contains the following fields:
//
// 1 1 1 1 1 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | ID |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | QDCOUNT |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | ANCOUNT |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | NSCOUNT |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | ARCOUNT |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// 1 1 1 1 1 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | ID |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | QDCOUNT |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | ANCOUNT |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | NSCOUNT |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | ARCOUNT |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
type Header struct {
ID uint16
Bits uint16
@ -194,7 +191,7 @@ type Header struct {
}
// SetMsgType sets the message type.
func (h *Header) SetMsgType(qr int) {
func (h *Header) SetMsgType(qr MsgType) {
h.Bits |= uint16(qr) << 15
}
@ -213,10 +210,11 @@ func (h *Header) SetAncount(ancount int) {
h.ANCOUNT = uint16(ancount)
}
func (h *Header) setFlag(QR uint16, Opcode uint16, AA uint16,
TC uint16, RD uint16, RA uint16, RCODE uint16) {
h.Bits = QR<<15 + Opcode<<11 + AA<<10 + TC<<9 + RD<<8 + RA<<7 + RCODE
}
// Not used now, but keep it for future use.
// func (h *Header) setFlag(QR uint16, Opcode uint16, AA uint16,
// TC uint16, RD uint16, RA uint16, RCODE uint16) {
// h.Bits = QR<<15 + Opcode<<11 + AA<<10 + TC<<9 + RD<<8 + RA<<7 + RCODE
// }
// MarshalTo marshals header struct to []byte and write to w.
func (h *Header) MarshalTo(w io.Writer) (int, error) {
@ -249,17 +247,17 @@ func UnmarshalHeader(b []byte, h *Header) error {
// i.e., the parameters that define what is being asked. The section
// contains QDCOUNT (usually 1) entries, each of the following format:
//
// 1 1 1 1 1 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | |
// / QNAME /
// / /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | QTYPE |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | QCLASS |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// 1 1 1 1 1 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | |
// / QNAME /
// / /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | QTYPE |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | QCLASS |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
type Question struct {
QNAME string
QTYPE uint16
@ -282,14 +280,12 @@ func (q *Question) MarshalTo(w io.Writer) (n int, err error) {
return
}
err = binary.Write(w, binary.BigEndian, q.QTYPE)
if err != nil {
if err = binary.Write(w, binary.BigEndian, q.QTYPE); err != nil {
return
}
n += 2
err = binary.Write(w, binary.BigEndian, q.QCLASS)
if err != nil {
if err = binary.Write(w, binary.BigEndian, q.QCLASS); err != nil {
return
}
n += 2
@ -329,26 +325,26 @@ func (m *Message) UnmarshalQuestion(b []byte, q *Question) (n int, err error) {
// records is specified in the corresponding count field in the header.
// Each resource record has the following format:
//
// 1 1 1 1 1 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | |
// / /
// / NAME /
// | |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | TYPE |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | CLASS |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | TTL |
// | |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | RDLENGTH |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
// / RDATA /
// / /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// 1 1 1 1 1 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | |
// / /
// / NAME /
// | |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | TYPE |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | CLASS |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | TTL |
// | |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | RDLENGTH |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
// / RDATA /
// / /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
type RR struct {
NAME string
TYPE uint16
@ -357,7 +353,7 @@ type RR struct {
RDLENGTH uint16
RDATA []byte
IP string
IP netip.Addr
}
// NewRR returns a new dns rr.
@ -372,20 +368,17 @@ func (rr *RR) MarshalTo(w io.Writer) (n int, err error) {
return
}
err = binary.Write(w, binary.BigEndian, rr.TYPE)
if err != nil {
if err = binary.Write(w, binary.BigEndian, rr.TYPE); err != nil {
return
}
n += 2
err = binary.Write(w, binary.BigEndian, rr.CLASS)
if err != nil {
if err = binary.Write(w, binary.BigEndian, rr.CLASS); err != nil {
return
}
n += 2
err = binary.Write(w, binary.BigEndian, rr.TTL)
if err != nil {
if err = binary.Write(w, binary.BigEndian, rr.TTL); err != nil {
return
}
n += 4
@ -396,8 +389,7 @@ func (rr *RR) MarshalTo(w io.Writer) (n int, err error) {
}
n += 2
_, err = w.Write(rr.RDATA)
if err != nil {
if _, err = w.Write(rr.RDATA); err != nil {
return
}
n += len(rr.RDATA)
@ -438,9 +430,9 @@ func (m *Message) UnmarshalRR(start int, rr *RR) (n int, err error) {
rr.RDATA = p[n+10 : n+10+int(rr.RDLENGTH)]
if rr.TYPE == QTypeA {
rr.IP = net.IP(rr.RDATA[:net.IPv4len]).String()
rr.IP = netip.AddrFrom4(*(*[4]byte)(rr.RDATA[:4]))
} else if rr.TYPE == QTypeAAAA {
rr.IP = net.IP(rr.RDATA[:net.IPv6len]).String()
rr.IP = netip.AddrFrom16(*(*[16]byte)(rr.RDATA[:16]))
}
n = n + 10 + int(rr.RDLENGTH)
@ -490,8 +482,7 @@ func (m *Message) UnmarshalDomainTo(sb *strings.Builder, b []byte) (int, error)
}
offset := binary.BigEndian.Uint16(b[idx : idx+2])
err := m.UnmarshalDomainPointTo(sb, int(offset&0x3FFF))
if err != nil {
if err := m.UnmarshalDomainPointTo(sb, int(offset&0x3FFF)); err != nil {
return 0, err
}

View File

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

View File

@ -3,7 +3,6 @@ package main
import (
// comment out the services you don't need to make the compiled binary smaller.
// _ "github.com/nadoo/glider/service/xxx"
_ "github.com/nadoo/glider/service/dhcpd"
// comment out the protocols you don't need to make the compiled binary smaller.
_ "github.com/nadoo/glider/proxy/http"

View File

@ -2,10 +2,11 @@ package main
import (
// comment out the services you don't need to make the compiled binary smaller.
// _ "github.com/nadoo/glider/service/xxx"
_ "github.com/nadoo/glider/service/dhcpd"
// comment out the protocols you don't need to make the compiled binary smaller.
_ "github.com/nadoo/glider/proxy/redir"
_ "github.com/nadoo/glider/proxy/tproxy"
_ "github.com/nadoo/glider/proxy/unix"
_ "github.com/nadoo/glider/proxy/vsock"
)

35
go.mod
View File

@ -1,36 +1,29 @@
module github.com/nadoo/glider
go 1.17
go 1.24
require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152
github.com/insomniacslk/dhcp v0.0.0-20211214070828-5297eed8f489
github.com/nadoo/conflag v0.2.3
github.com/nadoo/ipset v0.3.0
github.com/xtaci/kcp-go/v5 v5.6.1
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
github.com/nadoo/conflag v0.3.1
github.com/nadoo/ipset v0.5.0
github.com/xtaci/kcp-go/v5 v5.6.18
golang.org/x/crypto v0.35.0
golang.org/x/sys v0.30.0
)
require (
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/klauspost/reedsolomon v1.9.15 // indirect
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 // indirect
github.com/mdlayher/raw v0.0.0-20211126142749-4eae47f3d54b // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/klauspost/reedsolomon v1.12.4 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/templexxx/cpu v0.0.9 // indirect
github.com/templexxx/xorsimd v0.4.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-20210528151154-e40b768296a7 // indirect
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
golang.org/x/net v0.35.0 // indirect
)
// Replace dependency modules with local developing copy
// use `go list -m all` to confirm the final module used
// replace (
// github.com/nadoo/conflag => ../conflag
// )

146
go.sum
View File

@ -18,7 +18,6 @@ github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:ucvhdsUCE3TH0Lo
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -34,149 +33,82 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/insomniacslk/dhcp v0.0.0-20211214070828-5297eed8f489 h1:jhdHqd7DxBrzfuFSoPxjD6nUVaV/1RIn9aHA0WCf/as=
github.com/insomniacslk/dhcp v0.0.0-20211214070828-5297eed8f489/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/reedsolomon v1.9.9/go.mod h1:O7yFFHiQwDR6b2t63KPUpccPtNdp5ADgh1gg4fd12wo=
github.com/klauspost/reedsolomon v1.9.15 h1:g2erWKD2M6rgnPf89fCji6jNlhMKMdXcuNHMW1SYCIo=
github.com/klauspost/reedsolomon v1.9.15/go.mod h1:eqPAcE7xar5CIzcdfwydOEdcmchAKAP/qs14y4GCBOk=
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/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.0.0-20211126142749-4eae47f3d54b h1:MHcTarUMC4sFA7eiyR8IEJ6j2PgmgXR+B9X2IIMjh7A=
github.com/mdlayher/raw v0.0.0-20211126142749-4eae47f3d54b/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104/go.mod h1:wqKykBG2QzQDJEzvRkcS8x6MiSJkF52hXZsXcjaB3ls=
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/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/reedsolomon v1.12.4 h1:5aDr3ZGoJbgu/8+j45KtUJxzYm8k08JGtB9Wx1VQ4OA=
github.com/klauspost/reedsolomon v1.12.4/go.mod h1:d3CzOMOt0JXGIFZm1StgkyF14EYr3xneR2rNWo7NcMU=
github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY=
github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/nadoo/conflag v0.3.1 h1:4pHkLIz8PUsfg6ajNYRRSY3bt6m2LPsu6KOzn5uIXQw=
github.com/nadoo/conflag v0.3.1/go.mod h1:dzFfDUpXdr2uS2oV+udpy5N2vfNOu/bFzjhX1WI52co=
github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc=
github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/cpu v0.0.7/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/cpu v0.0.9 h1:cGGLK8twbc1J1S/fHnZW7BylXYaFP+0fR2s+nzsFDiU=
github.com/templexxx/cpu v0.0.9/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/xorsimd v0.4.1 h1:iUZcywbOYDRAZUasAs2eSCUW8eobuZDy0I9FJiORkVg=
github.com/templexxx/xorsimd v0.4.1/go.mod h1:W+ffZz8jJMH2SXwuKu9WhygqBMbFnp14G2fqEr8qaNo=
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
github.com/templexxx/cpu v0.1.1 h1:isxHaxBXpYFWnk2DReuKkigaZyrjs2+9ypIdGP4h+HI=
github.com/templexxx/cpu v0.1.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/xorsimd v0.4.3 h1:9AQTFHd7Bhk3dIT7Al2XeBX5DWOvsUPZCuhyAtNbHjU=
github.com/templexxx/xorsimd v0.4.3/go.mod h1:oZQcD6RFDisW2Am58dSAGwwL6rHjbzrlu25VDqfWkQg=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7 h1:XMAtQHwKjWHIRwg+8Nj/rzUomQY1q6cM3ncA0wP8GU4=
github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
github.com/xtaci/kcp-go/v5 v5.6.1 h1:Pwn0aoeNSPF9dTS7IgiPXn0HEtaIlVb6y5UKWPsx8bI=
github.com/xtaci/kcp-go/v5 v5.6.1/go.mod h1:W3kVPyNYwZ06p79dNwFWQOVFrdcBpDBsdyvK8moQrYo=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/xtaci/kcp-go/v5 v5.6.18 h1:7oV4mc272pcnn39/13BB11Bx7hJM4ogMIEokJYVWn4g=
github.com/xtaci/kcp-go/v5 v5.6.18/go.mod h1:75S1AKYYzNUSXIv30h+jPKJYZUwqpfvLshu63nCNSOM=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/arch v0.0.0-20190909030613-46d78d1859ac/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@ -191,9 +123,7 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.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=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

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

View File

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

View File

@ -1,39 +0,0 @@
package log
import (
"fmt"
stdlog "log"
)
// F is the main log function.
var F = func(string, ...interface{}) {}
// SetFlags sets the output flags for the logger.
func SetFlags(flag int) {
stdlog.SetFlags(flag)
}
// Debugf prints debug log.
func Debugf(f string, v ...interface{}) {
stdlog.Output(2, fmt.Sprintf(f, v...))
}
// Print prints log.
func Print(v ...interface{}) {
stdlog.Print(v...)
}
// Printf prints log.
func Printf(f string, v ...interface{}) {
stdlog.Printf(f, v...)
}
// Fatal log and exit.
func Fatal(v ...interface{}) {
stdlog.Fatal(v...)
}
// Fatalf log and exit.
func Fatalf(f string, v ...interface{}) {
stdlog.Fatalf(f, v...)
}

21
main.go
View File

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

41
pkg/log/log.go Normal file
View File

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

View File

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

View File

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

View File

@ -1,3 +1,25 @@
// MIT License
//
// Copyright (c) 2016-2017 xtaci
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package smux
import (
@ -38,16 +60,18 @@ const (
// Frame defines a packet from or to be multiplexed into a single connection
type Frame struct {
ver byte
cmd byte
sid uint32
data []byte
ver byte // version
cmd byte // command
sid uint32 // stream id
data []byte // payload
}
// newFrame creates a new frame with given version, command and stream id
func newFrame(version byte, cmd byte, sid uint32) Frame {
return Frame{ver: version, cmd: cmd, sid: sid}
}
// rawHeader is a byte array representation of Frame header
type rawHeader [headerSize]byte
func (h rawHeader) Version() byte {
@ -71,6 +95,7 @@ func (h rawHeader) String() string {
h.Version(), h.Cmd(), h.StreamID(), h.Length())
}
// updHeader is a byte array representation of cmdUPD
type updHeader [szCmdUPD]byte
func (h updHeader) Consumed() uint32 {

View File

@ -1,7 +1,25 @@
// Package smux is a multiplexing library for Golang.
// MIT License
//
// It relies on an underlying connection to provide reliability and ordering, such as TCP or KCP,
// and provides stream-oriented multiplexing over a single channel.
// Copyright (c) 2016-2017 xtaci
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package smux
import (

View File

@ -1,48 +1,89 @@
// MIT License
//
// Copyright (c) 2016-2017 xtaci
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package smux
import (
"container/heap"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"os"
"runtime"
"sync"
"sync/atomic"
"time"
"github.com/nadoo/glider/pool"
"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 = errors.New("timeout")
ErrTimeout = fmt.Errorf("smux: %w", os.ErrDeadlineExceeded)
ErrWouldBlock = errors.New("operation would block on IO")
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 {
prio uint32
class CLASSID
frame Frame
seq uint32
result chan writeResult
}
// writeResult represents the result of a write request
type writeResult struct {
n int
err error
}
type buffersWriter interface {
WriteBuffers(v [][]byte) (n int, err error)
}
// Session defines a multiplexed connection for streams
type Session struct {
conn io.ReadWriteCloser
@ -54,7 +95,7 @@ type Session struct {
bucket int32 // token bucket
bucketNotify chan struct{} // used for waiting for tokens
streams map[uint32]*Stream // all streams in this session
streams map[uint32]*stream // all streams in this session
streamLock sync.Mutex // locks streams
die chan struct{} // flag session has died
@ -73,7 +114,7 @@ type Session struct {
chProtoError chan struct{}
protoErrorOnce sync.Once
chAccepts chan *Stream
chAccepts chan *stream
dataReady int32 // flag data has arrived
@ -81,8 +122,9 @@ type Session struct {
deadline atomic.Value
shaper chan writeRequest // a shaper for writing
writes chan writeRequest
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 {
@ -90,8 +132,8 @@ func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session {
s.die = make(chan struct{})
s.conn = conn
s.config = config
s.streams = make(map[uint32]*Stream)
s.chAccepts = make(chan *Stream, defaultAcceptBacklog)
s.streams = make(map[uint32]*stream)
s.chAccepts = make(chan *stream, defaultAcceptBacklog)
s.bucket = int32(config.MaxReceiveBuffer)
s.bucketNotify = make(chan struct{}, 1)
s.shaper = make(chan writeRequest)
@ -139,7 +181,7 @@ func (s *Session) OpenStream() (*Stream, error) {
stream := newStream(sid, s.config.MaxFrameSize, s)
if _, err := s.writeFrame(newFrame(byte(s.config.Version), cmdSYN, sid)); err != nil {
if _, err := s.writeControlFrame(newFrame(byte(s.config.Version), cmdSYN, sid)); err != nil {
return nil, err
}
@ -154,7 +196,14 @@ func (s *Session) OpenStream() (*Stream, error) {
return nil, io.ErrClosedPipe
default:
s.streams[sid] = stream
return stream, nil
wrapper := &Stream{stream: stream}
// NOTE(x): disabled finalizer for issue #997
/*
runtime.SetFinalizer(wrapper, func(s *Stream) {
s.Close()
})
*/
return wrapper, nil
}
}
@ -175,7 +224,11 @@ func (s *Session) AcceptStream() (*Stream, error) {
select {
case stream := <-s.chAccepts:
return stream, nil
wrapper := &Stream{stream: stream}
runtime.SetFinalizer(wrapper, func(s *Stream) {
s.Close()
})
return wrapper, nil
case <-deadline:
return nil, ErrTimeout
case <-s.chSocketReadError:
@ -212,6 +265,12 @@ func (s *Session) Close() error {
}
}
// CloseChan can be used by someone who wants to be notified immediately when this
// session is closed
func (s *Session) CloseChan() <-chan struct{} {
return s.die
}
// notifyBucket notifies recvLoop that bucket is available
func (s *Session) notifyBucket() {
select {
@ -291,12 +350,15 @@ func (s *Session) RemoteAddr() net.Addr {
// notify the session that a stream has closed
func (s *Session) streamClosed(sid uint32) {
s.streamLock.Lock()
if n := s.streams[sid].recycleTokens(); n > 0 { // return remaining tokens to the bucket
if atomic.AddInt32(&s.bucket, int32(n)) > 0 {
s.notifyBucket()
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)
}
delete(s.streams, sid)
s.streamLock.Unlock()
}
@ -331,7 +393,7 @@ func (s *Session) recvLoop() {
sid := hdr.StreamID()
switch hdr.Cmd() {
case cmdNOP:
case cmdSYN:
case cmdSYN: // stream opening
s.streamLock.Lock()
if _, ok := s.streams[sid]; !ok {
stream := newStream(sid, s.config.MaxFrameSize, s)
@ -342,22 +404,26 @@ func (s *Session) recvLoop() {
}
}
s.streamLock.Unlock()
case cmdFIN:
case cmdFIN: // stream closing
s.streamLock.Lock()
if stream, ok := s.streams[sid]; ok {
stream.fin()
stream.notifyReadEvent()
}
s.streamLock.Unlock()
case cmdPSH:
case cmdPSH: // data frame
if hdr.Length() > 0 {
newbuf := pool.GetBuffer(int(hdr.Length()))
if written, err := io.ReadFull(s.conn, newbuf); err == nil {
s.streamLock.Lock()
if stream, ok := s.streams[sid]; ok {
stream.pushBytes(newbuf)
// a stream used some token
atomic.AddInt32(&s.bucket, -int32(written))
stream.notifyReadEvent()
} else {
// data directed to a missing/closed stream, recycle the buffer immediately.
pool.PutBuffer(newbuf)
}
s.streamLock.Unlock()
} else {
@ -365,7 +431,7 @@ func (s *Session) recvLoop() {
return
}
}
case cmdUPD:
case cmdUPD: // a window update signal
if _, err := io.ReadFull(s.conn, updHdr[:]); err == nil {
s.streamLock.Lock()
if stream, ok := s.streams[sid]; ok {
@ -387,6 +453,7 @@ func (s *Session) recvLoop() {
}
}
// keepalive sends NOP frame to peer to keep the connection alive, and detect dead peers
func (s *Session) keepalive() {
tickerPing := time.NewTicker(s.config.KeepAliveInterval)
tickerTimeout := time.NewTicker(s.config.KeepAliveTimeout)
@ -395,7 +462,7 @@ func (s *Session) keepalive() {
for {
select {
case <-tickerPing.C:
s.writeFrameInternal(newFrame(byte(s.config.Version), cmdNOP, 0), tickerPing.C, 0)
s.writeFrameInternal(newFrame(byte(s.config.Version), cmdNOP, 0), tickerPing.C, CLSCTRL)
s.notifyBucket() // force a signal to the recvLoop
case <-tickerTimeout.C:
if !atomic.CompareAndSwapInt32(&s.dataReady, 1, 0) {
@ -412,13 +479,16 @@ func (s *Session) keepalive() {
}
}
// shaper shapes the sending sequence among streams
// shaperLoop implements a priority queue for write requests,
// some control messages are prioritized over data messages
func (s *Session) shaperLoop() {
var reqs shaperHeap
var next writeRequest
var chWrite chan writeRequest
var chShaper chan writeRequest
for {
// chWrite is not available until it has packet to send
if len(reqs) > 0 {
chWrite = s.writes
next = heap.Pop(&reqs).(writeRequest)
@ -426,10 +496,22 @@ func (s *Session) shaperLoop() {
chWrite = nil
}
// control heap size, chShaper is not available until packets are less than maximum allowed
if len(reqs) >= maxShaperSize {
chShaper = nil
} else {
chShaper = s.shaper
}
// assertion on non nil
if chShaper == nil && chWrite == nil {
panic("both channel are nil")
}
select {
case <-s.die:
return
case r := <-s.shaper:
case r := <-chShaper:
if chWrite != nil { // next is valid, reshape
heap.Push(&reqs, next)
}
@ -439,13 +521,17 @@ func (s *Session) shaperLoop() {
}
}
// sendLoop sends frames to the underlying connection
func (s *Session) sendLoop() {
var buf []byte
var n int
var err error
var vec [][]byte // vector for writeBuffers
bw, ok := s.conn.(buffersWriter)
bw, ok := s.conn.(interface {
WriteBuffers(v [][]byte) (n int, err error)
})
if ok {
buf = make([]byte, headerSize)
vec = make([][]byte, 2)
@ -463,6 +549,7 @@ func (s *Session) sendLoop() {
binary.LittleEndian.PutUint16(buf[2:], uint16(len(request.frame.data)))
binary.LittleEndian.PutUint32(buf[4:], request.frame.sid)
// support for scatter-gather I/O
if len(vec) > 0 {
vec[0] = buf[:headerSize]
vec[1] = request.frame.data
@ -494,17 +581,21 @@ func (s *Session) sendLoop() {
}
}
// writeFrame writes the frame to the underlying connection
// writeControlFrame writes the control frame to the underlying connection
// and returns the number of bytes written if successful
func (s *Session) writeFrame(f Frame) (n int, err error) {
return s.writeFrameInternal(f, nil, 0)
func (s *Session) writeControlFrame(f Frame) (n int, err error) {
timer := time.NewTimer(openCloseTimeout)
defer timer.Stop()
return s.writeFrameInternal(f, timer.C, CLSCTRL)
}
// internal writeFrame version to support deadline used in keepalive
func (s *Session) writeFrameInternal(f Frame, deadline <-chan time.Time, prio uint32) (int, error) {
func (s *Session) writeFrameInternal(f Frame, deadline <-chan time.Time, class CLASSID) (int, error) {
req := writeRequest{
prio: prio,
class: class,
frame: f,
seq: atomic.AddUint32(&s.requestID, 1),
result: make(chan writeResult, 1),
}
select {

56
pkg/smux/shaper.go Normal file
View File

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

View File

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

31
pkg/sockopt/sockopt.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,7 +10,7 @@ import (
"sync"
"time"
"github.com/nadoo/glider/pool"
"github.com/nadoo/glider/pkg/pool"
)
var (
@ -175,19 +175,30 @@ func CopyBuffer(dst io.Writer, src io.Reader) (written int64, err error) {
return written, err
}
// RelayUDP copys from src to dst at target with read timeout.
func RelayUDP(dst net.PacketConn, target net.Addr, src net.PacketConn, timeout time.Duration) error {
b := pool.GetBuffer(UDPBufSize)
defer pool.PutBuffer(b)
// CopyUDP copys from src to dst at target with read timeout.
// if step sets to non-zero value,
// the read timeout will be increased from 0 to timeout by step in every read operation.
func CopyUDP(dst net.PacketConn, writeTo net.Addr, src net.PacketConn, timeout time.Duration, step time.Duration) error {
buf := pool.GetBuffer(UDPBufSize)
defer pool.PutBuffer(buf)
var t time.Duration
for {
src.SetReadDeadline(time.Now().Add(timeout))
n, _, err := src.ReadFrom(b)
if t += step; t == 0 || t > timeout {
t = timeout
}
src.SetReadDeadline(time.Now().Add(t))
n, addr, err := src.ReadFrom(buf)
if err != nil {
return err
}
_, err = dst.WriteTo(b[:n], target)
if writeTo != nil {
addr = writeTo
}
_, err = dst.WriteTo(buf[:n], addr)
if err != nil {
return err
}

View File

@ -3,6 +3,7 @@ package proxy
import (
"errors"
"net"
"sort"
"strings"
)
@ -32,7 +33,7 @@ type UDPDialer interface {
Addr() string
// DialUDP connects to the given address
DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error)
DialUDP(network, addr string) (pc net.PacketConn, err error)
}
// DialerCreator is a function to create dialers.
@ -66,3 +67,13 @@ func DialerFromURL(s string, dialer Dialer) (Dialer, error) {
return nil, errors.New("unknown scheme '" + scheme + "'")
}
// DialerSchemes returns the registered dialer schemes.
func DialerSchemes() string {
s := make([]string, 0, len(dialerCreators))
for name := range dialerCreators {
s = append(s, name)
}
sort.Strings(s)
return strings.Join(s, " ")
}

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ import (
"net/url"
"strings"
"github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/log"
)
// Methods are http methods from rfc.

View File

@ -8,8 +8,8 @@ import (
"strings"
"time"
"github.com/nadoo/glider/log"
"github.com/nadoo/glider/pool"
"github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/pkg/pool"
"github.com/nadoo/glider/proxy"
)

View File

@ -12,7 +12,7 @@ import (
kcp "github.com/xtaci/kcp-go/v5"
"golang.org/x/crypto/pbkdf2"
"github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/proxy"
)
@ -239,8 +239,8 @@ func (s *KCP) Dial(network, addr string) (net.Conn, error) {
}
// DialUDP connects to the given address via the proxy.
func (s *KCP) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
return nil, nil, proxy.ErrNotSupported
func (s *KCP) DialUDP(network, addr string) (net.PacketConn, error) {
return nil, proxy.ErrNotSupported
}
func (s *KCP) setParams(c *kcp.UDPSession) {
@ -266,3 +266,16 @@ func (s *KCP) setParams(c *kcp.UDPSession) {
c.SetMtu(1350)
c.SetACKNoDelay(true)
}
func init() {
proxy.AddUsage("kcp", `
KCP scheme:
kcp://CRYPT:KEY@host:port[?dataShards=NUM&parityShards=NUM&mode=MODE]
Available crypt types for KCP:
none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20
Available modes for KCP:
fast, fast2, fast3, normal, default: fast
`)
}

View File

@ -4,7 +4,7 @@ import (
"net"
"net/url"
"github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/proxy/http"
"github.com/nadoo/glider/proxy/socks5"
@ -64,7 +64,7 @@ func (m *Mixed) ListenAndServe() {
return
}
log.F("[mixed] listening TCP on %s", m.addr)
log.F("[mixed] http & socks5 server listening TCP on %s", m.addr)
for {
c, err := l.Accept()

View File

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

View File

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

View File

@ -17,7 +17,7 @@ import (
"net"
"time"
"github.com/nadoo/glider/pool"
"github.com/nadoo/glider/pkg/pool"
)
const (
@ -64,10 +64,7 @@ func (c *TLSObfsConn) Write(b []byte) (int, error) {
n := len(b)
for i := 0; i < n; i += chunkSize {
buf.Reset()
end := i + chunkSize
if end > n {
end = n
}
end := min(i+chunkSize, n)
buf.Write([]byte{0x17, 0x03, 0x03})
binary.Write(buf, binary.BigEndian, uint16(len(b[i:end])))

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2016-2017 Daniel Fu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,86 +0,0 @@
package smux
import (
"bytes"
"testing"
)
type buffer struct {
bytes.Buffer
}
func (b *buffer) Close() error {
b.Buffer.Reset()
return nil
}
func TestConfig(t *testing.T) {
VerifyConfig(DefaultConfig())
config := DefaultConfig()
config.KeepAliveInterval = 0
err := VerifyConfig(config)
t.Log(err)
if err == nil {
t.Fatal(err)
}
config = DefaultConfig()
config.KeepAliveInterval = 10
config.KeepAliveTimeout = 5
err = VerifyConfig(config)
t.Log(err)
if err == nil {
t.Fatal(err)
}
config = DefaultConfig()
config.MaxFrameSize = 0
err = VerifyConfig(config)
t.Log(err)
if err == nil {
t.Fatal(err)
}
config = DefaultConfig()
config.MaxFrameSize = 65536
err = VerifyConfig(config)
t.Log(err)
if err == nil {
t.Fatal(err)
}
config = DefaultConfig()
config.MaxReceiveBuffer = 0
err = VerifyConfig(config)
t.Log(err)
if err == nil {
t.Fatal(err)
}
config = DefaultConfig()
config.MaxStreamBuffer = 0
err = VerifyConfig(config)
t.Log(err)
if err == nil {
t.Fatal(err)
}
config = DefaultConfig()
config.MaxStreamBuffer = 100
config.MaxReceiveBuffer = 99
err = VerifyConfig(config)
t.Log(err)
if err == nil {
t.Fatal(err)
}
var bts buffer
if _, err := Server(&bts, config); err == nil {
t.Fatal("server started with wrong config")
}
if _, err := Client(&bts, config); err == nil {
t.Fatal("client started with wrong config")
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +0,0 @@
package smux
func _itimediff(later, earlier uint32) int32 {
return (int32)(later - earlier)
}
type shaperHeap []writeRequest
func (h shaperHeap) Len() int { return len(h) }
func (h shaperHeap) Less(i, j int) bool { return _itimediff(h[j].prio, h[i].prio) > 0 }
func (h shaperHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *shaperHeap) Push(x 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,32 +0,0 @@
package smux
import (
"container/heap"
"testing"
)
func TestShaper(t *testing.T) {
w1 := writeRequest{prio: 10}
w2 := writeRequest{prio: 10}
w3 := writeRequest{prio: 20}
w4 := writeRequest{prio: 100}
w5 := writeRequest{prio: (1 << 32) - 1}
var reqs shaperHeap
heap.Push(&reqs, w5)
heap.Push(&reqs, w4)
heap.Push(&reqs, w3)
heap.Push(&reqs, w2)
heap.Push(&reqs, w1)
var lastPrio = reqs[0].prio
for len(reqs) > 0 {
w := heap.Pop(&reqs).(writeRequest)
if int32(w.prio-lastPrio) < 0 {
t.Fatal("incorrect shaper priority")
}
t.Log("prio:", w.prio)
lastPrio = w.prio
}
}

View File

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

View File

@ -7,7 +7,7 @@ import (
"net/url"
"strings"
"github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/proxy"
)

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ package proxy
import (
"errors"
"net"
"sort"
"strings"
)
@ -15,6 +16,11 @@ type Server interface {
Serve(c net.Conn)
}
// PacketServer interface.
type PacketServer interface {
ServePacket(pc net.PacketConn)
}
// ServerCreator is a function to create proxy servers.
type ServerCreator func(s string, proxy Proxy) (Server, error)
@ -28,7 +34,7 @@ func RegisterServer(name string, c ServerCreator) {
}
// ServerFromURL calls the registered creator to create proxy servers.
// dialer is the default upstream dialer so cannot be nil, we can use Default when calling this function.
// proxy can not be nil.
func ServerFromURL(s string, proxy Proxy) (Server, error) {
if proxy == nil {
return nil, errors.New("ServerFromURL: dialer cannot be nil")
@ -46,3 +52,13 @@ func ServerFromURL(s string, proxy Proxy) (Server, error) {
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,15 +1,13 @@
package smux
import (
"errors"
"net"
"net/url"
"sync"
"github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/pkg/smux"
"github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/proxy/protocol/smux"
)
// SmuxClient struct.
@ -66,8 +64,8 @@ func (s *SmuxClient) Dial(network, addr string) (net.Conn, error) {
}
// DialUDP connects to the given address via the proxy.
func (s *SmuxClient) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
return nil, nil, errors.New("smux client does not support udp now")
func (s *SmuxClient) DialUDP(network, addr string) (net.PacketConn, error) {
return nil, proxy.ErrNotSupported
}
func (s *SmuxClient) initConn() error {

View File

@ -5,10 +5,9 @@ import (
"net/url"
"strings"
"github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/pkg/smux"
"github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/proxy/protocol/smux"
)
// SmuxServer struct.

10
proxy/smux/smux.go Normal file
View File

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

View File

@ -11,8 +11,8 @@ import (
"net/url"
"strconv"
"github.com/nadoo/glider/log"
"github.com/nadoo/glider/pool"
"github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/pkg/pool"
"github.com/nadoo/glider/proxy"
)
@ -88,8 +88,8 @@ func (s *SOCKS4) Dial(network, addr string) (net.Conn, error) {
}
// DialUDP connects to the given address via the proxy.
func (s *SOCKS4) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
return nil, nil, proxy.ErrNotSupported
func (s *SOCKS4) DialUDP(network, addr string) (pc net.PacketConn, err error) {
return nil, proxy.ErrNotSupported
}
func (s *SOCKS4) lookupIP(host string) (ip net.IP, err error) {
@ -118,13 +118,10 @@ func (s *SOCKS4) connect(conn net.Conn, target string) error {
return err
}
port, err := strconv.Atoi(portStr)
port, err := strconv.ParseUint(portStr, 10, 16)
if err != nil {
return errors.New("[socks4] failed to parse port number: " + portStr)
}
if port < 1 || port > 0xffff {
return errors.New("[socks4] port number out of range: " + portStr)
}
const baseBufSize = 8 + 1 // 1 is the len(userid)
bufSize := baseBufSize
@ -189,3 +186,10 @@ func (s *SOCKS4) connect(conn net.Conn, target string) error {
return err
}
func init() {
proxy.AddUsage("socks4", `
Socks4 scheme:
socks4://host:port
`)
}

View File

@ -4,14 +4,19 @@ import (
"errors"
"io"
"net"
"net/netip"
"strconv"
"github.com/nadoo/glider/log"
"github.com/nadoo/glider/pool"
"github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/pkg/pool"
"github.com/nadoo/glider/pkg/socks"
"github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/proxy/protocol/socks"
)
func init() {
proxy.RegisterDialer("socks5", NewSocks5Dialer)
}
// NewSocks5Dialer returns a socks5 proxy dialer.
func NewSocks5Dialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
return NewSocks5(s, d, nil)
@ -58,64 +63,55 @@ func (s *Socks5) dial(network, addr string) (net.Conn, error) {
}
// DialUDP connects to the given address via the proxy.
func (s *Socks5) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
func (s *Socks5) DialUDP(network, addr string) (pc net.PacketConn, err error) {
c, err := s.dial("tcp", s.addr)
if err != nil {
log.F("[socks5] dialudp dial tcp to %s error: %s", s.addr, err)
return nil, nil, err
return nil, err
}
var uAddr socks.Addr
if uAddr, err = s.connect(c, addr, socks.CmdUDPAssociate); err != nil {
c.Close()
return nil, nil, err
return nil, err
}
buf := pool.GetBuffer(socks.MaxAddrLen)
defer pool.PutBuffer(buf)
var uAddress string
h, p, _ := net.SplitHostPort(uAddr.String())
uAddress := uAddr.String()
h, p, _ := net.SplitHostPort(uAddress)
// if returned bind ip is unspecified
if ip := net.ParseIP(h); ip != nil && ip.IsUnspecified() {
if ip, err := netip.ParseAddr(h); err == nil && ip.IsUnspecified() {
// indicate using conventional addr
h, _, _ = net.SplitHostPort(s.addr)
uAddress = net.JoinHostPort(h, p)
} else {
uAddress = uAddr.String()
}
pc, nextHop, err := s.dialer.DialUDP(network, uAddress)
pc, err = s.dialer.DialUDP(network, uAddress)
if err != nil {
log.F("[socks5] dialudp to %s error: %s", uAddress, err)
return nil, nil, err
return nil, err
}
pkc := NewPktConn(pc, nextHop, socks.ParseAddr(addr), true, c)
return pkc, nextHop, err
writeTo, err := net.ResolveUDPAddr("udp", uAddress)
if err != nil {
log.F("[socks5] resolve addr error: %s", err)
return nil, err
}
return NewPktConn(pc, writeTo, socks.ParseAddr(addr), c), err
}
// connect takes an existing connection to a socks5 proxy server,
// and commands the server to extend that connection to target,
// which must be a canonical address with a host and port.
func (s *Socks5) connect(conn net.Conn, target string, cmd byte) (addr socks.Addr, err error) {
host, portStr, err := net.SplitHostPort(target)
if err != nil {
return
}
port, err := strconv.Atoi(portStr)
if err != nil {
return addr, errors.New("proxy: failed to parse port number: " + portStr)
}
if port < 1 || port > 0xffff {
return addr, errors.New("proxy: port number out of range: " + portStr)
}
// the size here is just an estimate
buf := make([]byte, 0, 6+len(host))
buf := pool.GetBuffer(socks.MaxAddrLen)
defer pool.PutBuffer(buf)
buf = append(buf, Version)
buf = append(buf[:0], Version)
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
buf = append(buf, 2 /* num auth methods */, socks.AuthNone, socks.AuthPassword)
} else {
@ -159,24 +155,7 @@ func (s *Socks5) connect(conn net.Conn, target string, cmd byte) (addr socks.Add
buf = buf[:0]
buf = append(buf, Version, cmd, 0 /* reserved */)
if ip := net.ParseIP(host); ip != nil {
if ip4 := ip.To4(); ip4 != nil {
buf = append(buf, socks.ATypIP4)
ip = ip4
} else {
buf = append(buf, socks.ATypIP6)
}
buf = append(buf, ip...)
} else {
if len(host) > 255 {
return addr, errors.New("proxy: destination hostname too long: " + host)
}
buf = append(buf, socks.ATypDomain)
buf = append(buf, byte(len(host)))
buf = append(buf, host...)
}
buf = append(buf, byte(port>>8), byte(port))
buf = append(buf, socks.ParseAddr(target)...)
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())

View File

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

View File

@ -8,14 +8,18 @@ import (
"sync"
"time"
"github.com/nadoo/glider/log"
"github.com/nadoo/glider/pool"
"github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/pkg/pool"
"github.com/nadoo/glider/pkg/socks"
"github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/proxy/protocol/socks"
)
var nm sync.Map
func init() {
proxy.RegisterServer("socks5", NewSocks5Server)
}
// NewSocks5Server returns a socks5 proxy server.
func NewSocks5Server(s string, p proxy.Proxy) (proxy.Server, error) {
return NewSocks5(s, nil, p)
@ -106,11 +110,16 @@ func (s *Socks5) ListenAndServeUDP() {
log.F("[socks5] listening UDP on %s", s.addr)
s.ServePacket(lc)
}
// ServePacket implements proxy.PacketServer.
func (s *Socks5) ServePacket(pc net.PacketConn) {
for {
c := NewPktConn(lc, nil, nil, true, nil)
c := NewPktConn(pc, nil, nil, nil)
buf := pool.GetBuffer(proxy.UDPBufSize)
n, srcAddr, err := c.ReadFrom(buf)
n, srcAddr, dstAddr, err := c.readFrom(buf)
if err != nil {
log.F("[socks5u] remote read error: %v", err)
continue
@ -121,65 +130,73 @@ func (s *Socks5) ListenAndServeUDP() {
v, ok := nm.Load(sessionKey)
if !ok || v == nil {
session = newSession(sessionKey, srcAddr, c)
session = newSession(sessionKey, srcAddr, dstAddr, c)
nm.Store(sessionKey, session)
go s.serveSession(session)
} else {
session = v.(*Session)
}
session.msgCh <- buf[:n]
session.msgCh <- message{dstAddr, buf[:n]}
}
}
func (s *Socks5) serveSession(session *Session) {
dstC, dialer, writeTo, err := s.proxy.DialUDP("udp", session.srcPC.tgtAddr.String())
dstPC, dialer, err := s.proxy.DialUDP("udp", session.srcPC.target.String())
if err != nil {
log.F("[socks5u] remote dial error: %v", err)
nm.Delete(session.key)
return
}
dstPC := NewPktConn(dstC, writeTo, nil, false, nil)
defer dstPC.Close()
go func() {
proxy.RelayUDP(session.srcPC, session.src, dstPC, 2*time.Minute)
proxy.CopyUDP(session.srcPC, nil, dstPC, 2*time.Minute, 5*time.Second)
nm.Delete(session.key)
close(session.finCh)
}()
log.F("[socks5u] %s <-> %s via %s", session.src, session.srcPC.tgtAddr, dialer.Addr())
log.F("[socks5u] %s <-> %s via %s", session.src, session.srcPC.target, dialer.Addr())
for {
select {
case p := <-session.msgCh:
_, err = dstPC.WriteTo(p, writeTo)
case msg := <-session.msgCh:
_, err = dstPC.WriteTo(msg.msg, msg.dst)
if err != nil {
log.F("[socks5u] writeTo %s error: %v", writeTo, err)
log.F("[socks5u] writeTo %s error: %v", msg.dst, err)
}
pool.PutBuffer(p)
pool.PutBuffer(msg.msg)
msg.msg = nil
case <-session.finCh:
return
}
}
}
type message struct {
dst net.Addr
msg []byte
}
// Session is a udp session
type Session struct {
key string
src net.Addr
dst net.Addr
srcPC *PktConn
msgCh chan []byte
msgCh chan message
finCh chan struct{}
}
func newSession(key string, src net.Addr, srcPC *PktConn) *Session {
return &Session{key, src, srcPC, make(chan []byte, 32), make(chan struct{})}
func newSession(key string, src, dst net.Addr, srcPC *PktConn) *Session {
return &Session{key, src, dst, srcPC, make(chan message, 32), make(chan struct{})}
}
// Handshake fast-tracks SOCKS initialization to get target address to connect.
func (s *Socks5) handshake(c net.Conn) (socks.Addr, error) {
// Read RFC 1928 for request and reply structure and sizes
buf := make([]byte, socks.MaxAddrLen)
buf := pool.GetBuffer(socks.MaxAddrLen)
defer pool.PutBuffer(buf)
// read VER, NMETHODS, METHODS
if _, err := io.ReadFull(c, buf[:2]); err != nil {
@ -257,7 +274,7 @@ func (s *Socks5) handshake(c net.Conn) (socks.Addr, error) {
return nil, err
}
cmd := buf[1]
addr, err := socks.ReadAddrBuf(c, buf)
addr, err := socks.ReadAddr(c)
if err != nil {
return nil, err
}
@ -266,6 +283,9 @@ func (s *Socks5) handshake(c net.Conn) (socks.Addr, error) {
_, err = c.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) // SOCKS v5, reply succeeded
case socks.CmdUDPAssociate:
listenAddr := socks.ParseAddr(c.LocalAddr().String())
if listenAddr == nil { // maybe it's unix socket
listenAddr = socks.ParseAddr("127.0.0.1:0")
}
_, err = c.Write(append([]byte{5, 0, 0}, listenAddr...)) // SOCKS v5, reply succeeded
if err != nil {
return nil, socks.Errors[7]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,10 +7,10 @@ import (
"sync"
"time"
"github.com/nadoo/glider/log"
"github.com/nadoo/glider/pool"
"github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/pkg/pool"
"github.com/nadoo/glider/pkg/socks"
"github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/proxy/protocol/socks"
)
var nm sync.Map
@ -59,15 +59,13 @@ func (s *SS) Serve(c net.Conn) {
tgt, err := socks.ReadAddr(sc)
if err != nil {
log.F("[ss] failed to get target address: %v", err)
log.F("[ss] %s <-> target error: %v", c.RemoteAddr(), err)
proxy.Copy(io.Discard, c) // https://github.com/nadoo/glider/issues/180
return
}
network := "tcp"
dialer := s.proxy.NextDialer(tgt.String())
rc, err := dialer.Dial(network, tgt.String())
rc, err := dialer.Dial("tcp", tgt.String())
if err != nil {
log.F("[ss] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
return
@ -96,12 +94,17 @@ func (s *SS) ListenAndServeUDP() {
log.F("[ss] listening UDP on %s", s.addr)
lc = s.PacketConn(lc)
s.ServePacket(lc)
}
// ServePacket implements proxy.PacketServer.
func (s *SS) ServePacket(pc net.PacketConn) {
lc := s.PacketConn(pc)
for {
c := NewPktConn(lc, nil, nil, true)
c := NewPktConn(lc, nil, nil)
buf := pool.GetBuffer(proxy.UDPBufSize)
n, srcAddr, err := c.ReadFrom(buf)
n, srcAddr, dstAddr, err := c.readFrom(buf)
if err != nil {
log.F("[ssu] remote read error: %v", err)
continue
@ -112,57 +115,64 @@ func (s *SS) ListenAndServeUDP() {
v, ok := nm.Load(sessionKey)
if !ok || v == nil {
session = newSession(sessionKey, srcAddr, c)
session = newSession(sessionKey, srcAddr, dstAddr, c)
nm.Store(sessionKey, session)
go s.serveSession(session)
} else {
session = v.(*Session)
}
session.msgCh <- buf[:n]
session.msgCh <- message{dstAddr, buf[:n]}
}
}
func (s *SS) serveSession(session *Session) {
dstC, dialer, writeTo, err := s.proxy.DialUDP("udp", session.srcPC.tgtAddr.String())
dstPC, dialer, err := s.proxy.DialUDP("udp", session.dst.String())
if err != nil {
log.F("[ssu] remote dial error: %v", err)
nm.Delete(session.key)
return
}
dstPC := NewPktConn(dstC, writeTo, nil, false)
defer dstPC.Close()
go func() {
proxy.RelayUDP(session.srcPC, session.src, dstPC, 2*time.Minute)
proxy.CopyUDP(session.srcPC, nil, dstPC, 2*time.Minute, 5*time.Second)
nm.Delete(session.key)
close(session.finCh)
}()
log.F("[ssu] %s <-> %s via %s", session.src, session.srcPC.tgtAddr, dialer.Addr())
log.F("[ssu] %s <-> %s via %s", session.src, session.dst, dialer.Addr())
for {
select {
case p := <-session.msgCh:
_, err = dstPC.WriteTo(p, writeTo)
case msg := <-session.msgCh:
_, err = dstPC.WriteTo(msg.msg, msg.dst)
if err != nil {
log.F("[ssu] writeTo %s error: %v", writeTo, err)
log.F("[ssu] writeTo %s error: %v", msg.dst, err)
}
pool.PutBuffer(p)
pool.PutBuffer(msg.msg)
msg.msg = nil
case <-session.finCh:
return
}
}
}
type message struct {
dst net.Addr
msg []byte
}
// Session is a udp session
type Session struct {
key string
src net.Addr
dst net.Addr
srcPC *PktConn
msgCh chan []byte
msgCh chan message
finCh chan struct{}
}
func newSession(key string, src net.Addr, srcPC *PktConn) *Session {
return &Session{key, src, srcPC, make(chan []byte, 32), make(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 (
"net/url"
"github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/proxy/ss/cipher"
)
@ -48,3 +48,19 @@ func NewSS(s string, d proxy.Dialer, p proxy.Proxy) (*SS, error) {
return ss, nil
}
func init() {
proxy.AddUsage("ss", `
SS scheme:
ss://method:pass@host:port
Available methods for ss:
AEAD Ciphers:
AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AEAD_XCHACHA20_POLY1305
Stream Ciphers:
AES-128-CFB AES-128-CTR AES-192-CFB AES-192-CTR AES-256-CFB AES-256-CTR CHACHA20-IETF XCHACHA20 CHACHA20 RC4-MD5
Alias:
chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305
Plain: NONE
`)
}

View File

@ -1,6 +1,7 @@
package ssh
import (
"errors"
"net"
"net/url"
"os"
@ -10,7 +11,7 @@ import (
"golang.org/x/crypto/ssh"
"github.com/nadoo/glider/log"
"github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/proxy"
)
@ -20,10 +21,12 @@ type SSH struct {
proxy proxy.Proxy
addr string
mu sync.Mutex
conn net.Conn
client *ssh.Client
config *ssh.ClientConfig
once sync.Once
mutex sync.RWMutex
}
func init() {
@ -103,20 +106,14 @@ func (s *SSH) Addr() string {
// Dial connects to the address addr on the network net via the proxy.
func (s *SSH) Dial(network, addr string) (net.Conn, error) {
s.mu.Lock()
defer s.mu.Unlock()
s.once.Do(func() { go s.keepConn(s.initConn() == nil) })
if s.client != nil {
if c, err := s.dial(network, addr); err == nil {
return c, nil
}
s.conn.Close()
s.mutex.RLock()
defer s.mutex.RUnlock()
if s.client == nil {
return nil, errors.New("ssh client is nil")
}
if err := s.initConn(); err != nil {
return nil, err
}
return s.dial(network, addr)
}
@ -128,29 +125,55 @@ func (s *SSH) dial(network, addr string) (net.Conn, error) {
}
func (s *SSH) initConn() error {
s.mutex.Lock()
defer s.mutex.Unlock()
log.F("[ssh] connecting to %s", s.addr)
c, err := s.dialer.Dial("tcp", s.addr)
if err != nil {
log.F("[ssh]: dial to %s error: %s", s.addr, err)
log.F("[ssh] dial connection to %s error: %s", s.addr, err)
return err
}
c.SetDeadline(time.Now().Add(s.config.Timeout))
sshConn, sshChan, sshReq, err := ssh.NewClientConn(c, s.addr, s.config)
conn, ch, req, err := ssh.NewClientConn(c, s.addr, s.config)
if err != nil {
log.F("[ssh]: initial connection to %s error: %s", s.addr, err)
log.F("[ssh] initial connection to %s error: %s", s.addr, err)
c.Close()
return err
}
c.SetDeadline(time.Time{})
s.conn = c
s.client = ssh.NewClient(sshConn, sshChan, sshReq)
s.client = ssh.NewClient(conn, ch, req)
return nil
}
func (s *SSH) keepConn(connected bool) {
if connected {
s.client.Conn.Wait()
s.conn.Close()
}
sleep := time.Second
for {
if err := s.initConn(); err != nil {
sleep *= 2
if sleep > time.Second*60 {
sleep = time.Second * 60
}
time.Sleep(sleep)
continue
}
sleep = time.Second
s.client.Conn.Wait()
s.conn.Close()
}
}
// DialUDP connects to the given address via the proxy.
func (s *SSH) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
return nil, nil, proxy.ErrNotSupported
func (s *SSH) DialUDP(network, addr string) (pc net.PacketConn, err error) {
return nil, proxy.ErrNotSupported
}
func privateKeyAuth(file string) (ssh.AuthMethod, error) {
@ -166,3 +189,11 @@ func privateKeyAuth(file string) (ssh.AuthMethod, error) {
return ssh.PublicKeys(key), nil
}
func init() {
proxy.AddUsage("ssh", `
SSH scheme:
ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS]
timeout: timeout of ssh handshake and channel operation, default: 5
`)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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