mirror of
https://github.com/nadoo/glider.git
synced 2025-04-30 16:09:56 +08:00
Compare commits
No commits in common. "main" and "v0.9.2" have entirely different histories.
38
.Dockerfile
38
.Dockerfile
@ -1,38 +0,0 @@
|
|||||||
# Build Stage
|
|
||||||
FROM --platform=$BUILDPLATFORM alpine AS build-env
|
|
||||||
|
|
||||||
COPY ./dist /dist
|
|
||||||
RUN apk add --no-cache ca-certificates
|
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
|
||||||
RUN case $TARGETPLATFORM in \
|
|
||||||
'linux/386') \
|
|
||||||
export FOLDER='default_linux_386_sse2'; \
|
|
||||||
;; \
|
|
||||||
'linux/amd64') \
|
|
||||||
export FOLDER='default_linux_amd64_v1'; \
|
|
||||||
;; \
|
|
||||||
'linux/arm/v6') \
|
|
||||||
export FOLDER='default_linux_arm_6'; \
|
|
||||||
;; \
|
|
||||||
'linux/arm/v7') \
|
|
||||||
export FOLDER='default_linux_arm_7'; \
|
|
||||||
;; \
|
|
||||||
'linux/arm64') \
|
|
||||||
export FOLDER='default_linux_arm64_v8.0'; \
|
|
||||||
;; \
|
|
||||||
'linux/riscv64') \
|
|
||||||
export FOLDER='default_linux_riscv64_rva20u64'; \
|
|
||||||
;; \
|
|
||||||
*) echo >&2 "error: unsupported architecture '$TARGETPLATFORM'"; exit 1 ;; \
|
|
||||||
esac \
|
|
||||||
&& mv /dist/$FOLDER /app
|
|
||||||
|
|
||||||
|
|
||||||
# Final Stage
|
|
||||||
FROM scratch
|
|
||||||
COPY --from=build-env /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
|
||||||
COPY --from=build-env /app /app
|
|
||||||
WORKDIR /app
|
|
||||||
USER 1000
|
|
||||||
ENTRYPOINT ["./glider"]
|
|
116
.github/workflows/build.yml
vendored
116
.github/workflows/build.yml
vendored
@ -1,103 +1,39 @@
|
|||||||
name: Build
|
name: Build
|
||||||
on:
|
on: [push]
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "dev"
|
|
||||||
tags:
|
|
||||||
- "*"
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
env:
|
|
||||||
APP_NAME: glider
|
|
||||||
DOCKERHUB_REPO: nadoo/glider
|
|
||||||
GHCR_REPO: ghcr.io/nadoo/glider
|
|
||||||
PLATFORMS: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/riscv64
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.13
|
||||||
|
id: go
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: go test -v .
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
needs: [test]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Set Vars
|
|
||||||
run: |
|
|
||||||
echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v1
|
||||||
with:
|
with:
|
||||||
check-latest: true
|
go-version: 1.13
|
||||||
go-version-file: "go.mod"
|
id: go
|
||||||
cache: true
|
|
||||||
|
|
||||||
- name: Test
|
- name: Check out code into the Go module directory
|
||||||
run: go test -v ./...
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: goreleaser/goreleaser-action@v6
|
run: go build -v .
|
||||||
if: "!startsWith(github.ref, 'refs/tags/')"
|
|
||||||
with:
|
|
||||||
args: build --snapshot --clean
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Upload Artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
if: "!startsWith(github.ref, 'refs/tags/')"
|
|
||||||
with:
|
|
||||||
name: ${{ env.APP_NAME }}-dev-${{ env.SHA_SHORT }}
|
|
||||||
path: |
|
|
||||||
./dist/default_linux_amd64_v1/${{ env.APP_NAME }}
|
|
||||||
./dist/default_linux_arm64/${{ env.APP_NAME }}
|
|
||||||
./dist/default_darwin_arm64/${{ env.APP_NAME }}
|
|
||||||
./dist/default_windows_amd64_v1/${{ env.APP_NAME }}.exe
|
|
||||||
|
|
||||||
- name: Release
|
|
||||||
uses: goreleaser/goreleaser-action@v6
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
with:
|
|
||||||
args: release --clean
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Docker - Set up Buildx
|
|
||||||
id: buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Docker - Login to DockerHub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Docker - Login to GHCR
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Docker - Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: |
|
|
||||||
${{ env.DOCKERHUB_REPO }}
|
|
||||||
${{ env.GHCR_REPO }}
|
|
||||||
tags: |
|
|
||||||
type=ref,event=branch
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
|
|
||||||
- name: Docker - Build and push
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: .Dockerfile
|
|
||||||
platforms: ${{ env.PLATFORMS }}
|
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
28
.github/workflows/release.yml
vendored
Normal file
28
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
name: Release
|
||||||
|
on:
|
||||||
|
create:
|
||||||
|
tags:
|
||||||
|
- v*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Release on GitHub
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.13
|
||||||
|
id: go
|
||||||
|
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Create release on GitHub
|
||||||
|
uses: goreleaser/goreleaser-action@v1
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
args: release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
17
.github/workflows/stale.yml
vendored
17
.github/workflows/stale.yml
vendored
@ -1,17 +0,0 @@
|
|||||||
name: 'Close stale issues'
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '30 1 * * *'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
stale:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/stale@v9
|
|
||||||
with:
|
|
||||||
stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
|
|
||||||
days-before-stale: 90
|
|
||||||
days-before-close: 5
|
|
||||||
exempt-issue-labels: "bug,enhancement"
|
|
||||||
exempt-pr-labels: "bug,enhancement"
|
|
||||||
|
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -17,14 +17,6 @@
|
|||||||
# custom
|
# custom
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
.zed
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
# dev test only
|
|
||||||
/dev/
|
|
||||||
dev*.go
|
|
||||||
|
|
||||||
*_test.go
|
|
||||||
|
|
||||||
dist
|
dist
|
||||||
|
|
||||||
|
@ -1,18 +1,30 @@
|
|||||||
version: 2
|
# Make sure to check the documentation at http://goreleaser.com
|
||||||
|
|
||||||
|
# release:
|
||||||
|
# git tag -a v0.1.0 -m "v0.1.0"
|
||||||
|
# git push origin v0.1.0
|
||||||
|
# goreleaser release --skip-publish --rm-dist
|
||||||
|
|
||||||
|
# #git tag -d v0.1.0
|
||||||
|
# #git push origin --delete tag v0.1.0
|
||||||
|
|
||||||
|
# snapshot:
|
||||||
|
# goreleaser --snapshot --rm-dist
|
||||||
|
|
||||||
|
# https://goreleaser.com/customization/
|
||||||
|
|
||||||
before:
|
before:
|
||||||
hooks:
|
hooks:
|
||||||
- go mod tidy
|
- go mod tidy
|
||||||
|
|
||||||
|
# https://goreleaser.com/build/
|
||||||
builds:
|
builds:
|
||||||
- id: default
|
- env:
|
||||||
env:
|
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
goos:
|
goos:
|
||||||
- windows
|
- windows
|
||||||
- linux
|
- linux
|
||||||
- darwin
|
- darwin
|
||||||
- freebsd
|
|
||||||
goarch:
|
goarch:
|
||||||
- 386
|
- 386
|
||||||
- amd64
|
- amd64
|
||||||
@ -22,75 +34,32 @@ builds:
|
|||||||
- mipsle
|
- mipsle
|
||||||
- mips64
|
- mips64
|
||||||
- mips64le
|
- mips64le
|
||||||
- riscv64
|
|
||||||
goamd64:
|
|
||||||
- v1
|
|
||||||
- v3
|
|
||||||
goarm:
|
goarm:
|
||||||
- 6
|
- 6
|
||||||
- 7
|
- 7
|
||||||
gomips:
|
|
||||||
- hardfloat
|
|
||||||
- softfloat
|
|
||||||
|
|
||||||
archives:
|
ignore:
|
||||||
- id: default
|
- goos: darwin
|
||||||
builds:
|
goarch: 386
|
||||||
- default
|
|
||||||
|
# https://goreleaser.com/archive/
|
||||||
|
archive:
|
||||||
|
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
||||||
wrap_in_directory: true
|
wrap_in_directory: true
|
||||||
formats: tar.gz
|
format: tar.gz
|
||||||
format_overrides:
|
format_overrides:
|
||||||
- goos: windows
|
- goos: windows
|
||||||
formats: zip
|
format: zip
|
||||||
files:
|
files:
|
||||||
- LICENSE
|
- LICENSE
|
||||||
- README.md
|
- README.md
|
||||||
- config/**/*
|
- config/**/*
|
||||||
- systemd/*
|
- systemd/*
|
||||||
|
|
||||||
|
# https://goreleaser.com/snapshots/
|
||||||
snapshot:
|
snapshot:
|
||||||
version_template: '{{ incpatch .Version }}-dev-{{.ShortCommit}}'
|
name_template: "dev@{{.ShortCommit}}"
|
||||||
|
|
||||||
|
# https://goreleaser.com/checksum/
|
||||||
checksum:
|
checksum:
|
||||||
name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
|
name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
|
||||||
|
|
||||||
release:
|
|
||||||
prerelease: true
|
|
||||||
draft: true
|
|
||||||
|
|
||||||
nfpms:
|
|
||||||
- id: glider
|
|
||||||
package_name: glider
|
|
||||||
vendor: nadoo
|
|
||||||
homepage: https://github.com/nadoo/glider
|
|
||||||
maintainer: nadoo
|
|
||||||
description: Glider is a forward proxy with multiple protocols support, and also a dns/dhcp server with ipset management features(like dnsmasq).
|
|
||||||
license: GPL-3.0 License
|
|
||||||
formats:
|
|
||||||
# - apk
|
|
||||||
- deb
|
|
||||||
# - rpm
|
|
||||||
dependencies:
|
|
||||||
- libsystemd0
|
|
||||||
bindir: /usr/bin
|
|
||||||
release: 1
|
|
||||||
epoch: 1
|
|
||||||
version_metadata: git
|
|
||||||
section: default
|
|
||||||
priority: extra
|
|
||||||
contents:
|
|
||||||
- src: systemd/glider@.service
|
|
||||||
dst: /etc/systemd/system/glider@.service
|
|
||||||
|
|
||||||
- src: config/glider.conf.example
|
|
||||||
dst: /etc/glider/glider.conf.example
|
|
||||||
|
|
||||||
scripts:
|
|
||||||
postinstall: "systemd/postinstall.sh"
|
|
||||||
preremove: "systemd/preremove.sh"
|
|
||||||
postremove: "systemd/postremove.sh"
|
|
||||||
|
|
||||||
deb:
|
|
||||||
triggers:
|
|
||||||
interest_noawait:
|
|
||||||
- /lib/systemd/systemd
|
|
||||||
|
14
Dockerfile
14
Dockerfile
@ -1,14 +0,0 @@
|
|||||||
# Build Stage
|
|
||||||
FROM golang:1.24-alpine AS build-env
|
|
||||||
ADD . /src
|
|
||||||
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/
|
|
||||||
WORKDIR /app
|
|
||||||
RUN apk -U upgrade --no-cache \
|
|
||||||
&& apk --no-cache add ca-certificates
|
|
||||||
USER 1000
|
|
||||||
ENTRYPOINT ["./glider"]
|
|
633
README.md
633
README.md
@ -1,12 +1,10 @@
|
|||||||
# [glider](https://github.com/nadoo/glider)
|
# [glider](https://github.com/nadoo/glider)
|
||||||
|
|
||||||
[](https://go.dev/dl/)
|
[](https://goreportcard.com/report/github.com/nadoo/glider)
|
||||||
[](https://goreportcard.com/report/github.com/nadoo/glider)
|
[](https://github.com/nadoo/glider/releases)
|
||||||
[](https://github.com/nadoo/glider/releases)
|
[](https://github.com/nadoo/glider/actions)
|
||||||
[](https://github.com/nadoo/glider/actions)
|
|
||||||
[](https://hub.docker.com/r/nadoo/glider)
|
|
||||||
|
|
||||||
glider is a forward proxy with multiple protocols support, and also a dns/dhcp server with ipset management features(like dnsmasq).
|
glider is a forward proxy with multiple protocols support, and also a dns forwarding server with ipset management features(like dnsmasq).
|
||||||
|
|
||||||
we can set up local listeners as proxy servers, and forward requests to internet via forwarders.
|
we can set up local listeners as proxy servers, and forward requests to internet via forwarders.
|
||||||
|
|
||||||
@ -17,307 +15,204 @@ we can set up local listeners as proxy servers, and forward requests to internet
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Act as both proxy client and proxy server(protocol converter)
|
|
||||||
- Flexible proxy & protocol chains
|
|
||||||
- Load balancing with the following scheduling algorithm:
|
|
||||||
- rr: round robin
|
|
||||||
- ha: high availability
|
|
||||||
- lha: latency based high availability
|
|
||||||
- dh: destination hashing
|
|
||||||
- Rule & priority based forwarder choosing: [Config Examples](config/examples)
|
|
||||||
- DNS forwarding server:
|
|
||||||
- dns over proxy
|
|
||||||
- force upstream querying by tcp
|
|
||||||
- association rules between dns and forwarder choosing
|
|
||||||
- association rules between dns and ipset
|
|
||||||
- dns cache support
|
|
||||||
- custom dns record
|
|
||||||
- IPSet management (linux kernel version >= 2.6.32):
|
|
||||||
- add ip/cidrs from rule files on startup
|
|
||||||
- add resolved ips for domains from rule files by dns forwarding server
|
|
||||||
- Serve http and socks5 on the same port
|
|
||||||
- Periodical availability checking for forwarders
|
|
||||||
- Send requests from specific local ip/interface
|
|
||||||
- Services:
|
|
||||||
- dhcpd: a simple dhcp server that can run in failover mode
|
|
||||||
|
|
||||||
## Protocols
|
Listen (local proxy server):
|
||||||
|
|
||||||
<details>
|
- Socks5 proxy(tcp&udp)
|
||||||
<summary>click to see details</summary>
|
- Http proxy(tcp)
|
||||||
|
- SS proxy(tcp&udp)
|
||||||
|
- Linux transparent proxy(iptables redirect)
|
||||||
|
- TCP tunnel
|
||||||
|
- UDP tunnel
|
||||||
|
- UDP over TCP tunnel
|
||||||
|
- TLS, use it together with above proxy protocols(tcp)
|
||||||
|
- Unix domain socket, use it together with above proxy protocols(tcp)
|
||||||
|
- KCP protocol, use it together with above proxy protocols(tcp)
|
||||||
|
|
||||||
|Protocol | Listen/TCP | Listen/UDP | Forward/TCP | Forward/UDP | Description
|
Forward (local proxy client/upstream proxy server):
|
||||||
|:-: |:-:|:-:|:-:|:-:|:-
|
|
||||||
|Mixed |√|√| | |http+socks5 server
|
|
||||||
|HTTP |√| |√| |client & server
|
|
||||||
|SOCKS5 |√|√|√|√|client & server
|
|
||||||
|SS |√|√|√|√|client & server
|
|
||||||
|Trojan |√|√|√|√|client & server
|
|
||||||
|Trojanc |√|√|√|√|trojan cleartext(without tls)
|
|
||||||
|VLESS |√|√|√|√|client & server
|
|
||||||
|VMess | | |√|√|client only
|
|
||||||
|SSR | | |√| |client only
|
|
||||||
|SSH | | |√| |client only
|
|
||||||
|SOCKS4 | | |√| |client only
|
|
||||||
|SOCKS4A | | |√| |client only
|
|
||||||
|TCP |√| |√| |tcp tunnel client & server
|
|
||||||
|UDP | |√| |√|udp tunnel client & server
|
|
||||||
|TLS |√| |√| |transport client & server
|
|
||||||
|KCP | |√|√| |transport client & server
|
|
||||||
|Unix |√|√|√|√|transport client & server
|
|
||||||
|VSOCK |√| |√| |transport client & server
|
|
||||||
|Smux |√| |√| |transport client & server
|
|
||||||
|Websocket(WS) |√| |√| |transport client & server
|
|
||||||
|WS Secure |√| |√| |websocket secure (wss)
|
|
||||||
|Proxy Protocol |√| | | |version 1 server only
|
|
||||||
|Simple-Obfs | | |√| |transport client only
|
|
||||||
|Redir |√| | | |linux redirect proxy
|
|
||||||
|Redir6 |√| | | |linux redirect proxy(ipv6)
|
|
||||||
|TProxy | |√| | |linux tproxy(udp only)
|
|
||||||
|Reject | | |√|√|reject all requests
|
|
||||||
|
|
||||||
</details>
|
- Socks5 proxy(tcp&udp)
|
||||||
|
- Http proxy(tcp)
|
||||||
|
- SS proxy(tcp&udp&uot)
|
||||||
|
- SSR proxy(tcp)
|
||||||
|
- VMess proxy(tcp)
|
||||||
|
- TLS, use it together with above proxy protocols(tcp)
|
||||||
|
- Websocket, use it together with above proxy protocols(tcp)
|
||||||
|
- Unix domain socket, use it together with above proxy protocols(tcp)
|
||||||
|
- KCP protocol, use it together with above proxy protocols(tcp)
|
||||||
|
- Simple-Obfs, use it together with above proxy protocols(tcp)
|
||||||
|
|
||||||
|
DNS Forwarding Server (udp2tcp):
|
||||||
|
|
||||||
|
- DNS Over Proxy
|
||||||
|
- Listen on UDP and forward dns requests to remote dns server in TCP via forwarders
|
||||||
|
- Specify different upstream dns server based on destinations(in rule file)
|
||||||
|
- Tunnel mode: forward to a fixed upstream dns server
|
||||||
|
- Add resolved IPs to proxy rules
|
||||||
|
- Add resolved IPs to ipset
|
||||||
|
- DNS cache
|
||||||
|
- Custom dns record
|
||||||
|
|
||||||
|
IPSet Management (Linux kernel version >= 2.6.32):
|
||||||
|
|
||||||
|
- Add ip/cidrs from rule files on startup
|
||||||
|
- Add resolved ips for domains from rule files by dns forwarding server
|
||||||
|
|
||||||
|
General:
|
||||||
|
|
||||||
|
- Http and socks5 on the same port
|
||||||
|
- Forwarder chain
|
||||||
|
- RR/HA/LHA/DH strategy for multiple forwarders
|
||||||
|
- Periodical proxy checking
|
||||||
|
- Rule proxy based on destinations: [Config Examples](config/examples)
|
||||||
|
- Send requests from specific ip/interface
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
|
||||||
|
- [ ] IPv6 support in ipset manager
|
||||||
|
- [ ] Transparent UDP proxy (iptables tproxy)
|
||||||
|
- [ ] Performance tuning
|
||||||
|
- [ ] TUN/TAP device support
|
||||||
|
- [ ] SSH tunnel support (maybe)
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
- Binary: [https://github.com/nadoo/glider/releases](https://github.com/nadoo/glider/releases)
|
Binary:
|
||||||
- Docker: `docker pull nadoo/glider`
|
|
||||||
- Manjaro: `pamac install glider`
|
- [https://github.com/nadoo/glider/releases](https://github.com/nadoo/glider/releases)
|
||||||
- ArchLinux: `sudo pacman -S glider`
|
|
||||||
- Homebrew: `brew install glider`
|
Go Get (requires **Go 1.13+** ):
|
||||||
- MacPorts: `sudo port install glider`
|
|
||||||
- Source: `go install github.com/nadoo/glider@latest`
|
```bash
|
||||||
|
go get -u github.com/nadoo/glider
|
||||||
|
```
|
||||||
|
|
||||||
|
ArchLinux:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo pacman -S glider
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
command line:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
glider -listen :8443 -verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
config file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
glider -config CONFIGPATH
|
||||||
|
```
|
||||||
|
|
||||||
|
command line with config file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
glider -config CONFIGPATH -listen :8080 -verbose
|
||||||
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
#### Run
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
glider -verbose -listen :8443
|
glider 0.9.0 usage:
|
||||||
# docker run --rm -it nadoo/glider -verbose -listen :8443
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Help
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code>glider -help</code></summary>
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Usage: glider [-listen URL]... [-forward URL]... [OPTION]...
|
|
||||||
|
|
||||||
e.g. glider -config /etc/glider/glider.conf
|
|
||||||
glider -listen :8443 -forward socks5://serverA:1080 -forward socks5://serverB:1080 -verbose
|
|
||||||
|
|
||||||
OPTION:
|
|
||||||
-check string
|
|
||||||
check=tcp[://HOST:PORT]: tcp port connect check
|
|
||||||
check=http://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
|
|
||||||
check=https://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
|
|
||||||
check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, env vars: FORWARDER_ADDR,FORWARDER_URL
|
|
||||||
check=disable: disable health check (default "http://www.msftconnecttest.com/connecttest.txt#expect=200")
|
|
||||||
-checkdisabledonly
|
|
||||||
check disabled fowarders only
|
|
||||||
-checkinterval int
|
-checkinterval int
|
||||||
fowarder check interval(seconds) (default 30)
|
proxy check interval(seconds) (default 30)
|
||||||
-checklatencysamples int
|
|
||||||
use the average latency of the latest N checks (default 10)
|
|
||||||
-checktimeout int
|
-checktimeout int
|
||||||
fowarder check timeout(seconds) (default 10)
|
proxy check timeout(seconds) (default 10)
|
||||||
-checktolerance int
|
-checkwebsite string
|
||||||
fowarder check tolerance(ms), switch only when new_latency < old_latency - tolerance, only used in lha mode
|
proxy check HTTP(NOT HTTPS) website address, format: HOST[:PORT], default port: 80 (default "www.apple.com")
|
||||||
-config string
|
-config string
|
||||||
config file path
|
config file path
|
||||||
-dialtimeout int
|
|
||||||
dial timeout(seconds) (default 3)
|
|
||||||
-dns string
|
-dns string
|
||||||
local dns server listen address
|
local dns server listen address
|
||||||
-dnsalwaystcp
|
-dnsalwaystcp
|
||||||
always use tcp to query upstream dns servers no matter there is a forwarder or not
|
always use tcp to query upstream dns servers no matter there is a forwarder or not
|
||||||
-dnscachelog
|
|
||||||
show query log of dns cache
|
|
||||||
-dnscachesize int
|
|
||||||
max number of dns response in CACHE (default 4096)
|
|
||||||
-dnsmaxttl int
|
-dnsmaxttl int
|
||||||
maximum TTL value for entries in the CACHE(seconds) (default 1800)
|
maximum TTL value for entries in the CACHE(seconds) (default 1800)
|
||||||
-dnsminttl int
|
-dnsminttl int
|
||||||
minimum TTL value for entries in the CACHE(seconds)
|
minimum TTL value for entries in the CACHE(seconds)
|
||||||
-dnsnoaaaa
|
|
||||||
disable AAAA query
|
|
||||||
-dnsrecord value
|
-dnsrecord value
|
||||||
custom dns record, format: domain/ip
|
custom dns record, format: domain/ip
|
||||||
-dnsserver value
|
-dnsserver value
|
||||||
remote dns server address
|
remote dns server address
|
||||||
-dnstimeout int
|
-dnstimeout int
|
||||||
timeout value used in multiple dnsservers switch(seconds) (default 3)
|
timeout value used in multiple dnsservers switch(seconds) (default 3)
|
||||||
-example
|
|
||||||
show usage examples
|
|
||||||
-forward value
|
-forward value
|
||||||
forward url, see the URL section below
|
forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]
|
||||||
-include value
|
-include value
|
||||||
include file
|
include file
|
||||||
-interface string
|
-interface string
|
||||||
source ip or source interface
|
source ip or source interface
|
||||||
-listen value
|
-listen value
|
||||||
listen url, see the URL section below
|
listen url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS
|
||||||
-logflags int
|
|
||||||
do not change it if you do not know what it is, ref: https://pkg.go.dev/log#pkg-constants (default 19)
|
|
||||||
-maxfailures int
|
-maxfailures int
|
||||||
max failures to change forwarder status to disabled (default 3)
|
max failures to change forwarder status to disabled (default 3)
|
||||||
-relaytimeout int
|
|
||||||
relay timeout(seconds)
|
|
||||||
-rulefile value
|
-rulefile value
|
||||||
rule file path
|
rule file path
|
||||||
-rules-dir string
|
-rules-dir string
|
||||||
rule file folder
|
rule file folder
|
||||||
-scheme string
|
|
||||||
show help message of proxy scheme, use 'all' to see all schemes
|
|
||||||
-service value
|
|
||||||
run specified services, format: SERVICE_NAME[,SERVICE_CONFIG]
|
|
||||||
-strategy string
|
-strategy string
|
||||||
rr: Round Robin mode
|
forward strategy, default: rr (default "rr")
|
||||||
ha: High Availability mode
|
|
||||||
lha: Latency based High Availability mode
|
|
||||||
dh: Destination Hashing mode (default "rr")
|
|
||||||
-tcpbufsize int
|
|
||||||
tcp buffer size in Bytes (default 32768)
|
|
||||||
-udpbufsize int
|
|
||||||
udp buffer size in Bytes (default 2048)
|
|
||||||
-verbose
|
-verbose
|
||||||
verbose mode
|
verbose mode
|
||||||
|
|
||||||
URL:
|
Available Schemes:
|
||||||
proxy: SCHEME://[USER:PASS@][HOST]:PORT
|
mixed: serve as a http/socks5 proxy on the same port. (default)
|
||||||
chain: proxy,proxy[,proxy]...
|
ss: ss proxy
|
||||||
|
socks5: socks5 proxy
|
||||||
|
http: http proxy
|
||||||
|
ssr: ssr proxy
|
||||||
|
vmess: vmess proxy
|
||||||
|
tls: tls transport
|
||||||
|
ws: websocket transport
|
||||||
|
redir: redirect proxy. (used on linux as a transparent proxy with iptables redirect rules)
|
||||||
|
redir6: redirect proxy(ipv6)
|
||||||
|
tcptun: tcp tunnel
|
||||||
|
udptun: udp tunnel
|
||||||
|
uottun: udp over tcp tunnel
|
||||||
|
unix: unix domain socket
|
||||||
|
kcp: kcp protocol
|
||||||
|
simple-obfs: simple-obfs protocol
|
||||||
|
reject: a virtual proxy which just reject connections
|
||||||
|
|
||||||
e.g. -listen socks5://:1080
|
Available schemes for different modes:
|
||||||
-listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// (protocol chain)
|
listen: mixed ss socks5 http redir redir6 tcptun udptun uottun tls unix kcp
|
||||||
|
forward: reject ss socks5 http ssr vmess tls ws unix kcp simple-obfs
|
||||||
|
|
||||||
e.g. -forward socks5://server:1080
|
|
||||||
-forward tls://server.com:443,http:// (protocol chain)
|
|
||||||
-forward socks5://serverA:1080,socks5://serverB:1080 (proxy chain)
|
|
||||||
|
|
||||||
SCHEME:
|
|
||||||
listen : http kcp mixed pxyproto redir redir6 smux sni socks5 ss tcp tls tproxy trojan trojanc udp unix vless vsock ws wss
|
|
||||||
forward: direct http kcp reject simple-obfs smux socks4 socks4a socks5 ss ssh ssr tcp tls trojan trojanc udp unix vless vmess vsock ws wss
|
|
||||||
|
|
||||||
Note: use 'glider -scheme all' or 'glider -scheme SCHEME' to see help info for the scheme.
|
|
||||||
|
|
||||||
--
|
|
||||||
Forwarder Options: FORWARD_URL#OPTIONS
|
|
||||||
priority : the priority of that forwarder, the larger the higher, default: 0
|
|
||||||
interface: the local interface or ip address used to connect remote server.
|
|
||||||
|
|
||||||
e.g. -forward socks5://server:1080#priority=100
|
|
||||||
-forward socks5://server:1080#interface=eth0
|
|
||||||
-forward socks5://server:1080#priority=100&interface=192.168.1.99
|
|
||||||
|
|
||||||
Services:
|
|
||||||
dhcpd: service=dhcpd,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
|
|
||||||
service=dhcpd-failover,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
|
|
||||||
e.g. service=dhcpd,eth1,192.168.1.100,192.168.1.199,720
|
|
||||||
|
|
||||||
--
|
|
||||||
Help:
|
|
||||||
glider -help
|
|
||||||
glider -scheme all
|
|
||||||
glider -example
|
|
||||||
|
|
||||||
see README.md and glider.conf.example for more details.
|
|
||||||
--
|
|
||||||
glider 0.16.4, https://github.com/nadoo/glider (glider.proxy@gmail.com)
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
#### Schemes
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code>glider -scheme all</code></summary>
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Direct scheme:
|
|
||||||
direct://
|
|
||||||
|
|
||||||
Only needed when you want to specify the outgoing interface:
|
|
||||||
glider -verbose -listen :8443 -forward direct://#interface=eth0
|
|
||||||
|
|
||||||
Or load balance multiple interfaces directly:
|
|
||||||
glider -verbose -listen :8443 -forward direct://#interface=eth0 -forward direct://#interface=eth1 -strategy rr
|
|
||||||
|
|
||||||
Or you can use the high availability mode:
|
|
||||||
glider -verbose -listen :8443 -forward direct://#interface=eth0&priority=100 -forward direct://#interface=eth1&priority=200 -strategy ha
|
|
||||||
|
|
||||||
--
|
|
||||||
Http scheme:
|
|
||||||
http://[user:pass@]host:port
|
|
||||||
|
|
||||||
--
|
|
||||||
KCP scheme:
|
|
||||||
kcp://CRYPT:KEY@host:port[?dataShards=NUM&parityShards=NUM&mode=MODE]
|
|
||||||
|
|
||||||
Available crypt types for KCP:
|
|
||||||
none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20
|
|
||||||
|
|
||||||
Available modes for KCP:
|
|
||||||
fast, fast2, fast3, normal, default: fast
|
|
||||||
|
|
||||||
--
|
|
||||||
Simple-Obfs scheme:
|
|
||||||
simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA]
|
|
||||||
|
|
||||||
Available types for simple-obfs:
|
|
||||||
http, tls
|
|
||||||
|
|
||||||
--
|
|
||||||
Reject scheme:
|
|
||||||
reject://
|
|
||||||
|
|
||||||
--
|
|
||||||
Smux scheme:
|
|
||||||
smux://host:port
|
|
||||||
|
|
||||||
--
|
|
||||||
Socks4 scheme:
|
|
||||||
socks4://host:port
|
|
||||||
|
|
||||||
--
|
|
||||||
Socks5 scheme:
|
|
||||||
socks5://[user:pass@]host:port
|
|
||||||
|
|
||||||
--
|
|
||||||
SS scheme:
|
SS scheme:
|
||||||
ss://method:pass@host:port
|
ss://method:pass@host:port
|
||||||
|
|
||||||
Available methods for ss:
|
Available methods for ss:
|
||||||
AEAD Ciphers:
|
AEAD Ciphers:
|
||||||
AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AEAD_XCHACHA20_POLY1305
|
AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AEAD_XCHACHA20_POLY1305
|
||||||
Stream Ciphers:
|
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
|
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:
|
Alias:
|
||||||
chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305
|
chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305
|
||||||
Plain: NONE
|
Plain: DUMMY
|
||||||
|
|
||||||
--
|
|
||||||
SSH scheme:
|
|
||||||
ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS]
|
|
||||||
timeout: timeout of ssh handshake and channel operation, default: 5
|
|
||||||
|
|
||||||
--
|
|
||||||
SSR scheme:
|
SSR scheme:
|
||||||
ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz
|
ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz
|
||||||
|
|
||||||
--
|
VMess scheme:
|
||||||
|
vmess://[security:]uuid@host:port?alterID=num
|
||||||
|
|
||||||
|
Available securities for vmess:
|
||||||
|
none, aes-128-gcm, chacha20-poly1305
|
||||||
|
|
||||||
TLS client scheme:
|
TLS client scheme:
|
||||||
tls://host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&alpn=proto1][&alpn=proto2]
|
tls://host:port[?skipVerify=true]
|
||||||
|
|
||||||
Proxy over tls client:
|
Proxy over tls client:
|
||||||
tls://host:port[?skipVerify=true][&serverName=SERVERNAME],scheme://
|
tls://host:port[?skipVerify=true],scheme://
|
||||||
tls://host:port[?skipVerify=true],http://[user:pass@]
|
tls://host:port[?skipVerify=true],http://[user:pass@]
|
||||||
tls://host:port[?skipVerify=true],socks5://[user:pass@]
|
tls://host:port[?skipVerify=true],socks5://[user:pass@]
|
||||||
tls://host:port[?skipVerify=true],vmess://[security:]uuid@?alterID=num
|
tls://host:port[?skipVerify=true],vmess://[security:]uuid@?alterID=num
|
||||||
|
|
||||||
TLS server scheme:
|
TLS server scheme:
|
||||||
tls://host:port?cert=PATH&key=PATH[&alpn=proto1][&alpn=proto2]
|
tls://host:port?cert=PATH&key=PATH
|
||||||
|
|
||||||
Proxy over tls server:
|
Proxy over tls server:
|
||||||
tls://host:port?cert=PATH&key=PATH,scheme://
|
tls://host:port?cert=PATH&key=PATH,scheme://
|
||||||
@ -325,66 +220,65 @@ Proxy over tls server:
|
|||||||
tls://host:port?cert=PATH&key=PATH,socks5://
|
tls://host:port?cert=PATH&key=PATH,socks5://
|
||||||
tls://host:port?cert=PATH&key=PATH,ss://method:pass@
|
tls://host:port?cert=PATH&key=PATH,ss://method:pass@
|
||||||
|
|
||||||
--
|
Websocket scheme:
|
||||||
Trojan client scheme:
|
ws://host:port[/path]
|
||||||
trojan://pass@host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH]
|
|
||||||
trojanc://pass@host:port (cleartext, without TLS)
|
|
||||||
|
|
||||||
Trojan server scheme:
|
Websocket with a specified proxy protocol:
|
||||||
trojan://pass@host:port?cert=PATH&key=PATH[&fallback=127.0.0.1]
|
ws://host:port[/path],scheme://
|
||||||
trojanc://pass@host:port[?fallback=127.0.0.1] (cleartext, without TLS)
|
ws://host:port[/path],http://[user:pass@]
|
||||||
|
ws://host:port[/path],socks5://[user:pass@]
|
||||||
|
ws://host:port[/path],vmess://[security:]uuid@?alterID=num
|
||||||
|
|
||||||
|
TLS and Websocket with a specified proxy protocol:
|
||||||
|
tls://host:port[?skipVerify=true],ws://[@/path],scheme://
|
||||||
|
tls://host:port[?skipVerify=true],ws://[@/path],http://[user:pass@]
|
||||||
|
tls://host:port[?skipVerify=true],ws://[@/path],socks5://[user:pass@]
|
||||||
|
tls://host:port[?skipVerify=true],ws://[@/path],vmess://[security:]uuid@?alterID=num
|
||||||
|
|
||||||
--
|
|
||||||
Unix domain socket scheme:
|
Unix domain socket scheme:
|
||||||
unix://path
|
unix://path
|
||||||
|
|
||||||
--
|
KCP scheme:
|
||||||
VLESS scheme:
|
kcp://CRYPT:KEY@host:port[?dataShards=NUM&parityShards=NUM]
|
||||||
vless://uuid@host:port[?fallback=127.0.0.1:80]
|
|
||||||
|
|
||||||
--
|
Available crypt types for KCP:
|
||||||
VMess scheme:
|
none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20
|
||||||
vmess://[security:]uuid@host:port[?alterID=num]
|
|
||||||
if alterID=0 or not set, VMessAEAD will be enabled
|
|
||||||
|
|
||||||
Available security for vmess:
|
Simple-Obfs scheme:
|
||||||
zero, none, aes-128-gcm, chacha20-poly1305
|
simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA]
|
||||||
|
|
||||||
--
|
Available types for simple-obfs:
|
||||||
Websocket client scheme:
|
http, tls
|
||||||
ws://host:port[/path][?host=HOST][&origin=ORIGIN]
|
|
||||||
wss://host:port[/path][?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&host=HOST][&origin=ORIGIN]
|
|
||||||
|
|
||||||
Websocket server scheme:
|
DNS forwarding server:
|
||||||
ws://:port[/path][?host=HOST]
|
dns=:53
|
||||||
wss://:port[/path]?cert=PATH&key=PATH[?host=HOST]
|
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
|
||||||
|
|
||||||
Websocket with a specified proxy protocol:
|
Available forward strategies:
|
||||||
ws://host:port[/path][?host=HOST],scheme://
|
rr: Round Robin mode
|
||||||
ws://host:port[/path][?host=HOST],http://[user:pass@]
|
ha: High Availability mode
|
||||||
ws://host:port[/path][?host=HOST],socks5://[user:pass@]
|
lha: Latency based High Availability mode
|
||||||
|
dh: Destination Hashing mode
|
||||||
|
|
||||||
TLS and Websocket with a specified proxy protocol:
|
Forwarder option scheme: FORWARD_URL#OPTIONS
|
||||||
tls://host:port[?skipVerify=true][&serverName=SERVERNAME],ws://[@/path[?host=HOST]],scheme://
|
priority: set the priority of that forwarder, default:0
|
||||||
tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],http://[user:pass@]
|
interface: set local interface or ip address used to connect remote server
|
||||||
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
|
Examples:
|
||||||
|
socks5://1.1.1.1:1080#priority=100
|
||||||
|
vmess://[security:]uuid@host:port?alterID=num#priority=200
|
||||||
|
vmess://[security:]uuid@host:port?alterID=num#priority=200&interface=192.168.1.99
|
||||||
|
vmess://[security:]uuid@host:port?alterID=num#priority=200&interface=eth0
|
||||||
|
|
||||||
--
|
Config file format(see `glider.conf.example` as an example):
|
||||||
VM socket scheme(linux only):
|
# COMMENT LINE
|
||||||
vsock://[CID]:port
|
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>
|
|
||||||
|
|
||||||
#### Examples
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code>glider -example</code></summary>
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Examples:
|
Examples:
|
||||||
glider -config glider.conf
|
glider -config glider.conf
|
||||||
-run glider with specified config file.
|
-run glider with specified config file.
|
||||||
@ -392,39 +286,53 @@ Examples:
|
|||||||
glider -listen :8443 -verbose
|
glider -listen :8443 -verbose
|
||||||
-listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode.
|
-listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode.
|
||||||
|
|
||||||
glider -listen socks5://:1080 -listen http://:8080 -verbose
|
glider -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443 -verbose
|
||||||
-multiple listeners: listen on :1080 as socks5 proxy server, and on :8080 as http proxy server.
|
-listen on 0.0.0.0:8443 as a ss server.
|
||||||
|
|
||||||
glider -listen :8443 -forward direct://#interface=eth0 -forward direct://#interface=eth1
|
glider -listen socks5://user1:pass1@:1080 -verbose
|
||||||
-multiple forwarders: listen on 8443 and forward requests via interface eth0 and eth1 in round robin mode.
|
-listen on :1080 as a socks5 proxy server, enable authentication.
|
||||||
|
|
||||||
glider -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose
|
glider -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose
|
||||||
-protocol chain: listen on :443 as a https(http over tls) proxy server.
|
-listen on :443 as a https(http over tls) proxy server.
|
||||||
|
|
||||||
glider -listen http://:8080 -forward socks5://serverA:1080,socks5://serverB:1080
|
glider -listen http://:8080 -forward socks5://127.0.0.1:1080
|
||||||
-proxy chain: listen on :8080 as a http proxy server, forward all requests via forward chain.
|
-listen on :8080 as a http proxy server, forward all requests via socks5 server.
|
||||||
|
|
||||||
glider -listen :8443 -forward socks5://serverA:1080 -forward socks5://serverB:1080#priority=10 -forward socks5://serverC:1080#priority=10
|
glider -listen redir://:1081 -forward ss://method:pass@1.1.1.1:8443
|
||||||
-forwarder priority: serverA will only be used when serverB and serverC are not available.
|
-listen on :1081 as a transparent redirect server, forward all requests via remote ss server.
|
||||||
|
|
||||||
glider -listen tcp://:80 -forward tcp://serverA:80
|
glider -listen redir://:1081 -forward "ssr://method:pass@1.1.1.1:8444?protocol=a&protocol_param=b&obfs=c&obfs_param=d"
|
||||||
-tcp tunnel: listen on :80 and forward all requests to serverA:80.
|
-listen on :1081 as a transparent redirect server, forward all requests via remote ssr server.
|
||||||
|
|
||||||
glider -listen udp://:53 -forward socks5://serverA:1080,udp://8.8.8.8:53
|
glider -listen redir://:1081 -forward "tls://1.1.1.1:443,vmess://security:uuid@?alterID=10"
|
||||||
-udp tunnel: listen on :53 and forward all udp requests to 8.8.8.8:53 via remote socks5 server.
|
-listen on :1081 as a transparent redirect server, forward all requests via remote tls+vmess server.
|
||||||
|
|
||||||
glider -verbose -dns=:53 -dnsserver=8.8.8.8:53 -forward socks5://serverA:1080 -dnsrecord=abc.com/1.2.3.4
|
glider -listen redir://:1081 -forward "ws://1.1.1.1:80,vmess://security:uuid@?alterID=10"
|
||||||
-dns over proxy: listen on :53 as dns server, forward to 8.8.8.8:53 via socks5 server.
|
-listen on :1081 as a transparent redirect server, forward all requests via remote ws+vmess server.
|
||||||
|
|
||||||
|
glider -listen tcptun://:80=2.2.2.2:80 -forward ss://method:pass@1.1.1.1:8443
|
||||||
|
-listen on :80 and forward all requests to 2.2.2.2:80 via remote ss server.
|
||||||
|
|
||||||
|
glider -listen udptun://:53=8.8.8.8:53 -forward ss://method:pass@1.1.1.1:8443
|
||||||
|
-listen on :53 and forward all udp requests to 8.8.8.8:53 via remote ss server.
|
||||||
|
|
||||||
|
glider -listen uottun://:53=8.8.8.8:53 -forward ss://method:pass@1.1.1.1:8443
|
||||||
|
-listen on :53 and forward all udp requests via udp over tcp tunnel.
|
||||||
|
|
||||||
|
glider -listen socks5://:1080 -listen http://:8080 -forward ss://method:pass@1.1.1.1:8443
|
||||||
|
-listen on :1080 as socks5 server, :8080 as http proxy server, forward all requests via remote ss server.
|
||||||
|
|
||||||
|
glider -listen redir://:1081 -dns=:53 -dnsserver=8.8.8.8:53 -forward ss://method:pass@server1:port1,ss://method:pass@server2:port2
|
||||||
|
-listen on :1081 as transparent redirect server, :53 as dns server, use forward chain: server1 -> server2.
|
||||||
|
|
||||||
|
glider -listen socks5://:1080 -forward ss://method:pass@server1:port1 -forward ss://method:pass@server2:port2 -strategy rr
|
||||||
|
-listen on :1080 as socks5 server, forward requests via server1 and server2 in round robin mode.
|
||||||
|
|
||||||
|
glider -verbose -dns=:53 -dnsserver=8.8.8.8:53 -dnsrecord=www.example.com/1.2.3.4
|
||||||
|
-listen on :53 as dns server, forward dns requests to 8.8.8.8:53, return 1.2.3.4 when resolving www.example.com.
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
## Advanced Usage
|
||||||
|
|
||||||
|
|
||||||
## Config
|
|
||||||
|
|
||||||
```bash
|
|
||||||
glider -config CONFIG_PATH
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ConfigFile](config)
|
- [ConfigFile](config)
|
||||||
- [glider.conf.example](config/glider.conf.example)
|
- [glider.conf.example](config/glider.conf.example)
|
||||||
@ -433,110 +341,45 @@ glider -config CONFIG_PATH
|
|||||||
- [transparent proxy with dnsmasq](config/examples/8.transparent_proxy_with_dnsmasq)
|
- [transparent proxy with dnsmasq](config/examples/8.transparent_proxy_with_dnsmasq)
|
||||||
- [transparent proxy without dnsmasq](config/examples/9.transparent_proxy_without_dnsmasq)
|
- [transparent proxy without dnsmasq](config/examples/9.transparent_proxy_without_dnsmasq)
|
||||||
|
|
||||||
## Service
|
### Proxy & Protocol Chain
|
||||||
|
In glider, you can easily chain several proxy servers or protocols together, e.g:
|
||||||
- dhcpd / dhcpd-failover:
|
|
||||||
- service=dhcpd,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
|
|
||||||
- service=dhcpd,eth1,192.168.1.100,192.168.1.199,720,fc:23:34:9e:25:01=192.168.1.101
|
|
||||||
- service=dhcpd-failover,eth2,192.168.2.100,192.168.2.199,720
|
|
||||||
- note: `dhcpd-failover` only serves requests when there's no other dhcp server exists in lan
|
|
||||||
- detect interval: 1min
|
|
||||||
|
|
||||||
## Linux 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>
|
|
||||||
|
|
||||||
|
|
||||||
## Customize Build
|
|
||||||
|
|
||||||
<details><summary>You can customize and build glider if you want a smaller binary (click to see details)</summary>
|
|
||||||
|
|
||||||
|
|
||||||
1. Clone the source code:
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/nadoo/glider && cd glider
|
|
||||||
```
|
|
||||||
2. Customize features:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
open `feature.go` & `feature_linux.go`, comment out the packages you don't need
|
|
||||||
// _ "github.com/nadoo/glider/proxy/kcp"
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Build it:
|
|
||||||
```bash
|
|
||||||
go build -v -ldflags "-s -w"
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Proxy & Protocol Chains
|
|
||||||
<details><summary>In glider, you can easily chain several proxy servers or protocols together (click to see details)</summary>
|
|
||||||
|
|
||||||
- Chain proxy servers:
|
- Chain proxy servers:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
forward=http://1.1.1.1:80,socks5://2.2.2.2:1080,ss://method:pass@3.3.3.3:8443@
|
forward=http://1.1.1.1:80,socks5://2.2.2.2:1080,ss://method:pass@3.3.3.3:8443@
|
||||||
```
|
```
|
||||||
|
|
||||||
- Chain protocols: https proxy (http over tls)
|
- Chain protocols: https proxy (http over tls)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
forward=tls://server.com:443,http://
|
forward=tls://1.1.1.1:443,http://
|
||||||
```
|
```
|
||||||
|
|
||||||
- Chain protocols: vmess over ws over tls
|
- Chain protocols: vmess over ws over tls
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
forward=tls://server.com:443,ws://,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
forward=tls://1.1.1.1:443,ws://,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
||||||
```
|
```
|
||||||
|
|
||||||
- Chain protocols and servers:
|
- Chain protocols and servers:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
forward=socks5://1.1.1.1:1080,tls://server.com:443,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
forward=socks5://1.1.1.1:1080,tls://2.2.2.2:443,ws://,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
||||||
```
|
```
|
||||||
|
|
||||||
- Chain protocols in listener: https proxy server
|
- Chain protocols in listener: https proxy server
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
listen=tls://:443?cert=crtFilePath&key=keyFilePath,http://
|
listen=tls://:443?cert=crtFilePath&key=keyFilePath,http://
|
||||||
```
|
```
|
||||||
|
|
||||||
- Chain protocols in listener: http over smux over websocket proxy server
|
|
||||||
|
|
||||||
``` bash
|
## Service
|
||||||
listen=ws://:10000,smux://,http://
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
- systemd: [https://github.com/nadoo/glider/blob/master/systemd/](https://github.com/nadoo/glider/blob/master/systemd/)
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
- [ipset](https://github.com/nadoo/ipset): netlink ipset package for Go.
|
- [conflag](https://github.com/nadoo/conflag): command line and config file parse support
|
||||||
- [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`
|
|
||||||
|
90
common/conn/conn.go
Normal file
90
common/conn/conn.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package conn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UDPBufSize is the size of udp buffer.
|
||||||
|
const UDPBufSize = 65536
|
||||||
|
|
||||||
|
// Conn is a base conn struct.
|
||||||
|
type Conn struct {
|
||||||
|
r *bufio.Reader
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConn returns a new conn.
|
||||||
|
func NewConn(c net.Conn) *Conn {
|
||||||
|
return &Conn{bufio.NewReader(c), c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek returns the next n bytes without advancing the reader.
|
||||||
|
func (c *Conn) Peek(n int) ([]byte, error) {
|
||||||
|
return c.r.Peek(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Read(p []byte) (int, error) {
|
||||||
|
return c.r.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reader returns the internal bufio.Reader.
|
||||||
|
func (c *Conn) Reader() *bufio.Reader {
|
||||||
|
return c.r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relay relays between left and right.
|
||||||
|
func Relay(left, right net.Conn) (int64, int64, error) {
|
||||||
|
type res struct {
|
||||||
|
N int64
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
ch := make(chan res)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
n, err := io.Copy(right, left)
|
||||||
|
right.SetDeadline(time.Now()) // wake up the other goroutine blocking on right
|
||||||
|
left.SetDeadline(time.Now()) // wake up the other goroutine blocking on left
|
||||||
|
ch <- res{n, err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
n, err := io.Copy(left, right)
|
||||||
|
right.SetDeadline(time.Now()) // wake up the other goroutine blocking on right
|
||||||
|
left.SetDeadline(time.Now()) // wake up the other goroutine blocking on left
|
||||||
|
rs := <-ch
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
err = rs.Err
|
||||||
|
}
|
||||||
|
return n, rs.N, 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 {
|
||||||
|
buf := make([]byte, UDPBufSize)
|
||||||
|
for {
|
||||||
|
src.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
n, _, err := src.ReadFrom(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = dst.WriteTo(buf[:n], target)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutboundIP returns preferred outbound ip of this machine.
|
||||||
|
func OutboundIP() string {
|
||||||
|
conn, err := net.Dial("udp", "8.8.8.8:80")
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
return conn.LocalAddr().(*net.UDPAddr).IP.String()
|
||||||
|
}
|
25
common/log/log.go
Normal file
25
common/log/log.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
stdlog "log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
stdlog.SetFlags(stdlog.LstdFlags | stdlog.Lshortfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Func defines a simple log function
|
||||||
|
type Func func(f string, v ...interface{})
|
||||||
|
|
||||||
|
// F is the main log function
|
||||||
|
var F Func = func(string, ...interface{}) {}
|
||||||
|
|
||||||
|
// 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...)
|
||||||
|
}
|
@ -4,7 +4,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,22 +13,21 @@ const (
|
|||||||
AuthPassword = 2
|
AuthPassword = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// SOCKS request commands as defined in RFC 1928 section 4
|
// SOCKS request commands as defined in RFC 1928 section 4.
|
||||||
const (
|
const (
|
||||||
CmdError byte = 0
|
CmdConnect = 1
|
||||||
CmdConnect byte = 1
|
CmdBind = 2
|
||||||
CmdBind byte = 2
|
CmdUDPAssociate = 3
|
||||||
CmdUDPAssociate byte = 3
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SOCKS address types as defined in RFC 1928 section 5
|
// SOCKS address types as defined in RFC 1928 section 5.
|
||||||
const (
|
const (
|
||||||
ATypIP4 = 1
|
ATypIP4 = 1
|
||||||
ATypDomain = 3
|
ATypDomain = 3
|
||||||
ATypIP6 = 4
|
ATypIP6 = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
// MaxAddrLen is the maximum size of SOCKS address in bytes
|
// MaxAddrLen is the maximum size of SOCKS address in bytes.
|
||||||
const MaxAddrLen = 1 + 1 + 255 + 2
|
const MaxAddrLen = 1 + 1 + 255 + 2
|
||||||
|
|
||||||
// Errors are socks5 errors
|
// Errors are socks5 errors
|
||||||
@ -46,14 +44,14 @@ var Errors = []error{
|
|||||||
errors.New("socks5UDPAssociate"),
|
errors.New("socks5UDPAssociate"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Addr represents a SOCKS address as defined in RFC 1928 section 5.
|
// Addr .
|
||||||
type Addr []byte
|
type Addr []byte
|
||||||
|
|
||||||
// String serializes SOCKS address a to string form.
|
// String serializes SOCKS address a to string form.
|
||||||
func (a Addr) String() string {
|
func (a Addr) String() string {
|
||||||
var host, port string
|
var host, port string
|
||||||
|
|
||||||
switch a[0] { // address type
|
switch ATYP(a[0]) { // address type
|
||||||
case ATypDomain:
|
case ATypDomain:
|
||||||
host = string(a[2 : 2+int(a[1])])
|
host = string(a[2 : 2+int(a[1])])
|
||||||
port = strconv.Itoa((int(a[2+int(a[1])]) << 8) | int(a[2+int(a[1])+1]))
|
port = strconv.Itoa((int(a[2+int(a[1])]) << 8) | int(a[2+int(a[1])+1]))
|
||||||
@ -68,18 +66,27 @@ func (a Addr) String() string {
|
|||||||
return net.JoinHostPort(host, port)
|
return net.JoinHostPort(host, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Network returns network name. Implements net.Addr interface.
|
// UoT returns whether it is udp over tcp
|
||||||
func (a Addr) Network() string { return "socks" }
|
func UoT(b byte) bool {
|
||||||
|
return b&0x8 == 0x8
|
||||||
|
}
|
||||||
|
|
||||||
// ReadAddr reads just enough bytes from r to get a valid Addr.
|
// ATYP returns the address type
|
||||||
func ReadAddr(r io.Reader) (Addr, error) {
|
func ATYP(b byte) int {
|
||||||
b := make([]byte, MaxAddrLen)
|
return int(b &^ 0x8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
_, err := io.ReadFull(r, b[:1]) // read 1st byte for address type
|
_, err := io.ReadFull(r, b[:1]) // read 1st byte for address type
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch b[0] {
|
switch ATYP(b[0]) {
|
||||||
case ATypDomain:
|
case ATypDomain:
|
||||||
_, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length
|
_, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -98,6 +105,11 @@ func ReadAddr(r io.Reader) (Addr, error) {
|
|||||||
return nil, Errors[8]
|
return nil, Errors[8]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadAddr reads just enough bytes from r to get a valid Addr.
|
||||||
|
func ReadAddr(r io.Reader) (Addr, error) {
|
||||||
|
return ReadAddrBuf(r, make([]byte, MaxAddrLen))
|
||||||
|
}
|
||||||
|
|
||||||
// SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed.
|
// SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed.
|
||||||
func SplitAddr(b []byte) Addr {
|
func SplitAddr(b []byte) Addr {
|
||||||
addrLen := 1
|
addrLen := 1
|
||||||
@ -105,7 +117,7 @@ func SplitAddr(b []byte) Addr {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch b[0] {
|
switch ATYP(b[0]) {
|
||||||
case ATypDomain:
|
case ATypDomain:
|
||||||
if len(b) < 2 {
|
if len(b) < 2 {
|
||||||
return nil
|
return nil
|
||||||
@ -117,6 +129,7 @@ func SplitAddr(b []byte) Addr {
|
|||||||
addrLen = 1 + net.IPv6len + 2
|
addrLen = 1 + net.IPv6len + 2
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(b) < addrLen {
|
if len(b) < addrLen {
|
||||||
@ -133,16 +146,16 @@ func ParseAddr(s string) Addr {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
if ip, err := netip.ParseAddr(host); err == nil {
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
if ip.Is4() {
|
|
||||||
addr = make([]byte, 1+net.IPv4len+2)
|
addr = make([]byte, 1+net.IPv4len+2)
|
||||||
addr[0] = ATypIP4
|
addr[0] = ATypIP4
|
||||||
|
copy(addr[1:], ip4)
|
||||||
} else {
|
} else {
|
||||||
addr = make([]byte, 1+net.IPv6len+2)
|
addr = make([]byte, 1+net.IPv6len+2)
|
||||||
addr[0] = ATypIP6
|
addr[0] = ATypIP6
|
||||||
|
copy(addr[1:], ip)
|
||||||
}
|
}
|
||||||
copy(addr[1:], ip.AsSlice())
|
|
||||||
} else {
|
} else {
|
||||||
if len(host) > 255 {
|
if len(host) > 255 {
|
||||||
return nil
|
return nil
|
306
conf.go
Normal file
306
conf.go
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/nadoo/conflag"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/dns"
|
||||||
|
"github.com/nadoo/glider/rule"
|
||||||
|
"github.com/nadoo/glider/strategy"
|
||||||
|
)
|
||||||
|
|
||||||
|
var flag = conflag.New()
|
||||||
|
|
||||||
|
var conf struct {
|
||||||
|
Verbose bool
|
||||||
|
|
||||||
|
Listen []string
|
||||||
|
|
||||||
|
Forward []string
|
||||||
|
StrategyConfig strategy.Config
|
||||||
|
|
||||||
|
RuleFile []string
|
||||||
|
RulesDir string
|
||||||
|
|
||||||
|
DNS string
|
||||||
|
DNSConfig dns.Config
|
||||||
|
|
||||||
|
rules []*rule.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func confInit() {
|
||||||
|
flag.BoolVar(&conf.Verbose, "verbose", false, "verbose mode")
|
||||||
|
flag.StringSliceUniqVar(&conf.Listen, "listen", nil, "listen url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS")
|
||||||
|
|
||||||
|
flag.StringSliceUniqVar(&conf.Forward, "forward", nil, "forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]")
|
||||||
|
flag.StringVar(&conf.StrategyConfig.Strategy, "strategy", "rr", "forward strategy, default: rr")
|
||||||
|
flag.StringVar(&conf.StrategyConfig.CheckWebSite, "checkwebsite", "www.apple.com", "proxy check HTTP(NOT HTTPS) website address, format: HOST[:PORT], default port: 80")
|
||||||
|
flag.IntVar(&conf.StrategyConfig.CheckInterval, "checkinterval", 30, "proxy check interval(seconds)")
|
||||||
|
flag.IntVar(&conf.StrategyConfig.CheckTimeout, "checktimeout", 10, "proxy check timeout(seconds)")
|
||||||
|
flag.IntVar(&conf.StrategyConfig.MaxFailures, "maxfailures", 3, "max failures to change forwarder status to disabled")
|
||||||
|
flag.StringVar(&conf.StrategyConfig.IntFace, "interface", "", "source ip or source interface")
|
||||||
|
|
||||||
|
flag.StringSliceUniqVar(&conf.RuleFile, "rulefile", nil, "rule file path")
|
||||||
|
flag.StringVar(&conf.RulesDir, "rules-dir", "", "rule file folder")
|
||||||
|
|
||||||
|
flag.StringVar(&conf.DNS, "dns", "", "local dns server listen address")
|
||||||
|
flag.StringSliceUniqVar(&conf.DNSConfig.Servers, "dnsserver", []string{"8.8.8.8:53"}, "remote dns server address")
|
||||||
|
flag.BoolVar(&conf.DNSConfig.AlwaysTCP, "dnsalwaystcp", false, "always use tcp to query upstream dns servers no matter there is a forwarder or not")
|
||||||
|
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.StringSliceUniqVar(&conf.DNSConfig.Records, "dnsrecord", nil, "custom dns record, format: domain/ip")
|
||||||
|
|
||||||
|
flag.Usage = usage
|
||||||
|
err := flag.Parse()
|
||||||
|
if err != nil {
|
||||||
|
// flag.Usage()
|
||||||
|
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(conf.Listen) == 0 && conf.DNS == "" {
|
||||||
|
// flag.Usage()
|
||||||
|
fmt.Fprintf(os.Stderr, "ERROR: listen url must be specified.\n")
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rulefiles
|
||||||
|
for _, ruleFile := range conf.RuleFile {
|
||||||
|
if !path.IsAbs(ruleFile) {
|
||||||
|
ruleFile = path.Join(flag.ConfDir(), ruleFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := rule.NewConfFromFile(ruleFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.rules = append(conf.rules, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.RulesDir != "" {
|
||||||
|
if !path.IsAbs(conf.RulesDir) {
|
||||||
|
conf.RulesDir = path.Join(flag.ConfDir(), conf.RulesDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleFolderFiles, _ := rule.ListDir(conf.RulesDir, ".rule")
|
||||||
|
for _, ruleFile := range ruleFolderFiles {
|
||||||
|
rule, err := rule.NewConfFromFile(ruleFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
conf.rules = append(conf.rules, rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func usage() {
|
||||||
|
app := os.Args[0]
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "%s %s usage:\n", app, version)
|
||||||
|
flag.PrintDefaults()
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Available Schemes:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " mixed: serve as a http/socks5 proxy on the same port. (default)\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " ss: ss proxy\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " socks5: socks5 proxy\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " http: http proxy\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " ssr: ssr proxy\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " vmess: vmess proxy\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " tls: tls transport\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " ws: websocket transport\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " redir: redirect proxy. (used on linux as a transparent proxy with iptables redirect rules)\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " redir6: redirect proxy(ipv6)\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " tcptun: tcp tunnel\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " udptun: udp tunnel\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " uottun: udp over tcp tunnel\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " unix: unix domain socket\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " kcp: kcp protocol\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " simple-obfs: simple-obfs protocol\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " reject: a virtual proxy which just reject connections\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Available schemes for different modes:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " listen: mixed ss socks5 http redir redir6 tcptun udptun uottun tls unix kcp\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " forward: reject ss socks5 http ssr vmess tls ws unix kcp simple-obfs\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "SS scheme:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " ss://method:pass@host:port\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Available methods for ss:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " AEAD Ciphers:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AEAD_XCHACHA20_POLY1305\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " Stream Ciphers:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " 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(os.Stderr, " Alias:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " Plain: DUMMY\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "SSR scheme:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "VMess scheme:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " vmess://[security:]uuid@host:port?alterID=num\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Available securities for vmess:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " none, aes-128-gcm, chacha20-poly1305\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "TLS client scheme:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " tls://host:port[?skipVerify=true]\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Proxy over tls client:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " tls://host:port[?skipVerify=true],scheme://\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " tls://host:port[?skipVerify=true],http://[user:pass@]\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " tls://host:port[?skipVerify=true],socks5://[user:pass@]\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " tls://host:port[?skipVerify=true],vmess://[security:]uuid@?alterID=num\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "TLS server scheme:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " tls://host:port?cert=PATH&key=PATH\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Proxy over tls server:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " tls://host:port?cert=PATH&key=PATH,scheme://\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " tls://host:port?cert=PATH&key=PATH,http://\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " tls://host:port?cert=PATH&key=PATH,socks5://\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " tls://host:port?cert=PATH&key=PATH,ss://method:pass@\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Websocket scheme:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " ws://host:port[/path]\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Websocket with a specified proxy protocol:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " ws://host:port[/path],scheme://\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " ws://host:port[/path],http://[user:pass@]\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " ws://host:port[/path],socks5://[user:pass@]\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " ws://host:port[/path],vmess://[security:]uuid@?alterID=num\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "TLS and Websocket with a specified proxy protocol:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " tls://host:port[?skipVerify=true],ws://[@/path],scheme://\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " tls://host:port[?skipVerify=true],ws://[@/path],http://[user:pass@]\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " tls://host:port[?skipVerify=true],ws://[@/path],socks5://[user:pass@]\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " tls://host:port[?skipVerify=true],ws://[@/path],vmess://[security:]uuid@?alterID=num\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Unix domain socket scheme:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " unix://path\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "KCP scheme:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " kcp://CRYPT:KEY@host:port[?dataShards=NUM&parityShards=NUM]\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Available crypt types for KCP:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Simple-Obfs scheme:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA]\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Available types for simple-obfs:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " http, tls\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "DNS forwarding server:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " dns=:53\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " dnsserver=8.8.8.8:53\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " dnsserver=1.1.1.1:53\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " dnsrecord=www.example.com/1.2.3.4\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " dnsrecord=www.example.com/2606:2800:220:1:248:1893:25c8:1946\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Available forward strategies:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " rr: Round Robin mode\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " ha: High Availability mode\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " lha: Latency based High Availability mode\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " dh: Destination Hashing mode\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Forwarder option scheme: FORWARD_URL#OPTIONS\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " priority: set the priority of that forwarder, default:0\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " interface: set local interface or ip address used to connect remote server\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " Examples:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " socks5://1.1.1.1:1080#priority=100\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " vmess://[security:]uuid@host:port?alterID=num#priority=200\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " vmess://[security:]uuid@host:port?alterID=num#priority=200&interface=192.168.1.99\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " vmess://[security:]uuid@host:port?alterID=num#priority=200&interface=eth0\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Config file format(see `"+app+".conf.example` as an example):\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " # COMMENT LINE\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " KEY=VALUE\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " KEY=VALUE\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " # KEY equals to command line flag name: listen forward strategy...\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Examples:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -config glider.conf\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -run glider with specified config file.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen :8443 -verbose\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443 -verbose\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on 0.0.0.0:8443 as a ss server.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://user1:pass1@:1080 -verbose\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :1080 as a socks5 proxy server, enable authentication.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :443 as a https(http over tls) proxy server.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen http://:8080 -forward socks5://127.0.0.1:1080\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :8080 as a http proxy server, forward all requests via socks5 server.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen redir://:1081 -forward ss://method:pass@1.1.1.1:8443\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :1081 as a transparent redirect server, forward all requests via remote ss server.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen redir://:1081 -forward \"ssr://method:pass@1.1.1.1:8444?protocol=a&protocol_param=b&obfs=c&obfs_param=d\"\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :1081 as a transparent redirect server, forward all requests via remote ssr server.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen redir://:1081 -forward \"tls://1.1.1.1:443,vmess://security:uuid@?alterID=10\"\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :1081 as a transparent redirect server, forward all requests via remote tls+vmess server.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen redir://:1081 -forward \"ws://1.1.1.1:80,vmess://security:uuid@?alterID=10\"\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :1081 as a transparent redirect server, forward all requests via remote ws+vmess server.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen tcptun://:80=2.2.2.2:80 -forward ss://method:pass@1.1.1.1:8443\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :80 and forward all requests to 2.2.2.2:80 via remote ss server.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen udptun://:53=8.8.8.8:53 -forward ss://method:pass@1.1.1.1:8443\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :53 and forward all udp requests to 8.8.8.8:53 via remote ss server.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen uottun://:53=8.8.8.8:53 -forward ss://method:pass@1.1.1.1:8443\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :53 and forward all udp requests via udp over tcp tunnel.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://:1080 -listen http://:8080 -forward ss://method:pass@1.1.1.1:8443\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :1080 as socks5 server, :8080 as http proxy server, forward all requests via remote ss server.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen redir://:1081 -dns=:53 -dnsserver=8.8.8.8:53 -forward ss://method:pass@server1:port1,ss://method:pass@server2:port2\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :1081 as transparent redirect server, :53 as dns server, use forward chain: server1 -> server2.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://:1080 -forward ss://method:pass@server1:port1 -forward ss://method:pass@server2:port2 -strategy rr\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :1080 as socks5 server, forward requests via server1 and server2 in round robin mode.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " "+app+" -verbose -dns=:53 -dnsserver=8.8.8.8:53 -dnsrecord=www.example.com/1.2.3.4\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " -listen on :53 as dns server, forward dns requests to 8.8.8.8:53, return 1.2.3.4 when resolving www.example.com.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
}
|
254
config.go
254
config.go
@ -1,254 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/nadoo/conflag"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/dns"
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
|
||||||
"github.com/nadoo/glider/proxy"
|
|
||||||
"github.com/nadoo/glider/rule"
|
|
||||||
)
|
|
||||||
|
|
||||||
var flag = conflag.New()
|
|
||||||
|
|
||||||
// Config is global config struct.
|
|
||||||
type Config struct {
|
|
||||||
Verbose bool
|
|
||||||
LogFlags int
|
|
||||||
TCPBufSize int
|
|
||||||
UDPBufSize int
|
|
||||||
|
|
||||||
Listens []string
|
|
||||||
|
|
||||||
Forwards []string
|
|
||||||
Strategy rule.Strategy
|
|
||||||
|
|
||||||
RuleFiles []string
|
|
||||||
RulesDir string
|
|
||||||
|
|
||||||
DNS string
|
|
||||||
DNSConfig dns.Config
|
|
||||||
|
|
||||||
rules []*rule.Config
|
|
||||||
|
|
||||||
Services []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseConfig() *Config {
|
|
||||||
conf := &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, "do not change it if you do not know what it is, ref: https://pkg.go.dev/log#pkg-constants")
|
|
||||||
flag.IntVar(&conf.TCPBufSize, "tcpbufsize", 32768, "tcp buffer size in Bytes")
|
|
||||||
flag.IntVar(&conf.UDPBufSize, "udpbufsize", 2048, "udp buffer size in Bytes")
|
|
||||||
flag.StringSliceUniqVar(&conf.Listens, "listen", nil, "listen url, see the URL section below")
|
|
||||||
|
|
||||||
flag.StringSliceVar(&conf.Forwards, "forward", nil, "forward url, see the URL section below")
|
|
||||||
flag.StringVar(&conf.Strategy.Strategy, "strategy", "rr", `rr: Round Robin mode
|
|
||||||
ha: High Availability mode
|
|
||||||
lha: Latency based High Availability mode
|
|
||||||
dh: Destination Hashing mode`)
|
|
||||||
flag.StringVar(&conf.Strategy.Check, "check", "http://www.msftconnecttest.com/connecttest.txt#expect=200",
|
|
||||||
`check=tcp[://HOST:PORT]: tcp port connect check
|
|
||||||
check=http://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
|
|
||||||
check=https://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
|
|
||||||
check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, env vars: FORWARDER_ADDR,FORWARDER_URL
|
|
||||||
check=disable: disable health check`)
|
|
||||||
flag.IntVar(&conf.Strategy.CheckInterval, "checkinterval", 30, "fowarder check interval(seconds)")
|
|
||||||
flag.IntVar(&conf.Strategy.CheckTimeout, "checktimeout", 10, "fowarder check timeout(seconds)")
|
|
||||||
flag.IntVar(&conf.Strategy.CheckTolerance, "checktolerance", 0, "fowarder check tolerance(ms), switch only when new_latency < old_latency - tolerance, only used in lha mode")
|
|
||||||
flag.IntVar(&conf.Strategy.CheckLatencySamples, "checklatencysamples", 10, "use the average latency of the latest N checks")
|
|
||||||
flag.BoolVar(&conf.Strategy.CheckDisabledOnly, "checkdisabledonly", false, "check disabled fowarders only")
|
|
||||||
flag.IntVar(&conf.Strategy.MaxFailures, "maxfailures", 3, "max failures to change forwarder status to disabled")
|
|
||||||
flag.IntVar(&conf.Strategy.DialTimeout, "dialtimeout", 3, "dial timeout(seconds)")
|
|
||||||
flag.IntVar(&conf.Strategy.RelayTimeout, "relaytimeout", 0, "relay timeout(seconds)")
|
|
||||||
flag.StringVar(&conf.Strategy.IntFace, "interface", "", "source ip or source interface")
|
|
||||||
|
|
||||||
flag.StringSliceUniqVar(&conf.RuleFiles, "rulefile", nil, "rule file path")
|
|
||||||
flag.StringVar(&conf.RulesDir, "rules-dir", "", "rule file folder")
|
|
||||||
|
|
||||||
// dns configs
|
|
||||||
flag.StringVar(&conf.DNS, "dns", "", "local dns server listen address")
|
|
||||||
flag.StringSliceUniqVar(&conf.DNSConfig.Servers, "dnsserver", []string{"8.8.8.8:53"}, "remote dns server address")
|
|
||||||
flag.BoolVar(&conf.DNSConfig.AlwaysTCP, "dnsalwaystcp", false, "always use tcp to query upstream dns servers no matter there is a forwarder or not")
|
|
||||||
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, "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
|
|
||||||
if err := flag.Parse(); err != nil {
|
|
||||||
// flag.Usage()
|
|
||||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
|
|
||||||
os.Exit(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *scheme != "" {
|
|
||||||
fmt.Fprint(flag.Output(), proxy.Usage(*scheme))
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *example {
|
|
||||||
fmt.Fprint(flag.Output(), examples)
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup logger
|
|
||||||
log.Set(conf.Verbose, conf.LogFlags)
|
|
||||||
|
|
||||||
if len(conf.Listens) == 0 && conf.DNS == "" && len(conf.Services) == 0 {
|
|
||||||
// flag.Usage()
|
|
||||||
fmt.Fprintf(os.Stderr, "ERROR: listen url must be specified.\n")
|
|
||||||
os.Exit(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// tcpbufsize
|
|
||||||
if conf.TCPBufSize > 0 {
|
|
||||||
proxy.TCPBufSize = conf.TCPBufSize
|
|
||||||
}
|
|
||||||
|
|
||||||
// udpbufsize
|
|
||||||
if conf.UDPBufSize > 0 {
|
|
||||||
proxy.UDPBufSize = conf.UDPBufSize
|
|
||||||
}
|
|
||||||
|
|
||||||
loadRules(conf)
|
|
||||||
return conf
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadRules(conf *Config) {
|
|
||||||
// rulefiles
|
|
||||||
for _, ruleFile := range conf.RuleFiles {
|
|
||||||
if !path.IsAbs(ruleFile) {
|
|
||||||
ruleFile = path.Join(flag.ConfDir(), ruleFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
rule, err := rule.NewConfFromFile(ruleFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
conf.rules = append(conf.rules, rule)
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.RulesDir != "" {
|
|
||||||
if !path.IsAbs(conf.RulesDir) {
|
|
||||||
conf.RulesDir = path.Join(flag.ConfDir(), conf.RulesDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
ruleFolderFiles, _ := rule.ListDir(conf.RulesDir, ".rule")
|
|
||||||
for _, ruleFile := range ruleFolderFiles {
|
|
||||||
rule, err := rule.NewConfFromFile(ruleFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
conf.rules = append(conf.rules, rule)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func usage() {
|
|
||||||
fmt.Fprint(flag.Output(), usage1)
|
|
||||||
flag.PrintDefaults()
|
|
||||||
fmt.Fprintf(flag.Output(), usage2, proxy.ServerSchemes(), proxy.DialerSchemes(), version)
|
|
||||||
}
|
|
||||||
|
|
||||||
var usage1 = `
|
|
||||||
Usage: glider [-listen URL]... [-forward URL]... [OPTION]...
|
|
||||||
|
|
||||||
e.g. glider -config /etc/glider/glider.conf
|
|
||||||
glider -listen :8443 -forward socks5://serverA:1080 -forward socks5://serverB:1080 -verbose
|
|
||||||
|
|
||||||
OPTION:
|
|
||||||
`
|
|
||||||
|
|
||||||
var usage2 = `
|
|
||||||
URL:
|
|
||||||
proxy: SCHEME://[USER:PASS@][HOST]:PORT
|
|
||||||
chain: proxy,proxy[,proxy]...
|
|
||||||
|
|
||||||
e.g. -listen socks5://:1080
|
|
||||||
-listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// (protocol chain)
|
|
||||||
|
|
||||||
e.g. -forward socks5://server:1080
|
|
||||||
-forward tls://server.com:443,http:// (protocol chain)
|
|
||||||
-forward socks5://serverA:1080,socks5://serverB:1080 (proxy chain)
|
|
||||||
|
|
||||||
SCHEME:
|
|
||||||
listen : %s
|
|
||||||
forward: %s
|
|
||||||
|
|
||||||
Note: use 'glider -scheme all' or 'glider -scheme SCHEME' to see help info for the scheme.
|
|
||||||
|
|
||||||
--
|
|
||||||
Forwarder Options: FORWARD_URL#OPTIONS
|
|
||||||
priority : the priority of that forwarder, the larger the higher, default: 0
|
|
||||||
interface: the local interface or ip address used to connect remote server.
|
|
||||||
|
|
||||||
e.g. -forward socks5://server:1080#priority=100
|
|
||||||
-forward socks5://server:1080#interface=eth0
|
|
||||||
-forward socks5://server:1080#priority=100&interface=192.168.1.99
|
|
||||||
|
|
||||||
Services:
|
|
||||||
dhcpd: service=dhcpd,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
|
|
||||||
service=dhcpd-failover,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
|
|
||||||
e.g. service=dhcpd,eth1,192.168.1.100,192.168.1.199,720
|
|
||||||
|
|
||||||
--
|
|
||||||
Help:
|
|
||||||
glider -help
|
|
||||||
glider -scheme all
|
|
||||||
glider -example
|
|
||||||
|
|
||||||
see README.md and glider.conf.example for more details.
|
|
||||||
--
|
|
||||||
glider %s, https://github.com/nadoo/glider (glider.proxy@gmail.com)
|
|
||||||
`
|
|
||||||
|
|
||||||
var examples = `
|
|
||||||
Examples:
|
|
||||||
glider -config glider.conf
|
|
||||||
-run glider with specified config file.
|
|
||||||
|
|
||||||
glider -listen :8443 -verbose
|
|
||||||
-listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode.
|
|
||||||
|
|
||||||
glider -listen socks5://:1080 -listen http://:8080 -verbose
|
|
||||||
-multiple listeners: listen on :1080 as socks5 proxy server, and on :8080 as http proxy server.
|
|
||||||
|
|
||||||
glider -listen :8443 -forward direct://#interface=eth0 -forward direct://#interface=eth1
|
|
||||||
-multiple forwarders: listen on 8443 and forward requests via interface eth0 and eth1 in round robin mode.
|
|
||||||
|
|
||||||
glider -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose
|
|
||||||
-protocol chain: listen on :443 as a https(http over tls) proxy server.
|
|
||||||
|
|
||||||
glider -listen http://:8080 -forward socks5://serverA:1080,socks5://serverB:1080
|
|
||||||
-proxy chain: listen on :8080 as a http proxy server, forward all requests via forward chain.
|
|
||||||
|
|
||||||
glider -listen :8443 -forward socks5://serverA:1080 -forward socks5://serverB:1080#priority=10 -forward socks5://serverC:1080#priority=10
|
|
||||||
-forwarder priority: serverA will only be used when serverB and serverC are not available.
|
|
||||||
|
|
||||||
glider -listen tcp://:80 -forward tcp://serverA:80
|
|
||||||
-tcp tunnel: listen on :80 and forward all requests to serverA:80.
|
|
||||||
|
|
||||||
glider -listen udp://:53 -forward socks5://serverA:1080,udp://8.8.8.8:53
|
|
||||||
-udp tunnel: listen on :53 and forward all udp requests to 8.8.8.8:53 via remote socks5 server.
|
|
||||||
|
|
||||||
glider -verbose -dns=:53 -dnsserver=8.8.8.8:53 -forward socks5://serverA:1080 -dnsrecord=abc.com/1.2.3.4
|
|
||||||
-dns over proxy: listen on :53 as dns server, forward to 8.8.8.8:53 via socks5 server.
|
|
||||||
`
|
|
@ -4,15 +4,7 @@ Command:
|
|||||||
```bash
|
```bash
|
||||||
glider -config glider.conf
|
glider -config glider.conf
|
||||||
```
|
```
|
||||||
Config file, **just use the command line flag name as key name**:
|
Config file, **just use the command line flag name as the key name**:
|
||||||
```bash
|
|
||||||
# COMMENT LINE
|
|
||||||
KEY=VALUE
|
|
||||||
KEY=VALUE
|
|
||||||
# KEY equals to command line flag name: listen forward strategy...
|
|
||||||
```
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```bash
|
```bash
|
||||||
### glider config file
|
### glider config file
|
||||||
|
|
||||||
@ -31,11 +23,13 @@ forward=ss://method:pass@1.1.1.1:8443
|
|||||||
# upstream forward proxy (forward chain)
|
# upstream forward proxy (forward chain)
|
||||||
forward=http://1.1.1.1:8080,socks5://2.2.2.2:1080
|
forward=http://1.1.1.1:8080,socks5://2.2.2.2:1080
|
||||||
|
|
||||||
# multiple upstream proxies forward strategy
|
# multiple upstream proxies forwad strategy
|
||||||
strategy=rr
|
strategy=rr
|
||||||
|
|
||||||
# forwarder health check
|
# Used to connect via forwarders, if the host is unreachable, the forwarder
|
||||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
# will be set to disabled.
|
||||||
|
# MUST be a HTTP website server address, format: HOST[:PORT]. HTTPS NOT SUPPORTED.
|
||||||
|
checkwebsite=www.apple.com
|
||||||
|
|
||||||
# check interval
|
# check interval
|
||||||
checkinterval=30
|
checkinterval=30
|
||||||
@ -56,8 +50,8 @@ rules-dir=rules.d
|
|||||||
#include=more.inc.conf
|
#include=more.inc.conf
|
||||||
```
|
```
|
||||||
See:
|
See:
|
||||||
- [glider.conf.example](glider.conf.example)
|
- [glider.conf.example](config/glider.conf.example)
|
||||||
- [examples](examples)
|
- [examples](config/examples)
|
||||||
|
|
||||||
## Rule File
|
## Rule File
|
||||||
Rule file, **same as the config file but specify forwarders based on destinations**:
|
Rule file, **same as the config file but specify forwarders based on destinations**:
|
||||||
@ -67,7 +61,7 @@ forward=socks5://192.168.1.10:1080
|
|||||||
forward=ss://method:pass@1.1.1.1:8443
|
forward=ss://method:pass@1.1.1.1:8443
|
||||||
forward=http://192.168.2.1:8080,socks5://192.168.2.2:1080
|
forward=http://192.168.2.1:8080,socks5://192.168.2.2:1080
|
||||||
strategy=rr
|
strategy=rr
|
||||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
checkwebsite=www.apple.com
|
||||||
checkinterval=30
|
checkinterval=30
|
||||||
|
|
||||||
# DNS SERVER for domains in this rule file
|
# DNS SERVER for domains in this rule file
|
||||||
|
@ -12,8 +12,10 @@ forward=http://1.1.1.1:8080
|
|||||||
# High Availability mode: ha
|
# High Availability mode: ha
|
||||||
strategy=rr
|
strategy=rr
|
||||||
|
|
||||||
# forwarder health check
|
# Used to connect via forwarders, if the host is unreachable, the forwarder
|
||||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
# will be set to disabled.
|
||||||
|
# MUST be a HTTP website server address, format: HOST[:PORT]. HTTPS NOT SUPPORTED.
|
||||||
|
checkwebsite=www.apple.com
|
||||||
|
|
||||||
# check interval(seconds)
|
# check interval(seconds)
|
||||||
checkinterval=30
|
checkinterval=30
|
||||||
|
@ -8,8 +8,7 @@ forward=http://1.1.1.1:8080
|
|||||||
# High Availability mode: ha
|
# High Availability mode: ha
|
||||||
strategy=rr
|
strategy=rr
|
||||||
|
|
||||||
# forwarder health check
|
checkwebsite=www.apple.com
|
||||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
|
||||||
checkinterval=30
|
checkinterval=30
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,8 +13,7 @@ forward=http://1.1.1.1:8080
|
|||||||
# High Availability mode: ha
|
# High Availability mode: ha
|
||||||
strategy=rr
|
strategy=rr
|
||||||
|
|
||||||
# forwarder health check
|
checkwebsite=www.apple.com
|
||||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
|
||||||
checkinterval=30
|
checkinterval=30
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
|
|
||||||
|
|
||||||
forward=http://forwarder1:8080
|
forward=http://forwarder4:8080
|
||||||
|
|
||||||
# first connect forwarder1 then forwarder2 then internet
|
# first connect forwarder1 then forwarder2 then internet
|
||||||
forward=http://forwarder1:8080,socks5://forwarder2:1080
|
forward=http://forwarder5:8080,socks6://forwarder3:1080
|
||||||
|
|
||||||
|
|
||||||
# Round Robin mode: rr
|
# Round Robin mode: rr
|
||||||
# High Availability mode: ha
|
# High Availability mode: ha
|
||||||
strategy=rr
|
strategy=rr
|
||||||
|
|
||||||
# forwarder health check
|
checkwebsite=www.apple.com
|
||||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
|
||||||
checkinterval=30
|
checkinterval=30
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,8 +10,7 @@ forward=http://forwarder2:8080,socks5://forwarder3:1080
|
|||||||
# High Availability mode: ha
|
# High Availability mode: ha
|
||||||
strategy=rr
|
strategy=rr
|
||||||
|
|
||||||
# forwarder health check
|
checkwebsite=www.apple.com
|
||||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
|
||||||
checkinterval=30
|
checkinterval=30
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,12 +11,13 @@ forward=http://1.1.1.1:8080
|
|||||||
dns=:5353
|
dns=:5353
|
||||||
dnsserver=8.8.8.8:53
|
dnsserver=8.8.8.8:53
|
||||||
strategy=rr
|
strategy=rr
|
||||||
|
checkwebsite=www.apple.com
|
||||||
checkinterval=30
|
checkinterval=30
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Create a ipset manually
|
#### Create a ipset manually
|
||||||
```bash
|
```bash
|
||||||
ipset create myset hash:net
|
ipset create myset hash:ip
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Config dnsmasq
|
#### Config dnsmasq
|
||||||
|
@ -12,6 +12,5 @@ dnsserver=8.8.8.8:53
|
|||||||
|
|
||||||
|
|
||||||
strategy=rr
|
strategy=rr
|
||||||
# forwarder health check
|
checkwebsite=www.apple.com
|
||||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
|
||||||
checkinterval=30
|
checkinterval=30
|
||||||
|
@ -38,7 +38,8 @@ rules-dir=rules.d
|
|||||||
forward=http://forwarder1:8080,socks5://forwarder2:1080
|
forward=http://forwarder1:8080,socks5://forwarder2:1080
|
||||||
forward=http://1.1.1.1:8080
|
forward=http://1.1.1.1:8080
|
||||||
strategy=rr
|
strategy=rr
|
||||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
checkwebsite=www.apple.com
|
||||||
|
checkinterval=30
|
||||||
|
|
||||||
# specify a different dns server(if need)
|
# specify a different dns server(if need)
|
||||||
dnsserver=208.67.222.222:53
|
dnsserver=208.67.222.222:53
|
||||||
@ -82,20 +83,19 @@ Set server's nameserver to glider:
|
|||||||
echo nameserver 127.0.0.1 > /etc/resolv.conf
|
echo nameserver 127.0.0.1 > /etc/resolv.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Client settings
|
#### Client DNS settings
|
||||||
Use the linux server's ip as your gateway.
|
|
||||||
Use the linux server's ip as your dns server.
|
Use the linux server's ip as your dns server.
|
||||||
|
|
||||||
#### When client requesting to access http://example1.com (in office.rule), the whole process:
|
#### When client requesting to access http://example1.com (in office.rule), the whole process:
|
||||||
DNS Resolving:
|
DNS Resolving:
|
||||||
1. client sends a udp dns request to linux server, and glider will receive the request(as it listens on the default dns port :53)
|
1. client sends a udp dns request to linux server, and glider will receive the request(as it listen on default dns port :53)
|
||||||
2. upstream dns server choice: glider will lookup it's rule config and find out the dns server to use for this domain(matched "example1.com" in office.rule, so 208.67.222.222:53 will be chosen)
|
2. upstream dns server choice: glider will lookup it's rule config and find out the dns server to use for this domain(matched "example1.com" in office.rule, so 208.67.222.222:53 will be chosen)
|
||||||
3. glider uses the forwarder in office.rule to ask 208.67.222.222:53 for the resolve answers(dns over proxy).
|
3. glider uses the forwarder in office.rule to ask 208.67.222.222:53 for the resolve answers.
|
||||||
4. glider updates it's office rule config, adds the resolved ip address to it.
|
4. glider updates it's office rule config, add the resolved ip address to it.
|
||||||
5. glider adds the resolved ip into ipset "glider", and returns the dns answer to client.
|
5. glider adds the resolved ip into ipset "glider", and return the dns answer to client.
|
||||||
|
|
||||||
Destination Accessing:
|
Destination Accessing:
|
||||||
1. client sends http request to the resolved ip of example1.com.
|
1. client sends http request to the resolved ip of example1.com.
|
||||||
2. linux gateway server will get the request.
|
2. linux gateway server will get the request.
|
||||||
3. iptables matches the ip in ipset "glider" and redirect this request to :1081(glider)
|
3. iptabes matches the ip in ipset "glider" and redirect this request to :1081(glider)
|
||||||
4. glider finds the ip in office rule, and then choose a forwarder in office.rule to complete the request.
|
4. glider finds the ip in office rule, and then choose a forwarder in office.rule to complete the request.
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
|
|
||||||
|
|
||||||
forward=http://forwarder1:8080
|
forward=http://forwarder4:8080
|
||||||
|
|
||||||
# first connect forwarder1 then forwarder2 then internet
|
# first connect forwarder1 then forwarder2 then internet
|
||||||
forward=http://forwarder1:8080,socks5://forwarder2:1080
|
forward=http://forwarder5:8080,socks5://forwarder3:1080
|
||||||
|
|
||||||
|
|
||||||
# Round Robin mode: rr
|
# Round Robin mode: rr
|
||||||
# High Availability mode: ha
|
# High Availability mode: ha
|
||||||
strategy=rr
|
strategy=rr
|
||||||
|
|
||||||
# forwarder health check
|
checkwebsite=www.apple.com
|
||||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
|
||||||
checkinterval=30
|
checkinterval=30
|
||||||
|
|
||||||
# as a ipset manager
|
# as a ipset manager
|
||||||
|
@ -9,8 +9,7 @@ forward=http://forwarder2:8080,socks5://forwarder3:1080
|
|||||||
# Round Robin mode: rr
|
# Round Robin mode: rr
|
||||||
# High Availability mode: ha
|
# High Availability mode: ha
|
||||||
strategy=rr
|
strategy=rr
|
||||||
# forwarder health check
|
checkwebsite=www.apple.com
|
||||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
|
||||||
checkinterval=30
|
checkinterval=30
|
||||||
|
|
||||||
# specify a different dns server(if need)
|
# specify a different dns server(if need)
|
||||||
|
@ -32,28 +32,28 @@ verbose=True
|
|||||||
# different protocols.
|
# different protocols.
|
||||||
|
|
||||||
# listen on 8443, serve as http/socks5 proxy on the same port.
|
# listen on 8443, serve as http/socks5 proxy on the same port.
|
||||||
# listen=:8443
|
listen=:8443
|
||||||
listen=127.0.0.1:8443
|
|
||||||
|
|
||||||
# listen on 8448 as a ss server.
|
# listen on 8448 as a ss server.
|
||||||
# listen=ss://AEAD_CHACHA20_POLY1305:pass@:8448
|
# listen=ss://AEAD_CHACHA20_POLY1305:pass@:8448
|
||||||
|
|
||||||
# listen on 8080 as a http proxy server.
|
# listen on 8080 as a http proxy server.
|
||||||
# listen=http://:8080
|
listen=http://:8080
|
||||||
|
|
||||||
# listen on 1080 as a socks5 proxy server.
|
# listen on 1080 as a socks5 proxy server.
|
||||||
# listen=socks5://:1080
|
listen=socks5://:1080
|
||||||
|
|
||||||
# listen on 1234 as vless proxy server.
|
|
||||||
# listen=vless://uuid@:1234
|
|
||||||
# listen on 1234 as vless proxy server, fallback to 127.0.0.1:8080 http server when client auth failed.
|
|
||||||
# listen=vless://uuid@:1234?fallback=127.0.0.1:8080
|
|
||||||
|
|
||||||
# listen on 1081 as a linux transparent proxy server.
|
# listen on 1081 as a linux transparent proxy server.
|
||||||
# listen=redir://:1081
|
# listen=redir://:1081
|
||||||
|
|
||||||
# listen on 1082 as a linux transparent proxy server(tproxy).
|
# listen on 1082 as a tcp tunnel, all requests to :1082 will be forward to 1.1.1.1:80
|
||||||
# listen=tproxy://:1082
|
# listen=tcptun://:1082=1.1.1.1:80
|
||||||
|
|
||||||
|
# listen on 1083 as a udp tunnel, all requests to :1083 will be forward to 1.1.1.1:53
|
||||||
|
# listen=udptun://:1083=1.1.1.1:53
|
||||||
|
|
||||||
|
# listen on 1084 as a udp over tcp tunnel, all requests to :1084 will be forward to 1.1.1.1:53
|
||||||
|
# listen=uottun://:1084=1.1.1.1:53
|
||||||
|
|
||||||
# http over tls (HTTPS proxy)
|
# http over tls (HTTPS proxy)
|
||||||
# listen=tls://:443?cert=crtFilePath&key=keyFilePath,http://
|
# listen=tls://:443?cert=crtFilePath&key=keyFilePath,http://
|
||||||
@ -62,28 +62,10 @@ listen=127.0.0.1:8443
|
|||||||
# listen=tls://:443?cert=crtFilePath&key=keyFilePath,ss://AEAD_CHACHA20_POLY1305:pass@
|
# listen=tls://:443?cert=crtFilePath&key=keyFilePath,ss://AEAD_CHACHA20_POLY1305:pass@
|
||||||
|
|
||||||
# socks5 over unix domain socket
|
# socks5 over unix domain socket
|
||||||
# listen=unix:///dev/shm/socket,socks5://
|
# listen=unix:///tmp/glider.socket,socks5://
|
||||||
|
|
||||||
# socks5 over vm socket
|
|
||||||
# listen=vsock://:1234,socks5://
|
|
||||||
|
|
||||||
# socks5 over kcp
|
# socks5 over kcp
|
||||||
# listen=kcp://aes:key@127.0.0.1:8444?dataShards=10&parityShards=3&mode=fast,socks5://
|
# listen=kcp://aes:key@127.0.0.1:8444?dataShards=10&parityShards=3,socks5://
|
||||||
|
|
||||||
# vless server
|
|
||||||
# listen=vless://UUID@:1234
|
|
||||||
|
|
||||||
# vless over tls server
|
|
||||||
# listen=tls://:1234?cert=/path/to/cert&key=/path/to/key,vless://UUID@?fallback=127.0.0.1:80
|
|
||||||
|
|
||||||
# vless over ws
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# trojanc server (trojan without tls)
|
|
||||||
# listen=trojanc://PASSWORD@:1234?fallback=127.0.0.1
|
|
||||||
|
|
||||||
# FORWARDERS
|
# FORWARDERS
|
||||||
# ----------
|
# ----------
|
||||||
@ -112,51 +94,36 @@ listen=127.0.0.1:8443
|
|||||||
# SSR proxy as forwarder
|
# SSR proxy as forwarder
|
||||||
# forward=ssr://method:pass@1.1.1.1:8443?protocol=auth_aes128_md5&protocol_param=xxx&obfs=tls1.2_ticket_auth&obfs_param=yyy
|
# forward=ssr://method:pass@1.1.1.1:8443?protocol=auth_aes128_md5&protocol_param=xxx&obfs=tls1.2_ticket_auth&obfs_param=yyy
|
||||||
|
|
||||||
# ssh forwarder
|
|
||||||
# forward=ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS]
|
|
||||||
# forward=ssh://root:pass@host:port
|
|
||||||
# forward=ssh://root@host:port?key=/path/to/keyfile
|
|
||||||
# forward=ssh://root@host:port?key=/path/to/keyfile&timeout=5
|
|
||||||
|
|
||||||
# http proxy as forwarder
|
# http proxy as forwarder
|
||||||
# forward=http://1.1.1.1:8080
|
# forward=http://1.1.1.1:8080
|
||||||
|
|
||||||
# trojan as forwarder
|
# vmess with none security
|
||||||
# forward=trojan://PASSWORD@1.1.1.1:8080[?serverName=SERVERNAME][&skipVerify=true]
|
|
||||||
|
|
||||||
# trojanc as forwarder
|
|
||||||
# forward=trojanc://PASSWORD@1.1.1.1:8080
|
|
||||||
|
|
||||||
# vless forwarder
|
|
||||||
# forward=vless://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443
|
|
||||||
|
|
||||||
# vmess with aead auth
|
|
||||||
# forward=vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443
|
|
||||||
|
|
||||||
# vmess with md5 auth (by setting alterID)
|
|
||||||
# forward=vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443?alterID=2
|
# forward=vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443?alterID=2
|
||||||
|
|
||||||
|
# vmess with aes-128-gcm security
|
||||||
|
# forward=vmess://aes-128-gcm:5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443?alterID=2
|
||||||
|
|
||||||
# vmess over tls
|
# vmess over tls
|
||||||
# forward=tls://server.com:443,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98
|
# forward=tls://1.1.1.1:443,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
||||||
|
|
||||||
# vmess over websocket
|
# vmess over websocket
|
||||||
# forward=ws://1.1.1.1:80/path?host=server.com,vmess://chacha20-poly1305:5a146038-0b56-4e95-b1dc-5c6f5a32cd98@
|
# forward=ws://1.1.1.1:80/path,vmess://chacha20-poly1305:5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
||||||
|
|
||||||
# vmess over ws over tls
|
# vmess over ws over tls
|
||||||
# forward=tls://server.com:443,ws://,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98
|
# forward=tls://1.1.1.1:443,ws://,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
||||||
# forward=tls://server.com:443,ws://@/path,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98
|
# forward=tls://1.1.1.1:443,ws://@/path,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
||||||
|
|
||||||
# ss over tls
|
# ss over tls
|
||||||
# forward=tls://server.com:443,ss://AEAD_CHACHA20_POLY1305:pass@
|
# forward=tls://1.1.1.1:443,ss://AEAD_CHACHA20_POLY1305:pass@
|
||||||
|
|
||||||
# ss over kcp
|
# ss over kcp
|
||||||
# forward=kcp://aes:key@127.0.0.1:8444?dataShards=10&parityShards=3&mode=fast,ss://AEAD_CHACHA20_POLY1305:pass@
|
# forward=kcp://aes:key@127.0.0.1:8444?dataShards=10&parityShards=3,ss://AEAD_CHACHA20_POLY1305:pass@
|
||||||
|
|
||||||
# ss with simple-obfs
|
# ss with simple-obfs
|
||||||
# forward=simple-obfs://1.1.1.1:443?type=tls&host=apple.com,ss://AEAD_CHACHA20_POLY1305:pass@
|
# forward=simple-obfs://1.1.1.1:443?type=tls&host=apple.com,ss://AEAD_CHACHA20_POLY1305:pass@
|
||||||
|
|
||||||
# socks5 over unix domain socket
|
# socks5 over unix domain socket
|
||||||
# forward=unix:///dev/shm/socket,socks5://
|
# forward=unix:///tmp/glider.socket,socks5://
|
||||||
|
|
||||||
# FORWARDER CHAIN
|
# FORWARDER CHAIN
|
||||||
# ---------------
|
# ---------------
|
||||||
@ -175,55 +142,28 @@ listen=127.0.0.1:8443
|
|||||||
# Destination Hashing mode: dh
|
# Destination Hashing mode: dh
|
||||||
strategy=rr
|
strategy=rr
|
||||||
|
|
||||||
# FORWARDER SETTINGS
|
|
||||||
# ------------------
|
|
||||||
# We can set some parameters for forwarders.
|
|
||||||
|
|
||||||
# forwarder will be set to disabled on how many failures counted(both dial and relay).
|
|
||||||
maxfailures=3
|
|
||||||
|
|
||||||
# timeout for create a connection(seconds)
|
|
||||||
# dialtimeout=3
|
|
||||||
|
|
||||||
# timeout for relay data from proxy server and client(seconds)
|
|
||||||
# DO NOT change it if you don't know what will happen.
|
|
||||||
# relaytimeout=0
|
|
||||||
|
|
||||||
|
|
||||||
# FORWARDERS CHECK
|
# FORWARDERS CHECK
|
||||||
# ----------------
|
# ----------------
|
||||||
# We can check whether a forwarder is available.
|
# We can check whether a forwarder is available.
|
||||||
|
|
||||||
# Forwarder health check:
|
# Used to connect via forwarders, if the host is unreachable, the forwarder
|
||||||
# check=tcp[://HOST:PORT]: tcp port connect check
|
# will be set to disabled.
|
||||||
# check=http://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
|
# MUST be a HTTP website server address, format: HOST[:PORT]. HTTPS NOT SUPPORTED.
|
||||||
# check=https://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
|
checkwebsite=www.apple.com
|
||||||
# e.g. check=https://www.netflix.com/title/81215567#expect=301|404
|
|
||||||
# check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR,FORWARDER_URL
|
|
||||||
# check=disable: disable health check
|
|
||||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
|
||||||
|
|
||||||
# check interval(seconds)
|
# check interval(seconds)
|
||||||
checkinterval=30
|
checkinterval=30
|
||||||
|
|
||||||
# timeout to set a forwarder to be disabled(seconds)
|
# check timeout(seconds)
|
||||||
checktimeout=10
|
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
|
|
||||||
|
|
||||||
# DNS FORWARDING SERVER
|
# DNS FORWARDING SERVER
|
||||||
# ----------------
|
# ----------------
|
||||||
# we can specify different upstream dns server in rule file for different destinations.
|
# we can specify different upstream dns server in rule file for different destinations
|
||||||
|
|
||||||
# Setup a dns forwarding server
|
# Setup a dns forwarding server
|
||||||
# dns=:53
|
dns=:53
|
||||||
|
|
||||||
# global remote dns server (you can specify different dns server in rule file)
|
# global remote dns server (you can specify different dns server in rule file)
|
||||||
dnsserver=8.8.8.8:53
|
dnsserver=8.8.8.8:53
|
||||||
@ -243,55 +183,30 @@ dnsmaxttl=1800
|
|||||||
# minimum TTL value for entries in the CACHE(seconds)
|
# minimum TTL value for entries in the CACHE(seconds)
|
||||||
dnsminttl=0
|
dnsminttl=0
|
||||||
|
|
||||||
# size of CACHE
|
|
||||||
dnscachesize=4096
|
|
||||||
|
|
||||||
# show query log of dns cache
|
|
||||||
dnscachelog=True
|
|
||||||
|
|
||||||
# disable AAAA queries
|
|
||||||
# dnsnoaaaa=True
|
|
||||||
|
|
||||||
# custom records
|
# custom records
|
||||||
dnsrecord=www.example.com/1.2.3.4
|
dnsrecord=www.example.com/1.2.3.4
|
||||||
dnsrecord=www.example.com/2606:2800:220:1:248:1893:25c8:1946
|
dnsrecord=www.example.com/2606:2800:220:1:248:1893:25c8:1946
|
||||||
|
|
||||||
# SERVICES
|
|
||||||
# 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
|
# INTERFACE SPECIFIC
|
||||||
# ------------------
|
# ------------------
|
||||||
# Specify global outbound ip/interface.
|
# Specify the outbound ip/interface.
|
||||||
#
|
#
|
||||||
# interface=""
|
# interface=""
|
||||||
# interface="192.168.1.100"
|
# interface="192.168.1.100"
|
||||||
# interface="eth0"
|
# interface="eth0"
|
||||||
#
|
|
||||||
# Specify interface for a forwarder:
|
|
||||||
# forward=socks5://192.168.1.10:1080#priority=100&interface=eth0
|
|
||||||
# forward=socks5://192.168.1.10:1080#priority=100&interface=192.168.1.100
|
|
||||||
|
|
||||||
# RULE FILES
|
# RULE FILES
|
||||||
# ----------
|
# ----------
|
||||||
# Specify additional forward rules.
|
# Specify additional forward rules.
|
||||||
#
|
|
||||||
# specify rules folder, so all *.rule files under this folder will be parsed as rule file
|
# specify rules folder, so all *.rule files under this folder will be parsed as rule file
|
||||||
# rules-dir=rules.d
|
rules-dir=rules.d
|
||||||
#
|
|
||||||
# specify a rule file
|
# specify a rule file
|
||||||
#rulefile=office.rule
|
#rulefile=office.rule
|
||||||
#rulefile=home.rule
|
#rulefile=home.rule
|
||||||
|
|
||||||
# INCLUDE CONFIG FILES
|
|
||||||
# ----------
|
# INCLUDE MORE CONFIG FILES
|
||||||
#include=dnsrecord.inc.conf
|
#include=dnsrecord.inc.conf
|
||||||
#include=more.conf
|
#include=more.conf
|
||||||
|
|
||||||
# ENVIRONMENT VARIABLES
|
|
||||||
# ----------
|
|
||||||
# use {$ENV_VAR_NAME} in VALUE to get the Environment Variable value.
|
|
||||||
# forward=socks5://{$USER_NAME}:{$USER_PASS}@:1080
|
|
||||||
|
@ -14,7 +14,7 @@ forward=http://192.168.2.1:8080,socks5://192.168.2.2:1080
|
|||||||
strategy=rr
|
strategy=rr
|
||||||
|
|
||||||
# FORWARDER CHECK SETTINGS
|
# FORWARDER CHECK SETTINGS
|
||||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
checkwebsite=www.apple.com
|
||||||
checkinterval=30
|
checkinterval=30
|
||||||
|
|
||||||
# DNS SERVER for domains in this rule file
|
# DNS SERVER for domains in this rule file
|
||||||
@ -26,7 +26,6 @@ dnsserver=208.67.222.222:53
|
|||||||
# - add ip/cidrs in rule files on startup
|
# - add ip/cidrs in rule files on startup
|
||||||
# - add resolved ips for domains in rule files by dns forwarding server
|
# - add resolved ips for domains in rule files by dns forwarding server
|
||||||
# Usually used in transparent proxy mode on linux
|
# Usually used in transparent proxy mode on linux
|
||||||
# Note: this will create 2 ipsets, glider for ipv4 and glider6 for ipv6
|
|
||||||
ipset=glider
|
ipset=glider
|
||||||
|
|
||||||
# DESTINATIONS
|
# DESTINATIONS
|
||||||
|
18
dev.go
Normal file
18
dev.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
//+build dev
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
_ "net/http/pprof"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
go func() {
|
||||||
|
err := http.ListenAndServe(":6060", nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Create pprof server error: %s\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
7
dev_linux.go
Normal file
7
dev_linux.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
//+build dev
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/nadoo/glider/proxy/tproxy"
|
||||||
|
)
|
149
dns/cache.go
149
dns/cache.go
@ -5,123 +5,62 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LruCache is the struct of LruCache.
|
// LongTTL is 50 years duration in seconds, used for none-expired items.
|
||||||
type LruCache struct {
|
const LongTTL = 50 * 365 * 24 * 3600
|
||||||
mu sync.Mutex
|
|
||||||
size int
|
|
||||||
head *item
|
|
||||||
tail *item
|
|
||||||
cache map[string]*item
|
|
||||||
store map[string][]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// item is the struct of cache item.
|
|
||||||
type item struct {
|
type item struct {
|
||||||
key string
|
value []byte
|
||||||
val []byte
|
expire time.Time
|
||||||
exp int64
|
|
||||||
prev *item
|
|
||||||
next *item
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLruCache returns a new LruCache.
|
// Cache is the struct of cache.
|
||||||
func NewLruCache(size int) *LruCache {
|
type Cache struct {
|
||||||
// init 2 items here, it doesn't matter cuz they will be deleted when the cache is full
|
m map[string]*item
|
||||||
head, tail := &item{key: "head"}, &item{key: "tail"}
|
l sync.RWMutex
|
||||||
head.next, tail.prev = tail, head
|
|
||||||
c := &LruCache{
|
|
||||||
size: size,
|
|
||||||
head: head,
|
|
||||||
tail: tail,
|
|
||||||
cache: make(map[string]*item, size),
|
|
||||||
store: make(map[string][]byte),
|
|
||||||
}
|
|
||||||
c.cache[head.key], c.cache[tail.key] = head, tail
|
|
||||||
return c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets an item from cache.
|
// NewCache returns a new cache.
|
||||||
func (c *LruCache) Get(k string) (v []byte, expired bool) {
|
func NewCache() (c *Cache) {
|
||||||
c.mu.Lock()
|
c = &Cache{m: make(map[string]*item)}
|
||||||
defer c.mu.Unlock()
|
go func() {
|
||||||
|
for now := range time.Tick(time.Second) {
|
||||||
if v, ok := c.store[k]; ok {
|
c.l.Lock()
|
||||||
return v, false
|
for k, v := range c.m {
|
||||||
|
if now.After(v.expire) {
|
||||||
|
delete(c.m, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
if it, ok := c.cache[k]; ok {
|
|
||||||
v = it.val
|
|
||||||
if it.exp < time.Now().Unix() {
|
|
||||||
expired = true
|
|
||||||
}
|
}
|
||||||
c.moveToHead(it)
|
c.l.Unlock()
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set sets an item with key, value, and ttl(seconds).
|
// Len returns the length of cache.
|
||||||
// if the ttl is zero, this item will be set and never be deleted.
|
func (c *Cache) Len() int {
|
||||||
// if the key exists, update it with value and exp and move it to head.
|
return len(c.m)
|
||||||
// if the key does not exist, put a new item to the cache's head.
|
}
|
||||||
// finally, remove the tail if the cache is full.
|
|
||||||
func (c *LruCache) Set(k string, v []byte, ttl int) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
if ttl == 0 {
|
// Put an item into cache, invalid after ttl seconds.
|
||||||
c.store[k] = v
|
func (c *Cache) Put(k string, v []byte, ttl int) {
|
||||||
|
if len(v) != 0 {
|
||||||
|
c.l.Lock()
|
||||||
|
it, ok := c.m[k]
|
||||||
|
if !ok {
|
||||||
|
it = &item{value: v}
|
||||||
|
c.m[k] = it
|
||||||
|
}
|
||||||
|
it.expire = time.Now().Add(time.Duration(ttl) * time.Second)
|
||||||
|
c.l.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get an item from cache.
|
||||||
|
func (c *Cache) Get(k string) (v []byte) {
|
||||||
|
c.l.RLock()
|
||||||
|
if it, ok := c.m[k]; ok {
|
||||||
|
v = it.value
|
||||||
|
}
|
||||||
|
c.l.RUnlock()
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
exp := time.Now().Add(time.Second * time.Duration(ttl)).Unix()
|
|
||||||
if it, ok := c.cache[k]; ok {
|
|
||||||
it.val = v
|
|
||||||
it.exp = exp
|
|
||||||
c.moveToHead(it)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.putToHead(k, v, exp)
|
|
||||||
|
|
||||||
// NOTE: the cache size will always >= 2,
|
|
||||||
// but it doesn't matter in our environment.
|
|
||||||
if len(c.cache) > c.size {
|
|
||||||
c.removeTail()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// putToHead puts a new item to cache's head.
|
|
||||||
func (c *LruCache) putToHead(k string, v []byte, exp int64) {
|
|
||||||
it := &item{key: k, val: v, exp: exp, prev: nil, next: c.head}
|
|
||||||
it.prev = nil
|
|
||||||
it.next = c.head
|
|
||||||
c.head.prev = it
|
|
||||||
c.head = it
|
|
||||||
|
|
||||||
c.cache[k] = it
|
|
||||||
}
|
|
||||||
|
|
||||||
// moveToHead moves an existing item to cache's head.
|
|
||||||
func (c *LruCache) moveToHead(it *item) {
|
|
||||||
if it != c.head {
|
|
||||||
if c.tail == it {
|
|
||||||
c.tail = it.prev
|
|
||||||
c.tail.next = nil
|
|
||||||
} else {
|
|
||||||
it.prev.next = it.next
|
|
||||||
it.next.prev = it.prev
|
|
||||||
}
|
|
||||||
it.prev = nil
|
|
||||||
it.next = c.head
|
|
||||||
c.head.prev = it
|
|
||||||
c.head = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeTail removes the tail from cache.
|
|
||||||
func (c *LruCache) removeTail() {
|
|
||||||
delete(c.cache, c.tail.key)
|
|
||||||
|
|
||||||
c.tail.prev.next = nil
|
|
||||||
c.tail = c.tail.prev
|
|
||||||
}
|
}
|
||||||
|
259
dns/client.go
259
dns/client.go
@ -1,22 +1,20 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
"github.com/nadoo/glider/common/log"
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AnswerHandler function handles the dns TypeA or TypeAAAA answer.
|
// HandleFunc function handles the dns TypeA or TypeAAAA answer.
|
||||||
type AnswerHandler func(domain string, ip netip.Addr) error
|
type HandleFunc func(Domain, ip string) error
|
||||||
|
|
||||||
// Config for dns.
|
// Config for dns.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@ -26,73 +24,51 @@ type Config struct {
|
|||||||
MinTTL int
|
MinTTL int
|
||||||
Records []string
|
Records []string
|
||||||
AlwaysTCP bool
|
AlwaysTCP bool
|
||||||
CacheSize int
|
|
||||||
CacheLog bool
|
|
||||||
NoAAAA bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client is a dns client struct.
|
// Client is a dns client struct.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
proxy proxy.Proxy
|
proxy proxy.Proxy
|
||||||
cache *LruCache
|
cache *Cache
|
||||||
config *Config
|
config *Config
|
||||||
upStream *UPStream
|
upServers []string
|
||||||
upStreamMap map[string]*UPStream
|
upServerMap map[string][]string
|
||||||
handlers []AnswerHandler
|
handlers []HandleFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient returns a new dns client.
|
// NewClient returns a new dns client.
|
||||||
func NewClient(proxy proxy.Proxy, config *Config) (*Client, error) {
|
func NewClient(proxy proxy.Proxy, config *Config) (*Client, error) {
|
||||||
c := &Client{
|
c := &Client{
|
||||||
proxy: proxy,
|
proxy: proxy,
|
||||||
cache: NewLruCache(config.CacheSize),
|
cache: NewCache(),
|
||||||
config: config,
|
config: config,
|
||||||
upStream: NewUPStream(config.Servers),
|
upServers: config.Servers,
|
||||||
upStreamMap: make(map[string]*UPStream),
|
upServerMap: make(map[string][]string),
|
||||||
}
|
}
|
||||||
|
|
||||||
// custom records
|
// custom records
|
||||||
for _, record := range config.Records {
|
for _, record := range config.Records {
|
||||||
if err := c.AddRecord(record); err != nil {
|
c.AddRecord(record)
|
||||||
log.F("[dns] add record '%s' error: %s", record, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exchange handles request message and returns response message.
|
// Exchange handles request message and returns response message.
|
||||||
// TODO: optimize it
|
// reqBytes = reqLen + reqMsg
|
||||||
func (c *Client) Exchange(reqBytes []byte, clientAddr string, preferTCP bool) ([]byte, error) {
|
func (c *Client) Exchange(reqBytes []byte, clientAddr string, preferTCP bool) ([]byte, error) {
|
||||||
req, err := UnmarshalMessage(reqBytes)
|
req, err := UnmarshalMessage(reqBytes[2:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.config.NoAAAA && req.Question.QTYPE == QTypeAAAA {
|
|
||||||
respBytes := valCopy(reqBytes)
|
|
||||||
respBytes[2] |= uint8(ResponseMsg) << 7
|
|
||||||
return respBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Question.QTYPE == QTypeA || req.Question.QTYPE == QTypeAAAA {
|
if req.Question.QTYPE == QTypeA || req.Question.QTYPE == QTypeAAAA {
|
||||||
if v, expired := c.cache.Get(qKey(req.Question)); len(v) > 2 {
|
v := c.cache.Get(getKey(req.Question))
|
||||||
v = valCopy(v)
|
if v != nil {
|
||||||
binary.BigEndian.PutUint16(v[:2], req.ID)
|
binary.BigEndian.PutUint16(v[2:4], req.ID)
|
||||||
|
|
||||||
if c.config.CacheLog {
|
|
||||||
log.F("[dns] %s <-> cache, type: %d, %s",
|
log.F("[dns] %s <-> cache, type: %d, %s",
|
||||||
clientAddr, req.Question.QTYPE, req.Question.QNAME)
|
clientAddr, req.Question.QTYPE, req.Question.QNAME)
|
||||||
}
|
|
||||||
|
|
||||||
if expired { // update cache
|
|
||||||
go func(qname string, reqBytes []byte, preferTCP bool) {
|
|
||||||
defer pool.PutBuffer(reqBytes)
|
|
||||||
if dnsServer, network, dialerAddr, respBytes, err := c.exchange(qname, reqBytes, preferTCP); err == nil {
|
|
||||||
c.handleAnswer(respBytes, "cache", dnsServer, network, dialerAddr)
|
|
||||||
}
|
|
||||||
}(req.Question.QNAME, valCopy(reqBytes), preferTCP)
|
|
||||||
}
|
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,44 +84,20 @@ func (c *Client) Exchange(reqBytes []byte, clientAddr string, preferTCP bool) ([
|
|||||||
return respBytes, nil
|
return respBytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.handleAnswer(respBytes, clientAddr, dnsServer, network, dialerAddr)
|
resp, err := UnmarshalMessage(respBytes[2:])
|
||||||
return respBytes, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) handleAnswer(respBytes []byte, clientAddr, dnsServer, network, dialerAddr string) error {
|
|
||||||
resp, err := UnmarshalMessage(respBytes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return respBytes, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ips, ttl := c.extractAnswer(resp)
|
|
||||||
if ttl > c.config.MaxTTL {
|
|
||||||
ttl = c.config.MaxTTL
|
|
||||||
} else if ttl < c.config.MinTTL {
|
|
||||||
ttl = c.config.MinTTL
|
|
||||||
}
|
|
||||||
|
|
||||||
if ttl <= 0 { // we got a null result
|
|
||||||
ttl = 1800
|
|
||||||
}
|
|
||||||
|
|
||||||
c.cache.Set(qKey(resp.Question), valCopy(respBytes), ttl)
|
|
||||||
log.F("[dns] %s <-> %s(%s) via %s, %s/%d: %s, ttl: %ds",
|
|
||||||
clientAddr, dnsServer, network, dialerAddr, resp.Question.QNAME, resp.Question.QTYPE, strings.Join(ips, ","), ttl)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) extractAnswer(resp *Message) ([]string, int) {
|
|
||||||
var ips []string
|
|
||||||
ttl := c.config.MinTTL
|
ttl := c.config.MinTTL
|
||||||
|
ips := []string{}
|
||||||
for _, answer := range resp.Answers {
|
for _, answer := range resp.Answers {
|
||||||
if answer.TYPE == QTypeA || answer.TYPE == QTypeAAAA {
|
if answer.TYPE == QTypeA || answer.TYPE == QTypeAAAA {
|
||||||
if answer.IP.IsValid() && !answer.IP.IsUnspecified() {
|
|
||||||
for _, h := range c.handlers {
|
for _, h := range c.handlers {
|
||||||
h(resp.Question.QNAME, answer.IP)
|
h(resp.Question.QNAME, answer.IP)
|
||||||
}
|
}
|
||||||
ips = append(ips, answer.IP.String())
|
if answer.IP != "" {
|
||||||
|
ips = append(ips, answer.IP)
|
||||||
}
|
}
|
||||||
if answer.TTL != 0 {
|
if answer.TTL != 0 {
|
||||||
ttl = int(answer.TTL)
|
ttl = int(answer.TTL)
|
||||||
@ -153,7 +105,21 @@ func (c *Client) extractAnswer(resp *Message) ([]string, int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ips, ttl
|
if ttl > c.config.MaxTTL {
|
||||||
|
ttl = c.config.MaxTTL
|
||||||
|
} else if ttl < c.config.MinTTL {
|
||||||
|
ttl = c.config.MinTTL
|
||||||
|
}
|
||||||
|
|
||||||
|
// add to cache only when there's a valid ip address
|
||||||
|
if len(ips) != 0 && ttl > 0 {
|
||||||
|
c.cache.Put(getKey(resp.Question), respBytes, ttl)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.F("[dns] %s <-> %s(%s) via %s, type: %d, %s: %s",
|
||||||
|
clientAddr, dnsServer, network, dialerAddr, resp.Question.QTYPE, resp.Question.QNAME, strings.Join(ips, ","))
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// exchange choose a upstream dns server based on qname, communicate with it on the network.
|
// exchange choose a upstream dns server based on qname, communicate with it on the network.
|
||||||
@ -162,13 +128,12 @@ func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) (
|
|||||||
|
|
||||||
// use tcp to connect upstream server default
|
// use tcp to connect upstream server default
|
||||||
network = "tcp"
|
network = "tcp"
|
||||||
dialer := c.proxy.NextDialer(qname + ":0")
|
dialer := c.proxy.NextDialer(qname + ":53")
|
||||||
|
|
||||||
// if we are resolving a domain which uses a forwarder `REJECT`, then use `DIRECT` instead
|
// if we are resolving the dialer's domain, then use Direct to avoid denpency loop
|
||||||
// so we can resolve it correctly.
|
|
||||||
// TODO: dialer.Addr() == "REJECT", tricky
|
// TODO: dialer.Addr() == "REJECT", tricky
|
||||||
if dialer.Addr() == "REJECT" {
|
if strings.Contains(dialer.Addr(), qname) || dialer.Addr() == "REJECT" {
|
||||||
dialer = c.proxy.NextDialer("direct:0")
|
dialer = proxy.Default
|
||||||
}
|
}
|
||||||
|
|
||||||
// If client uses udp and no forwarders specified, use udp
|
// If client uses udp and no forwarders specified, use udp
|
||||||
@ -177,24 +142,18 @@ func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) (
|
|||||||
network = "udp"
|
network = "udp"
|
||||||
}
|
}
|
||||||
|
|
||||||
ups := c.UpStream(qname)
|
servers := c.GetServers(qname)
|
||||||
server = ups.Server()
|
for _, server = range servers {
|
||||||
for range ups.Len() {
|
|
||||||
var rc net.Conn
|
var rc net.Conn
|
||||||
rc, err = dialer.Dial(network, server)
|
rc, err = dialer.Dial(network, server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
newServer := ups.SwitchIf(server)
|
log.F("[dns] failed to connect to server %v: %v", server, err)
|
||||||
log.F("[dns] error in resolving %s, failed to connect to server %v via %s: %v, next server: %s",
|
|
||||||
qname, server, dialer.Addr(), err, newServer)
|
|
||||||
server = newServer
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
defer rc.Close()
|
defer rc.Close()
|
||||||
|
|
||||||
// TODO: support timeout setting for different upstream server
|
// TODO: support timeout setting for different upstream server
|
||||||
if c.config.Timeout > 0 {
|
|
||||||
rc.SetDeadline(time.Now().Add(time.Duration(c.config.Timeout) * time.Second))
|
rc.SetDeadline(time.Now().Add(time.Duration(c.config.Timeout) * time.Second))
|
||||||
}
|
|
||||||
|
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
@ -207,16 +166,7 @@ func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) (
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
newServer := ups.SwitchIf(server)
|
log.F("[dns] failed to exchange with server %v: %v", server, err)
|
||||||
log.F("[dns] error in resolving %s, failed to exchange with server %v via %s: %v, next server: %s",
|
|
||||||
qname, server, dialer.Addr(), err, newServer)
|
|
||||||
|
|
||||||
server = newServer
|
|
||||||
}
|
|
||||||
|
|
||||||
// if all dns upstreams failed, then maybe the forwarder is not available.
|
|
||||||
if err != nil {
|
|
||||||
c.proxy.Record(dialer, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return server, network, dialer.Addr(), respBytes, err
|
return server, network, dialer.Addr(), respBytes, err
|
||||||
@ -224,22 +174,23 @@ func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) (
|
|||||||
|
|
||||||
// exchangeTCP exchange with server over tcp.
|
// exchangeTCP exchange with server over tcp.
|
||||||
func (c *Client) exchangeTCP(rc net.Conn, reqBytes []byte) ([]byte, error) {
|
func (c *Client) exchangeTCP(rc net.Conn, reqBytes []byte) ([]byte, error) {
|
||||||
lenBuf := pool.GetBuffer(2)
|
if _, err := rc.Write(reqBytes); err != nil {
|
||||||
defer pool.PutBuffer(lenBuf)
|
log.F("[dns] failed to write req message: %v", err)
|
||||||
|
|
||||||
binary.BigEndian.PutUint16(lenBuf, uint16(len(reqBytes)))
|
|
||||||
if _, err := (&net.Buffers{lenBuf, reqBytes}).WriteTo(rc); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var respLen uint16
|
var respLen uint16
|
||||||
if err := binary.Read(rc, binary.BigEndian, &respLen); err != nil {
|
if err := binary.Read(rc, binary.BigEndian, &respLen); err != nil {
|
||||||
|
log.F("[dns] failed to read response length: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
respBytes := pool.GetBuffer(int(respLen))
|
respBytes := make([]byte, respLen+2)
|
||||||
_, err := io.ReadFull(rc, respBytes)
|
binary.BigEndian.PutUint16(respBytes[:2], respLen)
|
||||||
|
|
||||||
|
_, err := io.ReadFull(rc, respBytes[2:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.F("[dns] error in read respMsg %s\n", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,97 +199,101 @@ func (c *Client) exchangeTCP(rc net.Conn, reqBytes []byte) ([]byte, error) {
|
|||||||
|
|
||||||
// exchangeUDP exchange with server over udp.
|
// exchangeUDP exchange with server over udp.
|
||||||
func (c *Client) exchangeUDP(rc net.Conn, reqBytes []byte) ([]byte, error) {
|
func (c *Client) exchangeUDP(rc net.Conn, reqBytes []byte) ([]byte, error) {
|
||||||
if _, err := rc.Write(reqBytes); err != nil {
|
if _, err := rc.Write(reqBytes[2:]); err != nil {
|
||||||
|
log.F("[dns] failed to write req message: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
respBytes := pool.GetBuffer(UDPMaxLen)
|
reqBytes = make([]byte, 2+UDPMaxLen)
|
||||||
n, err := rc.Read(respBytes)
|
n, err := rc.Read(reqBytes[2:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
binary.BigEndian.PutUint16(reqBytes[:2], uint16(n))
|
||||||
|
|
||||||
return respBytes[:n], nil
|
return reqBytes[:2+n], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetServers sets upstream dns servers for the given domain.
|
// SetServers sets upstream dns servers for the given domain.
|
||||||
func (c *Client) SetServers(domain string, servers []string) {
|
func (c *Client) SetServers(domain string, servers ...string) {
|
||||||
c.upStreamMap[strings.ToLower(domain)] = NewUPStream(servers)
|
c.upServerMap[domain] = append(c.upServerMap[domain], servers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpStream returns upstream dns server for the given domain.
|
// GetServers gets upstream dns servers for the given domain
|
||||||
func (c *Client) UpStream(domain string) *UPStream {
|
func (c *Client) GetServers(domain string) []string {
|
||||||
domain = strings.ToLower(domain)
|
domainParts := strings.Split(domain, ".")
|
||||||
for i := len(domain); i != -1; {
|
length := len(domainParts)
|
||||||
i = strings.LastIndexByte(domain[:i], '.')
|
for i := length - 1; i >= 0; i-- {
|
||||||
if upstream, ok := c.upStreamMap[domain[i+1:]]; ok {
|
domain := strings.Join(domainParts[i:length], ".")
|
||||||
return upstream
|
|
||||||
|
if servers, ok := c.upServerMap[domain]; ok {
|
||||||
|
return servers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c.upStream
|
|
||||||
|
return c.upServers
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddHandler adds a custom handler to handle the resolved result (A and AAAA).
|
// AddHandler adds a custom handler to handle the resolved result (A and AAAA).
|
||||||
func (c *Client) AddHandler(h AnswerHandler) {
|
func (c *Client) AddHandler(h HandleFunc) {
|
||||||
c.handlers = append(c.handlers, h)
|
c.handlers = append(c.handlers, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRecord adds custom record to dns cache, format:
|
// AddRecord adds custom record to dns cache, format:
|
||||||
// www.example.com/1.2.3.4 or www.example.com/2606:2800:220:1:248:1893:25c8:1946
|
// www.example.com/1.2.3.4 or www.example.com/2606:2800:220:1:248:1893:25c8:1946
|
||||||
func (c *Client) AddRecord(record string) error {
|
func (c *Client) AddRecord(record string) error {
|
||||||
domain, ip, found := strings.Cut(record, "/")
|
r := strings.Split(record, "/")
|
||||||
if !found {
|
domain, ip := r[0], r[1]
|
||||||
return errors.New("wrong record format, must contain '/'")
|
m, err := c.GenResponse(domain, ip)
|
||||||
}
|
|
||||||
m, err := MakeResponse(domain, ip, uint32(c.config.MaxTTL))
|
|
||||||
if err != nil {
|
|
||||||
log.F("[dns] add custom record error: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
wb := pool.GetBytesBuffer()
|
|
||||||
defer pool.PutBytesBuffer(wb)
|
|
||||||
|
|
||||||
_, err = m.MarshalTo(wb)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.cache.Set(qKey(m.Question), valCopy(wb.Bytes()), 0)
|
b, _ := m.Marshal()
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
binary.Write(&buf, binary.BigEndian, uint16(len(b)))
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
c.cache.Put(getKey(m.Question), buf.Bytes(), LongTTL)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeResponse makes a dns response message for the given domain and ip address.
|
// GenResponse generates a dns response message for the given domain and ip address.
|
||||||
// Note: you should make sure ttl > 0.
|
func (c *Client) GenResponse(domain string, ip string) (*Message, error) {
|
||||||
func MakeResponse(domain, ip string, ttl uint32) (*Message, error) {
|
ipb := net.ParseIP(ip)
|
||||||
addr, err := netip.ParseAddr(ip)
|
if ipb == nil {
|
||||||
if err != nil {
|
return nil, errors.New("GenResponse: invalid ip format")
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var qtype, rdlen uint16 = QTypeA, net.IPv4len
|
var rdata []byte
|
||||||
if addr.Is6() {
|
var qtype, rdlen uint16
|
||||||
qtype, rdlen = QTypeAAAA, net.IPv6len
|
if rdata = ipb.To4(); rdata != nil {
|
||||||
|
qtype = QTypeA
|
||||||
|
rdlen = net.IPv4len
|
||||||
|
} else {
|
||||||
|
qtype = QTypeAAAA
|
||||||
|
rdlen = net.IPv6len
|
||||||
|
rdata = ipb
|
||||||
}
|
}
|
||||||
|
|
||||||
m := NewMessage(0, ResponseMsg)
|
m := NewMessage(0, Response)
|
||||||
m.SetQuestion(NewQuestion(qtype, domain))
|
m.SetQuestion(NewQuestion(qtype, domain))
|
||||||
rr := &RR{NAME: domain, TYPE: qtype, CLASS: ClassINET,
|
rr := &RR{NAME: domain, TYPE: qtype, CLASS: ClassINET,
|
||||||
TTL: ttl, RDLENGTH: rdlen, RDATA: addr.AsSlice()}
|
TTL: uint32(c.config.MinTTL), RDLENGTH: rdlen, RDATA: rdata}
|
||||||
m.AddAnswer(rr)
|
m.AddAnswer(rr)
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func qKey(q *Question) string {
|
func getKey(q *Question) string {
|
||||||
return q.QNAME + "/" + strconv.FormatUint(uint64(q.QTYPE), 10)
|
qtype := ""
|
||||||
}
|
switch q.QTYPE {
|
||||||
|
case QTypeA:
|
||||||
func valCopy(v []byte) (b []byte) {
|
qtype = "A"
|
||||||
if v != nil {
|
case QTypeAAAA:
|
||||||
b = pool.GetBuffer(len(v))
|
qtype = "AAAA"
|
||||||
copy(b, v)
|
|
||||||
}
|
}
|
||||||
return
|
return q.QNAME + "/" + qtype
|
||||||
}
|
}
|
||||||
|
266
dns/message.go
266
dns/message.go
@ -4,29 +4,28 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"math/rand"
|
||||||
"math/rand/v2"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UDPMaxLen is the max size of udp dns request.
|
// UDPMaxLen is the max size of udp dns request.
|
||||||
// https://www.dnsflagday.net/2020/
|
// https://tools.ietf.org/html/rfc1035#section-4.2.1
|
||||||
const UDPMaxLen = 1232
|
// Messages carried by UDP are restricted to 512 bytes (not counting the IP
|
||||||
|
// or UDP headers). Longer messages are truncated and the TC bit is set in
|
||||||
|
// the header.
|
||||||
|
const UDPMaxLen = 512
|
||||||
|
|
||||||
// HeaderLen is the length of dns msg header.
|
// HeaderLen is the length of dns msg header.
|
||||||
const HeaderLen = 12
|
const HeaderLen = 12
|
||||||
|
|
||||||
// MsgType is the dns Message type.
|
// Message types
|
||||||
type MsgType byte
|
|
||||||
|
|
||||||
// Message types.
|
|
||||||
const (
|
const (
|
||||||
QueryMsg MsgType = 0
|
Query = 0
|
||||||
ResponseMsg MsgType = 1
|
Response = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
// Query types.
|
// Query types
|
||||||
const (
|
const (
|
||||||
QTypeA uint16 = 1 //ipv4
|
QTypeA uint16 = 1 //ipv4
|
||||||
QTypeAAAA uint16 = 28 ///ipv6
|
QTypeAAAA uint16 = 28 ///ipv6
|
||||||
@ -35,8 +34,8 @@ const (
|
|||||||
// ClassINET .
|
// ClassINET .
|
||||||
const ClassINET uint16 = 1
|
const ClassINET uint16 = 1
|
||||||
|
|
||||||
// Message format:
|
// Message format
|
||||||
// https://www.rfc-editor.org/rfc/rfc1035#section-4.1
|
// https://tools.ietf.org/html/rfc1035#section-4.1
|
||||||
// All communications inside of the domain protocol are carried in a single
|
// All communications inside of the domain protocol are carried in a single
|
||||||
// format called a message. The top level format of message is divided
|
// format called a message. The top level format of message is divided
|
||||||
// into 5 sections (some of which are empty in certain cases) shown below:
|
// into 5 sections (some of which are empty in certain cases) shown below:
|
||||||
@ -64,7 +63,7 @@ type Message struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewMessage returns a new message.
|
// NewMessage returns a new message.
|
||||||
func NewMessage(id uint16, msgType MsgType) *Message {
|
func NewMessage(id uint16, msgType int) *Message {
|
||||||
if id == 0 {
|
if id == 0 {
|
||||||
id = uint16(rand.Uint32())
|
id = uint16(rand.Uint32())
|
||||||
}
|
}
|
||||||
@ -90,40 +89,32 @@ func (m *Message) AddAnswer(rr *RR) error {
|
|||||||
|
|
||||||
// Marshal marshals message struct to []byte.
|
// Marshal marshals message struct to []byte.
|
||||||
func (m *Message) Marshal() ([]byte, error) {
|
func (m *Message) Marshal() ([]byte, error) {
|
||||||
buf := &bytes.Buffer{}
|
var buf bytes.Buffer
|
||||||
if _, err := m.MarshalTo(buf); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalTo marshals message struct to []byte and write to w.
|
|
||||||
func (m *Message) MarshalTo(w io.Writer) (n int, err error) {
|
|
||||||
m.Header.SetQdcount(1)
|
m.Header.SetQdcount(1)
|
||||||
m.Header.SetAncount(len(m.Answers))
|
m.Header.SetAncount(len(m.Answers))
|
||||||
|
|
||||||
nn := 0
|
b, err := m.Header.Marshal()
|
||||||
nn, err = m.Header.MarshalTo(w)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
n += nn
|
buf.Write(b)
|
||||||
|
|
||||||
nn, err = m.Question.MarshalTo(w)
|
b, err = m.Question.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
n += nn
|
buf.Write(b)
|
||||||
|
|
||||||
for _, answer := range m.Answers {
|
for _, answer := range m.Answers {
|
||||||
nn, err = answer.MarshalTo(w)
|
b, err := answer.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
n += nn
|
buf.Write(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalMessage unmarshals []bytes to Message.
|
// UnmarshalMessage unmarshals []bytes to Message.
|
||||||
@ -133,7 +124,8 @@ func UnmarshalMessage(b []byte) (*Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m := &Message{unMarshaled: b}
|
m := &Message{unMarshaled: b}
|
||||||
if err := UnmarshalHeader(b[:HeaderLen], &m.Header); err != nil {
|
err := UnmarshalHeader(b[:HeaderLen], &m.Header)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +138,7 @@ func UnmarshalMessage(b []byte) (*Message, error) {
|
|||||||
|
|
||||||
// resp answers
|
// resp answers
|
||||||
rrIdx := HeaderLen + qLen
|
rrIdx := HeaderLen + qLen
|
||||||
for range int(m.Header.ANCOUNT) {
|
for i := 0; i < int(m.Header.ANCOUNT); i++ {
|
||||||
rr := &RR{}
|
rr := &RR{}
|
||||||
rrLen, err := m.UnmarshalRR(rrIdx, rr)
|
rrLen, err := m.UnmarshalRR(rrIdx, rr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -162,8 +154,8 @@ func UnmarshalMessage(b []byte) (*Message, error) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header format:
|
// Header format
|
||||||
// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.1
|
// https://tools.ietf.org/html/rfc1035#section-4.1.1
|
||||||
// The header contains the following fields:
|
// The header contains the following fields:
|
||||||
//
|
//
|
||||||
// 1 1 1 1 1 1
|
// 1 1 1 1 1 1
|
||||||
@ -181,6 +173,7 @@ func UnmarshalMessage(b []byte) (*Message, error) {
|
|||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
// | ARCOUNT |
|
// | ARCOUNT |
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
//
|
||||||
type Header struct {
|
type Header struct {
|
||||||
ID uint16
|
ID uint16
|
||||||
Bits uint16
|
Bits uint16
|
||||||
@ -191,7 +184,7 @@ type Header struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetMsgType sets the message type.
|
// SetMsgType sets the message type.
|
||||||
func (h *Header) SetMsgType(qr MsgType) {
|
func (h *Header) SetMsgType(qr int) {
|
||||||
h.Bits |= uint16(qr) << 15
|
h.Bits |= uint16(qr) << 15
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,15 +203,16 @@ func (h *Header) SetAncount(ancount int) {
|
|||||||
h.ANCOUNT = uint16(ancount)
|
h.ANCOUNT = uint16(ancount)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not used now, but keep it for future use.
|
func (h *Header) setFlag(QR uint16, Opcode uint16, AA uint16,
|
||||||
// func (h *Header) setFlag(QR uint16, Opcode uint16, AA uint16,
|
TC uint16, RD uint16, RA uint16, RCODE uint16) {
|
||||||
// TC uint16, RD uint16, RA uint16, RCODE uint16) {
|
h.Bits = QR<<15 + Opcode<<11 + AA<<10 + TC<<9 + RD<<8 + RA<<7 + RCODE
|
||||||
// h.Bits = QR<<15 + Opcode<<11 + AA<<10 + TC<<9 + RD<<8 + RA<<7 + RCODE
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// MarshalTo marshals header struct to []byte and write to w.
|
// Marshal marshals header struct to []byte.
|
||||||
func (h *Header) MarshalTo(w io.Writer) (int, error) {
|
func (h *Header) Marshal() ([]byte, error) {
|
||||||
return HeaderLen, binary.Write(w, binary.BigEndian, h)
|
var buf bytes.Buffer
|
||||||
|
err := binary.Write(&buf, binary.BigEndian, h)
|
||||||
|
return buf.Bytes(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalHeader unmarshals []bytes to Header.
|
// UnmarshalHeader unmarshals []bytes to Header.
|
||||||
@ -241,8 +235,8 @@ func UnmarshalHeader(b []byte, h *Header) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Question format:
|
// Question format
|
||||||
// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.2
|
// https://tools.ietf.org/html/rfc1035#section-4.1.2
|
||||||
// The question section is used to carry the "question" in most queries,
|
// The question section is used to carry the "question" in most queries,
|
||||||
// i.e., the parameters that define what is being asked. The section
|
// i.e., the parameters that define what is being asked. The section
|
||||||
// contains QDCOUNT (usually 1) entries, each of the following format:
|
// contains QDCOUNT (usually 1) entries, each of the following format:
|
||||||
@ -273,24 +267,15 @@ func NewQuestion(qtype uint16, domain string) *Question {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalTo marshals Question struct to []byte and write to w.
|
// Marshal marshals Question struct to []byte.
|
||||||
func (q *Question) MarshalTo(w io.Writer) (n int, err error) {
|
func (q *Question) Marshal() ([]byte, error) {
|
||||||
n, err = MarshalDomainTo(w, q.QNAME)
|
var buf bytes.Buffer
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = binary.Write(w, binary.BigEndian, q.QTYPE); err != nil {
|
buf.Write(MarshalDomain(q.QNAME))
|
||||||
return
|
binary.Write(&buf, binary.BigEndian, q.QTYPE)
|
||||||
}
|
binary.Write(&buf, binary.BigEndian, q.QCLASS)
|
||||||
n += 2
|
|
||||||
|
|
||||||
if err = binary.Write(w, binary.BigEndian, q.QCLASS); err != nil {
|
return buf.Bytes(), nil
|
||||||
return
|
|
||||||
}
|
|
||||||
n += 2
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalQuestion unmarshals []bytes to Question.
|
// UnmarshalQuestion unmarshals []bytes to Question.
|
||||||
@ -303,23 +288,21 @@ func (m *Message) UnmarshalQuestion(b []byte, q *Question) (n int, err error) {
|
|||||||
return 0, errors.New("UnmarshalQuestion: not enough data")
|
return 0, errors.New("UnmarshalQuestion: not enough data")
|
||||||
}
|
}
|
||||||
|
|
||||||
sb := new(strings.Builder)
|
domain, idx, err := m.UnmarshalDomain(b)
|
||||||
sb.Grow(32)
|
|
||||||
idx, err := m.UnmarshalDomainTo(sb, b)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
q.QNAME = sb.String()
|
q.QNAME = domain
|
||||||
q.QTYPE = binary.BigEndian.Uint16(b[idx : idx+2])
|
q.QTYPE = binary.BigEndian.Uint16(b[idx : idx+2])
|
||||||
q.QCLASS = binary.BigEndian.Uint16(b[idx+2 : idx+4])
|
q.QCLASS = binary.BigEndian.Uint16(b[idx+2 : idx+4])
|
||||||
|
|
||||||
return idx + 3 + 1, nil
|
return idx + 3 + 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RR format:
|
// RR format
|
||||||
// https://www.rfc-editor.org/rfc/rfc1035#section-3.2.1
|
// https://tools.ietf.org/html/rfc1035#section-3.2.1
|
||||||
// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.3
|
// https://tools.ietf.org/html/rfc1035#section-4.1.3
|
||||||
// The answer, authority, and additional sections all share the same
|
// The answer, authority, and additional sections all share the same
|
||||||
// format: a variable number of resource records, where the number of
|
// format: a variable number of resource records, where the number of
|
||||||
// records is specified in the corresponding count field in the header.
|
// records is specified in the corresponding count field in the header.
|
||||||
@ -353,48 +336,27 @@ type RR struct {
|
|||||||
RDLENGTH uint16
|
RDLENGTH uint16
|
||||||
RDATA []byte
|
RDATA []byte
|
||||||
|
|
||||||
IP netip.Addr
|
IP string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRR returns a new dns rr.
|
// NewRR returns a new dns rr.
|
||||||
func NewRR() *RR {
|
func NewRR() *RR {
|
||||||
return &RR{}
|
rr := &RR{}
|
||||||
|
return rr
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalTo marshals RR struct to []byte and write to w.
|
// Marshal marshals RR struct to []byte.
|
||||||
func (rr *RR) MarshalTo(w io.Writer) (n int, err error) {
|
func (rr *RR) Marshal() ([]byte, error) {
|
||||||
n, err = MarshalDomainTo(w, rr.NAME)
|
var buf bytes.Buffer
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = binary.Write(w, binary.BigEndian, rr.TYPE); err != nil {
|
buf.Write(MarshalDomain(rr.NAME))
|
||||||
return
|
binary.Write(&buf, binary.BigEndian, rr.TYPE)
|
||||||
}
|
binary.Write(&buf, binary.BigEndian, rr.CLASS)
|
||||||
n += 2
|
binary.Write(&buf, binary.BigEndian, rr.TTL)
|
||||||
|
binary.Write(&buf, binary.BigEndian, rr.RDLENGTH)
|
||||||
|
buf.Write(rr.RDATA)
|
||||||
|
|
||||||
if err = binary.Write(w, binary.BigEndian, rr.CLASS); err != nil {
|
return buf.Bytes(), nil
|
||||||
return
|
|
||||||
}
|
|
||||||
n += 2
|
|
||||||
|
|
||||||
if err = binary.Write(w, binary.BigEndian, rr.TTL); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n += 4
|
|
||||||
|
|
||||||
err = binary.Write(w, binary.BigEndian, rr.RDLENGTH)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n += 2
|
|
||||||
|
|
||||||
if _, err = w.Write(rr.RDATA); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n += len(rr.RDATA)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalRR unmarshals []bytes to RR.
|
// UnmarshalRR unmarshals []bytes to RR.
|
||||||
@ -405,14 +367,11 @@ func (m *Message) UnmarshalRR(start int, rr *RR) (n int, err error) {
|
|||||||
|
|
||||||
p := m.unMarshaled[start:]
|
p := m.unMarshaled[start:]
|
||||||
|
|
||||||
sb := new(strings.Builder)
|
domain, n, err := m.UnmarshalDomain(p)
|
||||||
sb.Grow(32)
|
|
||||||
|
|
||||||
n, err = m.UnmarshalDomainTo(sb, p)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
rr.NAME = sb.String()
|
rr.NAME = domain
|
||||||
|
|
||||||
if len(p) <= n+10 {
|
if len(p) <= n+10 {
|
||||||
return 0, errors.New("UnmarshalRR: not enough data")
|
return 0, errors.New("UnmarshalRR: not enough data")
|
||||||
@ -430,9 +389,9 @@ func (m *Message) UnmarshalRR(start int, rr *RR) (n int, err error) {
|
|||||||
rr.RDATA = p[n+10 : n+10+int(rr.RDLENGTH)]
|
rr.RDATA = p[n+10 : n+10+int(rr.RDLENGTH)]
|
||||||
|
|
||||||
if rr.TYPE == QTypeA {
|
if rr.TYPE == QTypeA {
|
||||||
rr.IP = netip.AddrFrom4(*(*[4]byte)(rr.RDATA[:4]))
|
rr.IP = net.IP(rr.RDATA[:net.IPv4len]).String()
|
||||||
} else if rr.TYPE == QTypeAAAA {
|
} else if rr.TYPE == QTypeAAAA {
|
||||||
rr.IP = netip.AddrFrom16(*(*[16]byte)(rr.RDATA[:16]))
|
rr.IP = net.IP(rr.RDATA[:net.IPv6len]).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
n = n + 10 + int(rr.RDLENGTH)
|
n = n + 10 + int(rr.RDLENGTH)
|
||||||
@ -440,88 +399,69 @@ func (m *Message) UnmarshalRR(start int, rr *RR) (n int, err error) {
|
|||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalDomainTo marshals domain string struct to []byte and write to w.
|
// MarshalDomain marshals domain string struct to []byte.
|
||||||
func MarshalDomainTo(w io.Writer, domain string) (n int, err error) {
|
func MarshalDomain(domain string) []byte {
|
||||||
nn := 0
|
var buf bytes.Buffer
|
||||||
|
|
||||||
for _, seg := range strings.Split(domain, ".") {
|
for _, seg := range strings.Split(domain, ".") {
|
||||||
nn, err = w.Write([]byte{byte(len(seg))})
|
binary.Write(&buf, binary.BigEndian, byte(len(seg)))
|
||||||
if err != nil {
|
binary.Write(&buf, binary.BigEndian, []byte(seg))
|
||||||
return
|
|
||||||
}
|
}
|
||||||
n += nn
|
binary.Write(&buf, binary.BigEndian, byte(0x00))
|
||||||
|
|
||||||
nn, err = io.WriteString(w, seg)
|
return buf.Bytes()
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n += nn
|
|
||||||
}
|
|
||||||
|
|
||||||
nn, err = w.Write([]byte{0x00})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n += nn
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalDomainTo gets domain from bytes to string builder.
|
// UnmarshalDomain gets domain from bytes.
|
||||||
func (m *Message) UnmarshalDomainTo(sb *strings.Builder, b []byte) (int, error) {
|
func (m *Message) UnmarshalDomain(b []byte) (string, int, error) {
|
||||||
var idx, size int
|
var idx, size int
|
||||||
|
var labels = []string{}
|
||||||
|
|
||||||
for len(b[idx:]) != 0 {
|
for {
|
||||||
// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.4
|
// https://tools.ietf.org/html/rfc1035#section-4.1.4
|
||||||
// "Message compression",
|
// "Message compression",
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
// | 1 1| OFFSET |
|
// | 1 1| OFFSET |
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
if b[idx]&0xC0 == 0xC0 {
|
if b[idx]&0xC0 == 0xC0 {
|
||||||
if len(b[idx:]) < 2 {
|
|
||||||
return 0, errors.New("UnmarshalDomainTo: not enough size for compressed domain")
|
|
||||||
}
|
|
||||||
|
|
||||||
offset := binary.BigEndian.Uint16(b[idx : idx+2])
|
offset := binary.BigEndian.Uint16(b[idx : idx+2])
|
||||||
if err := m.UnmarshalDomainPointTo(sb, int(offset&0x3FFF)); err != nil {
|
label, err := m.UnmarshalDomainPoint(int(offset & 0x3FFF))
|
||||||
return 0, err
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
labels = append(labels, label)
|
||||||
idx += 2
|
idx += 2
|
||||||
break
|
break
|
||||||
}
|
} else {
|
||||||
|
|
||||||
size = int(b[idx])
|
size = int(b[idx])
|
||||||
idx++
|
|
||||||
|
|
||||||
// root domain name
|
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
|
idx++
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if size > 63 {
|
if size > 63 {
|
||||||
return 0, errors.New("UnmarshalDomainTo: label size larger than 63")
|
return "", 0, errors.New("UnmarshalDomain: label size larger than 63")
|
||||||
}
|
}
|
||||||
|
|
||||||
if idx+size > len(b) {
|
if idx+size+1 > len(b) {
|
||||||
return 0, errors.New("UnmarshalDomainTo: label size larger than msg length")
|
return "", 0, errors.New("UnmarshalDomain: label size larger than msg length")
|
||||||
}
|
}
|
||||||
|
|
||||||
if sb.Len() > 0 {
|
labels = append(labels, string(b[idx+1:idx+size+1]))
|
||||||
sb.WriteByte('.')
|
idx += (size + 1)
|
||||||
}
|
}
|
||||||
sb.Write(b[idx : idx+size])
|
|
||||||
|
|
||||||
idx += size
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return idx, nil
|
domain := strings.Join(labels, ".")
|
||||||
|
return domain, idx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalDomainPointTo gets domain from offset point to string builder.
|
// UnmarshalDomainPoint gets domain from offset point.
|
||||||
func (m *Message) UnmarshalDomainPointTo(sb *strings.Builder, offset int) error {
|
func (m *Message) UnmarshalDomainPoint(offset int) (string, error) {
|
||||||
if offset > len(m.unMarshaled) {
|
if offset > len(m.unMarshaled) {
|
||||||
return errors.New("UnmarshalDomainPointTo: offset larger than msg length")
|
return "", errors.New("UnmarshalDomainPoint: offset larger than msg length")
|
||||||
}
|
}
|
||||||
_, err := m.UnmarshalDomainTo(sb, m.unMarshaled[offset:])
|
domain, _, err := m.UnmarshalDomain(m.unMarshaled[offset:])
|
||||||
return err
|
return domain, err
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,11 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
"github.com/nadoo/glider/common/log"
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// conn timeout, in seconds.
|
// conn timeout, seconds
|
||||||
const timeout = 30
|
const timeout = 30
|
||||||
|
|
||||||
// Server is a dns server struct.
|
// Server is a dns server struct.
|
||||||
@ -25,15 +24,12 @@ type Server struct {
|
|||||||
// NewServer returns a new dns server.
|
// NewServer returns a new dns server.
|
||||||
func NewServer(addr string, p proxy.Proxy, config *Config) (*Server, error) {
|
func NewServer(addr string, p proxy.Proxy, config *Config) (*Server, error) {
|
||||||
c, err := NewClient(p, config)
|
c, err := NewClient(p, config)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s := &Server{
|
s := &Server{
|
||||||
addr: addr,
|
addr: addr,
|
||||||
Client: c,
|
Client: c,
|
||||||
}
|
}
|
||||||
return s, nil
|
|
||||||
|
return s, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts the dns forwarding server.
|
// Start starts the dns forwarding server.
|
||||||
@ -49,46 +45,47 @@ func (s *Server) Start() {
|
|||||||
|
|
||||||
// ListenAndServeUDP listen and serves on udp port.
|
// ListenAndServeUDP listen and serves on udp port.
|
||||||
func (s *Server) ListenAndServeUDP(wg *sync.WaitGroup) {
|
func (s *Server) ListenAndServeUDP(wg *sync.WaitGroup) {
|
||||||
pc, err := net.ListenPacket("udp", s.addr)
|
c, err := net.ListenPacket("udp", s.addr)
|
||||||
wg.Done()
|
wg.Done()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[dns] failed to listen on %s, error: %v", s.addr, err)
|
log.F("[dns] failed to listen on %s, error: %v", s.addr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer pc.Close()
|
defer c.Close()
|
||||||
|
|
||||||
log.F("[dns] listening UDP on %s", s.addr)
|
log.F("[dns] listening UDP on %s", s.addr)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
reqBytes := pool.GetBuffer(UDPMaxLen)
|
reqBytes := make([]byte, 2+UDPMaxLen)
|
||||||
n, caddr, err := pc.ReadFrom(reqBytes)
|
n, caddr, err := c.ReadFrom(reqBytes[2:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[dns] local read error: %v", err)
|
log.F("[dns] local read error: %v", err)
|
||||||
pool.PutBuffer(reqBytes)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go s.ServePacket(pc, caddr, reqBytes[:n])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServePacket serves dns packet conn.
|
reqLen := uint16(n)
|
||||||
func (s *Server) ServePacket(pc net.PacketConn, caddr net.Addr, reqBytes []byte) {
|
if reqLen <= HeaderLen+2 {
|
||||||
respBytes, err := s.Exchange(reqBytes, caddr.String(), false)
|
log.F("[dns] not enough message data")
|
||||||
defer func() {
|
continue
|
||||||
pool.PutBuffer(reqBytes)
|
}
|
||||||
pool.PutBuffer(respBytes)
|
binary.BigEndian.PutUint16(reqBytes[:2], reqLen)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
respBytes, err := s.Client.Exchange(reqBytes[:2+n], caddr.String(), false)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[dns] error in exchange: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = c.WriteTo(respBytes[2:], caddr)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[dns] error in local write: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
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 to %s: %s", caddr, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenAndServeTCP listen and serves on tcp port.
|
// ListenAndServeTCP listen and serves on tcp port.
|
||||||
@ -113,7 +110,7 @@ func (s *Server) ListenAndServeTCP(wg *sync.WaitGroup) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeTCP serves a dns tcp connection.
|
// ServeTCP serves a tcp connection.
|
||||||
func (s *Server) ServeTCP(c net.Conn) {
|
func (s *Server) ServeTCP(c net.Conn) {
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
@ -125,27 +122,23 @@ func (s *Server) ServeTCP(c net.Conn) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
reqBytes := pool.GetBuffer(int(reqLen))
|
reqBytes := make([]byte, reqLen+2)
|
||||||
defer pool.PutBuffer(reqBytes)
|
_, err := io.ReadFull(c, reqBytes[2:])
|
||||||
|
|
||||||
_, err := io.ReadFull(c, reqBytes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[dns-tcp] error in read reqBytes %s", err)
|
log.F("[dns-tcp] error in read reqBytes %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint16(reqBytes[:2], reqLen)
|
||||||
|
|
||||||
respBytes, err := s.Exchange(reqBytes, c.RemoteAddr().String(), true)
|
respBytes, err := s.Exchange(reqBytes, c.RemoteAddr().String(), true)
|
||||||
defer pool.PutBuffer(respBytes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[dns-tcp] error in exchange: %s", err)
|
log.F("[dns-tcp] error in exchange: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lenBuf := pool.GetBuffer(2)
|
if err := binary.Write(c, binary.BigEndian, respBytes); err != nil {
|
||||||
defer pool.PutBuffer(lenBuf)
|
log.F("[dns-tcp] error in local write respBytes: %s", err)
|
||||||
|
return
|
||||||
binary.BigEndian.PutUint16(lenBuf, uint16(len(respBytes)))
|
|
||||||
if _, err := (&net.Buffers{lenBuf, respBytes}).WriteTo(c); err != nil {
|
|
||||||
log.F("[dns-tcp] error in write respBytes: %s", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UPStream is a dns upstream.
|
|
||||||
type UPStream struct {
|
|
||||||
index uint32
|
|
||||||
servers []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUPStream returns a new UpStream.
|
|
||||||
func NewUPStream(servers []string) *UPStream {
|
|
||||||
// default port for dns upstream servers
|
|
||||||
for i, server := range servers {
|
|
||||||
if _, port, _ := net.SplitHostPort(server); port == "" {
|
|
||||||
servers[i] = net.JoinHostPort(server, "53")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &UPStream{servers: servers}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server returns a dns server.
|
|
||||||
func (u *UPStream) Server() string {
|
|
||||||
return u.servers[atomic.LoadUint32(&u.index)%uint32(len(u.servers))]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch switches to the next dns server.
|
|
||||||
func (u *UPStream) Switch() string {
|
|
||||||
return u.servers[atomic.AddUint32(&u.index, 1)%uint32(len(u.servers))]
|
|
||||||
}
|
|
||||||
|
|
||||||
// SwitchIf switches to the next dns server if needed.
|
|
||||||
func (u *UPStream) SwitchIf(server string) string {
|
|
||||||
if u.Server() == server {
|
|
||||||
return u.Switch()
|
|
||||||
}
|
|
||||||
return u.Server()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the number of dns servers.
|
|
||||||
func (u *UPStream) Len() int {
|
|
||||||
return len(u.servers)
|
|
||||||
}
|
|
27
feature.go
27
feature.go
@ -1,27 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
// comment out the services you don't need to make the compiled binary smaller.
|
|
||||||
// _ "github.com/nadoo/glider/service/xxx"
|
|
||||||
|
|
||||||
// comment out the protocols you don't need to make the compiled binary smaller.
|
|
||||||
_ "github.com/nadoo/glider/proxy/http"
|
|
||||||
_ "github.com/nadoo/glider/proxy/kcp"
|
|
||||||
_ "github.com/nadoo/glider/proxy/mixed"
|
|
||||||
_ "github.com/nadoo/glider/proxy/obfs"
|
|
||||||
_ "github.com/nadoo/glider/proxy/pxyproto"
|
|
||||||
_ "github.com/nadoo/glider/proxy/reject"
|
|
||||||
_ "github.com/nadoo/glider/proxy/smux"
|
|
||||||
_ "github.com/nadoo/glider/proxy/socks4"
|
|
||||||
_ "github.com/nadoo/glider/proxy/socks5"
|
|
||||||
_ "github.com/nadoo/glider/proxy/ss"
|
|
||||||
_ "github.com/nadoo/glider/proxy/ssh"
|
|
||||||
_ "github.com/nadoo/glider/proxy/ssr"
|
|
||||||
_ "github.com/nadoo/glider/proxy/tcp"
|
|
||||||
_ "github.com/nadoo/glider/proxy/tls"
|
|
||||||
_ "github.com/nadoo/glider/proxy/trojan"
|
|
||||||
_ "github.com/nadoo/glider/proxy/udp"
|
|
||||||
_ "github.com/nadoo/glider/proxy/vless"
|
|
||||||
_ "github.com/nadoo/glider/proxy/vmess"
|
|
||||||
_ "github.com/nadoo/glider/proxy/ws"
|
|
||||||
)
|
|
@ -1,12 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
// comment out the services you don't need to make the compiled binary smaller.
|
|
||||||
_ "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"
|
|
||||||
)
|
|
44
go.mod
44
go.mod
@ -1,29 +1,27 @@
|
|||||||
module github.com/nadoo/glider
|
module github.com/nadoo/glider
|
||||||
|
|
||||||
go 1.24
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
|
github.com/klauspost/cpuid v1.2.1 // indirect
|
||||||
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d
|
github.com/klauspost/reedsolomon v1.9.3 // indirect
|
||||||
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb
|
github.com/nadoo/conflag v0.2.2
|
||||||
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152
|
github.com/nadoo/go-shadowsocks2 v0.1.2
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
|
github.com/nadoo/shadowsocksR v0.1.0
|
||||||
github.com/nadoo/conflag v0.3.1
|
github.com/pkg/errors v0.8.1 // indirect
|
||||||
github.com/nadoo/ipset v0.5.0
|
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.18
|
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b // indirect
|
||||||
golang.org/x/crypto v0.35.0
|
github.com/tjfoc/gmsm v1.0.1 // indirect
|
||||||
golang.org/x/sys v0.30.0
|
github.com/xtaci/kcp-go v5.4.11+incompatible
|
||||||
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
|
||||||
|
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20191020212454-3e7259c5e7c2 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
// Replace dependency modules with local developing copy
|
||||||
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 // indirect
|
// use `go list -m all` to confirm the final module used
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
// replace (
|
||||||
github.com/klauspost/reedsolomon v1.12.4 // indirect
|
// github.com/nadoo/conflag => ../conflag
|
||||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
// github.com/nadoo/go-shadowsocks2 => ../go-shadowsocks2
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
// )
|
||||||
github.com/templexxx/cpu v0.1.1 // indirect
|
|
||||||
github.com/templexxx/xorsimd v0.4.3 // indirect
|
|
||||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
|
||||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
|
||||||
golang.org/x/net v0.35.0 // indirect
|
|
||||||
)
|
|
||||||
|
148
go.sum
148
go.sum
@ -1,129 +1,49 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/dgryski/go-camellia v0.0.0-20140412174459-3be6b3054dd1 h1:/5UddQ9I3CXetvBVN2ipRc209YUB0AMR8bufErftAxI=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/dgryski/go-camellia v0.0.0-20140412174459-3be6b3054dd1/go.mod h1:QX5ZVULjAfZJux/W62Y91HvCh9hyW6enAwcrrv/sLj0=
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d h1:CPqTNIigGweVPT4CYb+OO2E6XyRKFOmvTHwWRLgCAlE=
|
|
||||||
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d/go.mod h1:QX5ZVULjAfZJux/W62Y91HvCh9hyW6enAwcrrv/sLj0=
|
|
||||||
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb h1:zXpN5126w/mhECTkqazBkrOJIMatbPP71aSIDR5UuW4=
|
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb h1:zXpN5126w/mhECTkqazBkrOJIMatbPP71aSIDR5UuW4=
|
||||||
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb/go.mod h1:F7WkpqJj9t98ePxB/WJGQTIDeOVPuSJ3qdn6JUjg170=
|
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb/go.mod h1:F7WkpqJj9t98ePxB/WJGQTIDeOVPuSJ3qdn6JUjg170=
|
||||||
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 h1:ED31mPIxDJnrLt9W9dH5xgd/6KjzEACKHBVGQ33czc0=
|
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 h1:ED31mPIxDJnrLt9W9dH5xgd/6KjzEACKHBVGQ33czc0=
|
||||||
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152/go.mod h1:I9fhc/EvSg88cDxmfQ47v35Ssz9rlFunL/KY0A1JAYI=
|
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152/go.mod h1:I9fhc/EvSg88cDxmfQ47v35Ssz9rlFunL/KY0A1JAYI=
|
||||||
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 h1:fBHFH+Y/GPGFGo7LIrErQc3p2MeAhoIQNgaxPWYsSxk=
|
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 h1:fBHFH+Y/GPGFGo7LIrErQc3p2MeAhoIQNgaxPWYsSxk=
|
||||||
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:ucvhdsUCE3TH0LoLRb6ShHiJl8e39dGlx6A4g/ujlow=
|
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:ucvhdsUCE3TH0LoLRb6ShHiJl8e39dGlx6A4g/ujlow=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/klauspost/reedsolomon v1.9.3 h1:N/VzgeMfHmLc+KHMD1UL/tNkfXAt8FnUqlgXGIduwAY=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/klauspost/reedsolomon v1.9.3/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/nadoo/conflag v0.2.2 h1:xywuyaevdBnA3+4g9S11ng+Nby725WN1LXargWnAXpM=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/nadoo/conflag v0.2.2/go.mod h1:dzFfDUpXdr2uS2oV+udpy5N2vfNOu/bFzjhX1WI52co=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/nadoo/go-shadowsocks2 v0.1.2 h1:+tCSt65YAAMf24wj3tqv6a9oVBcqSGFYVsifBZwT9w8=
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/nadoo/go-shadowsocks2 v0.1.2/go.mod h1:/E2kSkS0mqF/e79wcAA0PezoWXk4CY9HldJlzwWtbwU=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
github.com/nadoo/shadowsocksR v0.1.0 h1:sYPxZi0l8F1nxDDcckzb0DHXxhe0LNW5iSeohqPw6Fg=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
github.com/nadoo/shadowsocksR v0.1.0/go.mod h1:nqcLRU7laARXdLLBrHP8odruT/6GIureicuRTs4R+RY=
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b h1:mnG1fcsIB1d/3vbkBak2MM0u+vhGhlQwpeimUi7QncM=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/tjfoc/gmsm v1.0.1 h1:R11HlqhXkDospckjZEihx9SW/2VW0RgdwrykyWMFOQU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
|
github.com/xtaci/kcp-go v5.4.11+incompatible h1:tJbtarpmOoOD74cZ41uvvF5Hyt1nvctHQCOxZ6ot5xw=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
|
github.com/xtaci/kcp-go v5.4.11+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
|
||||||
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/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.1.1 h1:isxHaxBXpYFWnk2DReuKkigaZyrjs2+9ypIdGP4h+HI=
|
|
||||||
github.com/templexxx/cpu v0.1.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
|
||||||
github.com/templexxx/xorsimd v0.4.3 h1:9AQTFHd7Bhk3dIT7Al2XeBX5DWOvsUPZCuhyAtNbHjU=
|
|
||||||
github.com/templexxx/xorsimd v0.4.3/go.mod h1:oZQcD6RFDisW2Am58dSAGwwL6rHjbzrlu25VDqfWkQg=
|
|
||||||
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
|
||||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
|
||||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
|
||||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.18 h1:7oV4mc272pcnn39/13BB11Bx7hJM4ogMIEokJYVWn4g=
|
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.18/go.mod h1:75S1AKYYzNUSXIv30h+jPKJYZUwqpfvLshu63nCNSOM=
|
|
||||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
|
||||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191010185427-af544f31c8ac h1:/b4NMZurYfBIQyRMqaPGMDeUrSW6gU7/7Hv6owY1Vjk=
|
||||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
golang.org/x/crypto v0.0.0-20191010185427-af544f31c8ac/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 h1:p9xBe/w/OzkeYVKm234g55gMdD1nSIooTir5kV11kfA=
|
||||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
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.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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191009170203-06d7bd2c5f4f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
golang.org/x/sys v0.0.0-20191020212454-3e7259c5e7c2 h1:nq114VpM8lsSlP+lyUbANecYHYiFcSNFtqcBlxRV+gA=
|
||||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
golang.org/x/sys v0.0.0-20191020212454-3e7259c5e7c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
|
||||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
|
@ -1,78 +1,475 @@
|
|||||||
|
// Apache License 2.0
|
||||||
|
// @mdlayher https://github.com/mdlayher/netlink
|
||||||
|
// Ref: https://github.com/vishvananda/netlink/blob/master/nl/nl_linux.go
|
||||||
|
|
||||||
package ipset
|
package ipset
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/netip"
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/nadoo/ipset"
|
"github.com/nadoo/glider/common/log"
|
||||||
|
|
||||||
"github.com/nadoo/glider/rule"
|
"github.com/nadoo/glider/rule"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Manager struct.
|
// NFNL_SUBSYS_IPSET netfilter netlink message types
|
||||||
|
// https://github.com/torvalds/linux/blob/9e66317d3c92ddaab330c125dfe9d06eee268aff/include/uapi/linux/netfilter/nfnetlink.h#L56
|
||||||
|
const NFNL_SUBSYS_IPSET = 6
|
||||||
|
|
||||||
|
// IPSET_PROTOCOL The protocol version
|
||||||
|
// http://git.netfilter.org/ipset/tree/include/libipset/linux_ip_set.h
|
||||||
|
const IPSET_PROTOCOL = 6
|
||||||
|
|
||||||
|
// IPSET_MAXNAMELEN The max length of strings including NUL: set and type identifiers
|
||||||
|
const IPSET_MAXNAMELEN = 32
|
||||||
|
|
||||||
|
// Message types and commands
|
||||||
|
const (
|
||||||
|
IPSET_CMD_CREATE = 2
|
||||||
|
IPSET_CMD_FLUSH = 4
|
||||||
|
IPSET_CMD_ADD = 9
|
||||||
|
IPSET_CMD_DEL = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
// Attributes at command level
|
||||||
|
const (
|
||||||
|
IPSET_ATTR_PROTOCOL = 1 /* 1: Protocol version */
|
||||||
|
IPSET_ATTR_SETNAME = 2 /* 2: Name of the set */
|
||||||
|
IPSET_ATTR_TYPENAME = 3 /* 3: Typename */
|
||||||
|
IPSET_ATTR_REVISION = 4 /* 4: Settype revision */
|
||||||
|
IPSET_ATTR_FAMILY = 5 /* 5: Settype family */
|
||||||
|
IPSET_ATTR_DATA = 7 /* 7: Nested attributes */
|
||||||
|
)
|
||||||
|
|
||||||
|
// CADT specific attributes
|
||||||
|
const (
|
||||||
|
IPSET_ATTR_IP = 1
|
||||||
|
IPSET_ATTR_CIDR = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// IP specific attributes
|
||||||
|
const (
|
||||||
|
IPSET_ATTR_IPADDR_IPV4 = 1
|
||||||
|
IPSET_ATTR_IPADDR_IPV6 = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// ATTR flags
|
||||||
|
const (
|
||||||
|
NLA_F_NESTED = (1 << 15)
|
||||||
|
NLA_F_NET_BYTEORDER = (1 << 14)
|
||||||
|
)
|
||||||
|
|
||||||
|
var nextSeqNr uint32
|
||||||
|
var nativeEndian binary.ByteOrder
|
||||||
|
|
||||||
|
// Manager struct
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
|
fd int
|
||||||
|
lsa syscall.SockaddrNetlink
|
||||||
|
|
||||||
domainSet sync.Map
|
domainSet sync.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManager returns a Manager
|
// NewManager returns a Manager
|
||||||
func NewManager(rules []*rule.Config) (*Manager, error) {
|
func NewManager(rules []*rule.Config) (*Manager, error) {
|
||||||
if err := ipset.Init(); err != nil {
|
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_NETFILTER)
|
||||||
|
if err != nil {
|
||||||
|
log.F("%s", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// defer syscall.Close(fd)
|
||||||
|
|
||||||
|
lsa := syscall.SockaddrNetlink{
|
||||||
|
Family: syscall.AF_NETLINK,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = syscall.Bind(fd, &lsa); err != nil {
|
||||||
|
log.F("%s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
m := &Manager{}
|
m := &Manager{fd: fd, lsa: lsa}
|
||||||
sets := make(map[string]struct{})
|
|
||||||
|
|
||||||
|
// create ipset
|
||||||
for _, r := range rules {
|
for _, r := range rules {
|
||||||
if r.IPSet == "" {
|
if r.IPSet != "" {
|
||||||
continue
|
CreateSet(fd, lsa, r.IPSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// init ipset
|
||||||
|
for _, r := range rules {
|
||||||
|
if r.IPSet != "" {
|
||||||
for _, domain := range r.Domain {
|
for _, domain := range r.Domain {
|
||||||
m.domainSet.Store(domain, r.IPSet)
|
m.domainSet.Store(domain, r.IPSet)
|
||||||
}
|
}
|
||||||
for _, ip := range r.IP {
|
for _, ip := range r.IP {
|
||||||
addToSet(r.IPSet, ip)
|
AddToSet(fd, lsa, r.IPSet, ip)
|
||||||
}
|
}
|
||||||
for _, cidr := range r.CIDR {
|
for _, cidr := range r.CIDR {
|
||||||
addToSet(r.IPSet, cidr)
|
AddToSet(fd, lsa, r.IPSet, cidr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddDomainIP implements the dns AnswerHandler function, used to update ipset according to domainSet rule.
|
// AddDomainIP implements the DNSAnswerHandler function, used to update ipset according to domainSet rule
|
||||||
func (m *Manager) AddDomainIP(domain string, ip netip.Addr) error {
|
func (m *Manager) AddDomainIP(domain, ip string) error {
|
||||||
domain = strings.ToLower(domain)
|
if ip != "" {
|
||||||
for i := len(domain); i != -1; {
|
domainParts := strings.Split(domain, ".")
|
||||||
i = strings.LastIndexByte(domain[:i], '.')
|
length := len(domainParts)
|
||||||
if setName, ok := m.domainSet.Load(domain[i+1:]); ok {
|
for i := length - 1; i >= 0; i-- {
|
||||||
addAddrToSet(setName.(string), ip)
|
domain := strings.Join(domainParts[i:length], ".")
|
||||||
|
|
||||||
|
// find in domainMap
|
||||||
|
if ipset, ok := m.domainSet.Load(domain); ok {
|
||||||
|
AddToSet(m.fd, m.lsa, ipset.(string), ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addToSet(s, item string) error {
|
// CreateSet create a ipset
|
||||||
if strings.IndexByte(item, '.') == -1 {
|
func CreateSet(fd int, lsa syscall.SockaddrNetlink, setName string) {
|
||||||
return ipset.Add(s+"6", item)
|
if setName == "" {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return ipset.Add(s, item)
|
|
||||||
|
if len(setName) > IPSET_MAXNAMELEN {
|
||||||
|
log.Fatal("ipset: name too long")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.F("ipset create %s hash:net", setName)
|
||||||
|
|
||||||
|
req := NewNetlinkRequest(IPSET_CMD_CREATE|(NFNL_SUBSYS_IPSET<<8), syscall.NLM_F_REQUEST)
|
||||||
|
|
||||||
|
// TODO: support AF_INET6
|
||||||
|
req.AddData(NewNfGenMsg(syscall.AF_INET, 0, 0))
|
||||||
|
req.AddData(NewRtAttr(IPSET_ATTR_PROTOCOL, Uint8Attr(IPSET_PROTOCOL)))
|
||||||
|
req.AddData(NewRtAttr(IPSET_ATTR_SETNAME, ZeroTerminated(setName)))
|
||||||
|
req.AddData(NewRtAttr(IPSET_ATTR_TYPENAME, ZeroTerminated("hash:net")))
|
||||||
|
req.AddData(NewRtAttr(IPSET_ATTR_REVISION, Uint8Attr(1)))
|
||||||
|
req.AddData(NewRtAttr(IPSET_ATTR_FAMILY, Uint8Attr(2)))
|
||||||
|
req.AddData(NewRtAttr(IPSET_ATTR_DATA|NLA_F_NESTED, nil))
|
||||||
|
|
||||||
|
err := syscall.Sendto(fd, req.Serialize(), 0, &lsa)
|
||||||
|
if err != nil {
|
||||||
|
log.F("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
FlushSet(fd, lsa, setName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addAddrToSet(s string, ip netip.Addr) error {
|
// FlushSet flush a ipset
|
||||||
if ip.Is4() {
|
func FlushSet(fd int, lsa syscall.SockaddrNetlink, setName string) {
|
||||||
return ipset.AddAddr(s, ip)
|
log.F("ipset flush %s", setName)
|
||||||
|
|
||||||
|
req := NewNetlinkRequest(IPSET_CMD_FLUSH|(NFNL_SUBSYS_IPSET<<8), syscall.NLM_F_REQUEST)
|
||||||
|
|
||||||
|
// TODO: support AF_INET6
|
||||||
|
req.AddData(NewNfGenMsg(syscall.AF_INET, 0, 0))
|
||||||
|
req.AddData(NewRtAttr(IPSET_ATTR_PROTOCOL, Uint8Attr(IPSET_PROTOCOL)))
|
||||||
|
req.AddData(NewRtAttr(IPSET_ATTR_SETNAME, ZeroTerminated(setName)))
|
||||||
|
|
||||||
|
err := syscall.Sendto(fd, req.Serialize(), 0, &lsa)
|
||||||
|
if err != nil {
|
||||||
|
log.F("%s", err)
|
||||||
}
|
}
|
||||||
return ipset.AddAddr(s+"6", ip)
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddToSet adds an entry to ipset
|
||||||
|
func AddToSet(fd int, lsa syscall.SockaddrNetlink, setName, entry string) {
|
||||||
|
if setName == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(setName) > IPSET_MAXNAMELEN {
|
||||||
|
log.F("ipset: name too long")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.F("ipset add %s %s", setName, entry)
|
||||||
|
|
||||||
|
var ip net.IP
|
||||||
|
var cidr *net.IPNet
|
||||||
|
|
||||||
|
ip, cidr, err := net.ParseCIDR(entry)
|
||||||
|
if err != nil {
|
||||||
|
ip = net.ParseIP(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip == nil {
|
||||||
|
log.F("ipset: parse %s error", entry)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := NewNetlinkRequest(IPSET_CMD_ADD|(NFNL_SUBSYS_IPSET<<8), syscall.NLM_F_REQUEST)
|
||||||
|
|
||||||
|
// TODO: support AF_INET6
|
||||||
|
req.AddData(NewNfGenMsg(syscall.AF_INET, 0, 0))
|
||||||
|
req.AddData(NewRtAttr(IPSET_ATTR_PROTOCOL, Uint8Attr(IPSET_PROTOCOL)))
|
||||||
|
req.AddData(NewRtAttr(IPSET_ATTR_SETNAME, ZeroTerminated(setName)))
|
||||||
|
|
||||||
|
attrNested := NewRtAttr(IPSET_ATTR_DATA|NLA_F_NESTED, nil)
|
||||||
|
attrIP := NewRtAttrChild(attrNested, IPSET_ATTR_IP|NLA_F_NESTED, nil)
|
||||||
|
|
||||||
|
// TODO: support ipV6
|
||||||
|
NewRtAttrChild(attrIP, IPSET_ATTR_IPADDR_IPV4|NLA_F_NET_BYTEORDER, ip.To4())
|
||||||
|
|
||||||
|
// for cidr prefix
|
||||||
|
if cidr != nil {
|
||||||
|
cidrPrefix, _ := cidr.Mask.Size()
|
||||||
|
NewRtAttrChild(attrNested, IPSET_ATTR_CIDR, Uint8Attr(uint8(cidrPrefix)))
|
||||||
|
}
|
||||||
|
|
||||||
|
NewRtAttrChild(attrNested, 9|NLA_F_NET_BYTEORDER, Uint32Attr(0))
|
||||||
|
req.AddData(attrNested)
|
||||||
|
|
||||||
|
err = syscall.Sendto(fd, req.Serialize(), 0, &lsa)
|
||||||
|
if err != nil {
|
||||||
|
log.F("%s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NativeEndian get native endianness for the system
|
||||||
|
func NativeEndian() binary.ByteOrder {
|
||||||
|
if nativeEndian == nil {
|
||||||
|
var x uint32 = 0x01020304
|
||||||
|
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
|
||||||
|
nativeEndian = binary.BigEndian
|
||||||
|
} else {
|
||||||
|
nativeEndian = binary.LittleEndian
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nativeEndian
|
||||||
|
}
|
||||||
|
|
||||||
|
func rtaAlignOf(attrlen int) int {
|
||||||
|
return (attrlen + syscall.RTA_ALIGNTO - 1) & ^(syscall.RTA_ALIGNTO - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetlinkRequestData .
|
||||||
|
type NetlinkRequestData interface {
|
||||||
|
Len() int
|
||||||
|
Serialize() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NfGenMsg .
|
||||||
|
type NfGenMsg struct {
|
||||||
|
nfgenFamily uint8
|
||||||
|
version uint8
|
||||||
|
resID uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNfGenMsg .
|
||||||
|
func NewNfGenMsg(nfgenFamily, version, resID int) *NfGenMsg {
|
||||||
|
return &NfGenMsg{
|
||||||
|
nfgenFamily: uint8(nfgenFamily),
|
||||||
|
version: uint8(version),
|
||||||
|
resID: uint16(resID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len .
|
||||||
|
func (m *NfGenMsg) Len() int {
|
||||||
|
return rtaAlignOf(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize .
|
||||||
|
func (m *NfGenMsg) Serialize() []byte {
|
||||||
|
native := NativeEndian()
|
||||||
|
|
||||||
|
length := m.Len()
|
||||||
|
buf := make([]byte, rtaAlignOf(length))
|
||||||
|
buf[0] = m.nfgenFamily
|
||||||
|
buf[1] = m.version
|
||||||
|
native.PutUint16(buf[2:4], m.resID)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// RtAttr Extend RtAttr to handle data and children
|
||||||
|
type RtAttr struct {
|
||||||
|
syscall.RtAttr
|
||||||
|
Data []byte
|
||||||
|
children []NetlinkRequestData
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRtAttr Create a new Extended RtAttr object
|
||||||
|
func NewRtAttr(attrType int, data []byte) *RtAttr {
|
||||||
|
return &RtAttr{
|
||||||
|
RtAttr: syscall.RtAttr{
|
||||||
|
Type: uint16(attrType),
|
||||||
|
},
|
||||||
|
children: []NetlinkRequestData{},
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRtAttrChild Create a new RtAttr obj anc add it as a child of an existing object
|
||||||
|
func NewRtAttrChild(parent *RtAttr, attrType int, data []byte) *RtAttr {
|
||||||
|
attr := NewRtAttr(attrType, data)
|
||||||
|
parent.children = append(parent.children, attr)
|
||||||
|
return attr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len .
|
||||||
|
func (a *RtAttr) Len() int {
|
||||||
|
if len(a.children) == 0 {
|
||||||
|
return (syscall.SizeofRtAttr + len(a.Data))
|
||||||
|
}
|
||||||
|
|
||||||
|
l := 0
|
||||||
|
for _, child := range a.children {
|
||||||
|
l += rtaAlignOf(child.Len())
|
||||||
|
}
|
||||||
|
l += syscall.SizeofRtAttr
|
||||||
|
return rtaAlignOf(l + len(a.Data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize the RtAttr into a byte array
|
||||||
|
// This can't just unsafe.cast because it must iterate through children.
|
||||||
|
func (a *RtAttr) Serialize() []byte {
|
||||||
|
native := NativeEndian()
|
||||||
|
|
||||||
|
length := a.Len()
|
||||||
|
buf := make([]byte, rtaAlignOf(length))
|
||||||
|
|
||||||
|
next := 4
|
||||||
|
if a.Data != nil {
|
||||||
|
copy(buf[next:], a.Data)
|
||||||
|
next += rtaAlignOf(len(a.Data))
|
||||||
|
}
|
||||||
|
if len(a.children) > 0 {
|
||||||
|
for _, child := range a.children {
|
||||||
|
childBuf := child.Serialize()
|
||||||
|
copy(buf[next:], childBuf)
|
||||||
|
next += rtaAlignOf(len(childBuf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if l := uint16(length); l != 0 {
|
||||||
|
native.PutUint16(buf[0:2], l)
|
||||||
|
}
|
||||||
|
native.PutUint16(buf[2:4], a.Type)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetlinkRequest .
|
||||||
|
type NetlinkRequest struct {
|
||||||
|
syscall.NlMsghdr
|
||||||
|
Data []NetlinkRequestData
|
||||||
|
RawData []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNetlinkRequest create a new netlink request from proto and flags
|
||||||
|
// Note the Len value will be inaccurate once data is added until
|
||||||
|
// the message is serialized
|
||||||
|
func NewNetlinkRequest(proto, flags int) *NetlinkRequest {
|
||||||
|
return &NetlinkRequest{
|
||||||
|
NlMsghdr: syscall.NlMsghdr{
|
||||||
|
Len: uint32(syscall.SizeofNlMsghdr),
|
||||||
|
Type: uint16(proto),
|
||||||
|
Flags: syscall.NLM_F_REQUEST | uint16(flags),
|
||||||
|
Seq: atomic.AddUint32(&nextSeqNr, 1),
|
||||||
|
// Pid: uint32(os.Getpid()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize the Netlink Request into a byte array
|
||||||
|
func (req *NetlinkRequest) Serialize() []byte {
|
||||||
|
length := syscall.SizeofNlMsghdr
|
||||||
|
dataBytes := make([][]byte, len(req.Data))
|
||||||
|
for i, data := range req.Data {
|
||||||
|
dataBytes[i] = data.Serialize()
|
||||||
|
length = length + len(dataBytes[i])
|
||||||
|
}
|
||||||
|
length += len(req.RawData)
|
||||||
|
|
||||||
|
req.Len = uint32(length)
|
||||||
|
b := make([]byte, length)
|
||||||
|
hdr := (*(*[syscall.SizeofNlMsghdr]byte)(unsafe.Pointer(req)))[:]
|
||||||
|
next := syscall.SizeofNlMsghdr
|
||||||
|
copy(b[0:next], hdr)
|
||||||
|
for _, data := range dataBytes {
|
||||||
|
for _, dataByte := range data {
|
||||||
|
b[next] = dataByte
|
||||||
|
next = next + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add the raw data if any
|
||||||
|
if len(req.RawData) > 0 {
|
||||||
|
copy(b[next:length], req.RawData)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddData add data to request
|
||||||
|
func (req *NetlinkRequest) AddData(data NetlinkRequestData) {
|
||||||
|
if data != nil {
|
||||||
|
req.Data = append(req.Data, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRawData adds raw bytes to the end of the NetlinkRequest object during serialization
|
||||||
|
func (req *NetlinkRequest) AddRawData(data []byte) {
|
||||||
|
if data != nil {
|
||||||
|
req.RawData = append(req.RawData, data...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint8Attr .
|
||||||
|
func Uint8Attr(v uint8) []byte {
|
||||||
|
return []byte{byte(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint16Attr .
|
||||||
|
func Uint16Attr(v uint16) []byte {
|
||||||
|
native := NativeEndian()
|
||||||
|
bytes := make([]byte, 2)
|
||||||
|
native.PutUint16(bytes, v)
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32Attr .
|
||||||
|
func Uint32Attr(v uint32) []byte {
|
||||||
|
native := NativeEndian()
|
||||||
|
bytes := make([]byte, 4)
|
||||||
|
native.PutUint32(bytes, v)
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZeroTerminated .
|
||||||
|
func ZeroTerminated(s string) []byte {
|
||||||
|
bytes := make([]byte, len(s)+1)
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
bytes[i] = s[i]
|
||||||
|
}
|
||||||
|
bytes[len(s)] = 0
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// NonZeroTerminated .
|
||||||
|
func NonZeroTerminated(s string) []byte {
|
||||||
|
bytes := make([]byte, len(s))
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
bytes[i] = s[i]
|
||||||
|
}
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytesToString .
|
||||||
|
func BytesToString(b []byte) string {
|
||||||
|
n := bytes.Index(b, []byte{0})
|
||||||
|
return string(b[:n])
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
//go:build !linux
|
// +build !linux
|
||||||
|
|
||||||
package ipset
|
package ipset
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/rule"
|
"github.com/nadoo/glider/rule"
|
||||||
)
|
)
|
||||||
@ -18,6 +17,6 @@ func NewManager(rules []*rule.Config) (*Manager, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddDomainIP implements the DNSAnswerHandler function
|
// AddDomainIP implements the DNSAnswerHandler function
|
||||||
func (m *Manager) AddDomainIP(domain string, ip netip.Addr) error {
|
func (m *Manager) AddDomainIP(domain, ip string) error {
|
||||||
return errors.New("ipset not supported on this os")
|
return errors.New("ipset not supported on this os")
|
||||||
}
|
}
|
||||||
|
88
main.go
88
main.go
@ -1,92 +1,92 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"fmt"
|
||||||
"net"
|
stdlog "log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/common/log"
|
||||||
"github.com/nadoo/glider/dns"
|
"github.com/nadoo/glider/dns"
|
||||||
"github.com/nadoo/glider/ipset"
|
"github.com/nadoo/glider/ipset"
|
||||||
"github.com/nadoo/glider/pkg/log"
|
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
"github.com/nadoo/glider/rule"
|
"github.com/nadoo/glider/rule"
|
||||||
"github.com/nadoo/glider/service"
|
"github.com/nadoo/glider/strategy"
|
||||||
|
|
||||||
|
_ "github.com/nadoo/glider/proxy/http"
|
||||||
|
_ "github.com/nadoo/glider/proxy/kcp"
|
||||||
|
_ "github.com/nadoo/glider/proxy/mixed"
|
||||||
|
_ "github.com/nadoo/glider/proxy/obfs"
|
||||||
|
_ "github.com/nadoo/glider/proxy/reject"
|
||||||
|
_ "github.com/nadoo/glider/proxy/socks5"
|
||||||
|
_ "github.com/nadoo/glider/proxy/ss"
|
||||||
|
_ "github.com/nadoo/glider/proxy/ssr"
|
||||||
|
_ "github.com/nadoo/glider/proxy/tcptun"
|
||||||
|
_ "github.com/nadoo/glider/proxy/tls"
|
||||||
|
_ "github.com/nadoo/glider/proxy/udptun"
|
||||||
|
_ "github.com/nadoo/glider/proxy/uottun"
|
||||||
|
_ "github.com/nadoo/glider/proxy/vmess"
|
||||||
|
_ "github.com/nadoo/glider/proxy/ws"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var version = "0.9.2"
|
||||||
version = "0.17.0"
|
|
||||||
config = parseConfig()
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// read configs
|
||||||
|
confInit()
|
||||||
|
|
||||||
|
// setup a log func
|
||||||
|
log.F = func(f string, v ...interface{}) {
|
||||||
|
if conf.Verbose {
|
||||||
|
stdlog.Output(2, fmt.Sprintf(f, v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// global rule proxy
|
// global rule proxy
|
||||||
pxy := rule.NewProxy(config.Forwards, &config.Strategy, config.rules)
|
p := rule.NewProxy(conf.rules, strategy.NewProxy(conf.Forward, &conf.StrategyConfig))
|
||||||
|
|
||||||
// ipset manager
|
// ipset manager
|
||||||
ipsetM, _ := ipset.NewManager(config.rules)
|
ipsetM, _ := ipset.NewManager(conf.rules)
|
||||||
|
|
||||||
// check and setup dns server
|
// check and setup dns server
|
||||||
if config.DNS != "" {
|
if conf.DNS != "" {
|
||||||
d, err := dns.NewServer(config.DNS, pxy, &config.DNSConfig)
|
d, err := dns.NewServer(conf.DNS, p, &conf.DNSConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// rules
|
// rule
|
||||||
for _, r := range config.rules {
|
for _, r := range conf.rules {
|
||||||
if len(r.DNSServers) > 0 {
|
|
||||||
for _, domain := range r.Domain {
|
for _, domain := range r.Domain {
|
||||||
d.SetServers(domain, r.DNSServers)
|
if len(r.DNSServers) > 0 {
|
||||||
|
d.SetServers(domain, r.DNSServers...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add a handler to update proxy rules when a domain resolved
|
// add a handler to update proxy rules when a domain resolved
|
||||||
d.AddHandler(pxy.AddDomainIP)
|
d.AddHandler(p.AddDomainIP)
|
||||||
if ipsetM != nil {
|
if ipsetM != nil {
|
||||||
d.AddHandler(ipsetM.AddDomainIP)
|
d.AddHandler(ipsetM.AddDomainIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.Start()
|
d.Start()
|
||||||
|
|
||||||
// custom resolver
|
|
||||||
net.DefaultResolver = &net.Resolver{
|
|
||||||
PreferGo: true,
|
|
||||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
|
||||||
d := net.Dialer{Timeout: time.Second * 3}
|
|
||||||
return d.DialContext(ctx, "udp", config.DNS)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range config.rules {
|
|
||||||
r.IP, r.CIDR, r.Domain = nil, nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// enable checkers
|
// enable checkers
|
||||||
pxy.Check()
|
p.Check()
|
||||||
|
|
||||||
// run proxy servers
|
// Proxy Servers
|
||||||
for _, listen := range config.Listens {
|
for _, listen := range conf.Listen {
|
||||||
local, err := proxy.ServerFromURL(listen, pxy)
|
local, err := proxy.ServerFromURL(listen, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go local.ListenAndServe()
|
go local.ListenAndServe()
|
||||||
}
|
}
|
||||||
|
|
||||||
// run services
|
|
||||||
for _, s := range config.Services {
|
|
||||||
service, err := service.New(s)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
go service.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
sigCh := make(chan os.Signal, 1)
|
sigCh := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||||
<-sigCh
|
<-sigCh
|
||||||
|
6
main_linux.go
Normal file
6
main_linux.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/nadoo/glider/proxy/redir"
|
||||||
|
_ "github.com/nadoo/glider/proxy/unix"
|
||||||
|
)
|
@ -1,41 +0,0 @@
|
|||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
stdlog "log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var enable = false
|
|
||||||
|
|
||||||
// Set sets the logger's verbose mode and output flags.
|
|
||||||
func Set(verbose bool, flag int) {
|
|
||||||
enable = verbose
|
|
||||||
stdlog.SetFlags(flag)
|
|
||||||
}
|
|
||||||
|
|
||||||
// F prints debug log.
|
|
||||||
func F(f string, v ...any) {
|
|
||||||
if enable {
|
|
||||||
stdlog.Output(2, fmt.Sprintf(f, v...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print prints log.
|
|
||||||
func Print(v ...any) {
|
|
||||||
stdlog.Print(v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf prints log.
|
|
||||||
func Printf(f string, v ...any) {
|
|
||||||
stdlog.Printf(f, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatal log and exit.
|
|
||||||
func Fatal(v ...any) {
|
|
||||||
stdlog.Fatal(v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatalf log and exit.
|
|
||||||
func Fatalf(f string, v ...any) {
|
|
||||||
stdlog.Fatalf(f, v...)
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
package pool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/bits"
|
|
||||||
"sync"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// number of pools.
|
|
||||||
num = 17
|
|
||||||
maxsize = 1 << (num - 1)
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
sizes [num]int
|
|
||||||
pools [num]sync.Pool
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
for i := range num {
|
|
||||||
size := 1 << i
|
|
||||||
sizes[i] = size
|
|
||||||
pools[i].New = func() any {
|
|
||||||
buf := make([]byte, size)
|
|
||||||
return unsafe.SliceData(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBuffer gets a buffer from pool, size should in range: [1, 65536],
|
|
||||||
// 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 p := pools[i].Get().(*byte); p != nil {
|
|
||||||
return unsafe.Slice(p, 1<<i)[:size]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return make([]byte, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutBuffer puts a buffer into pool.
|
|
||||||
func PutBuffer(buf []byte) {
|
|
||||||
if size := cap(buf); size >= 1 && size <= maxsize {
|
|
||||||
i := bits.Len32(uint32(size - 1))
|
|
||||||
if sizes[i] == size {
|
|
||||||
pools[i].Put(unsafe.SliceData(buf))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package pool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var bufReaderPool sync.Pool
|
|
||||||
|
|
||||||
// GetBufReader returns a *bufio.Reader from pool.
|
|
||||||
func GetBufReader(r io.Reader) *bufio.Reader {
|
|
||||||
if v := bufReaderPool.Get(); v != nil {
|
|
||||||
br := v.(*bufio.Reader)
|
|
||||||
br.Reset(r)
|
|
||||||
return br
|
|
||||||
}
|
|
||||||
return bufio.NewReader(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutBufReader puts a *bufio.Reader into pool.
|
|
||||||
func PutBufReader(br *bufio.Reader) {
|
|
||||||
bufReaderPool.Put(br)
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package pool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var bytesBufPool = sync.Pool{
|
|
||||||
New: func() any { return &bytes.Buffer{} },
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBytesBuffer returns a bytes.buffer from pool.
|
|
||||||
func GetBytesBuffer() *bytes.Buffer {
|
|
||||||
return bytesBufPool.Get().(*bytes.Buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutBytesBuffer puts a bytes.buffer into pool.
|
|
||||||
func PutBytesBuffer(buf *bytes.Buffer) {
|
|
||||||
if buf.Cap() <= 64<<10 {
|
|
||||||
buf.Reset()
|
|
||||||
bytesBufPool.Put(buf)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,106 +0,0 @@
|
|||||||
// MIT License
|
|
||||||
//
|
|
||||||
// Copyright (c) 2016-2017 xtaci
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in all
|
|
||||||
// copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
// SOFTWARE.
|
|
||||||
|
|
||||||
package smux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ( // cmds
|
|
||||||
// protocol version 1:
|
|
||||||
cmdSYN byte = iota // stream open
|
|
||||||
cmdFIN // stream close, a.k.a EOF mark
|
|
||||||
cmdPSH // data push
|
|
||||||
cmdNOP // no operation
|
|
||||||
|
|
||||||
// protocol version 2 extra commands
|
|
||||||
// notify bytes consumed by remote peer-end
|
|
||||||
cmdUPD
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// data size of cmdUPD, format:
|
|
||||||
// |4B data consumed(ACK)| 4B window size(WINDOW) |
|
|
||||||
szCmdUPD = 8
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// initial peer window guess, a slow-start
|
|
||||||
initialPeerWindow = 262144
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
sizeOfVer = 1
|
|
||||||
sizeOfCmd = 1
|
|
||||||
sizeOfLength = 2
|
|
||||||
sizeOfSid = 4
|
|
||||||
headerSize = sizeOfVer + sizeOfCmd + sizeOfSid + sizeOfLength
|
|
||||||
)
|
|
||||||
|
|
||||||
// Frame defines a packet from or to be multiplexed into a single connection
|
|
||||||
type Frame struct {
|
|
||||||
ver byte // version
|
|
||||||
cmd byte // command
|
|
||||||
sid uint32 // stream id
|
|
||||||
data []byte // payload
|
|
||||||
}
|
|
||||||
|
|
||||||
// newFrame creates a new frame with given version, command and stream id
|
|
||||||
func newFrame(version byte, cmd byte, sid uint32) Frame {
|
|
||||||
return Frame{ver: version, cmd: cmd, sid: sid}
|
|
||||||
}
|
|
||||||
|
|
||||||
// rawHeader is a byte array representation of Frame header
|
|
||||||
type rawHeader [headerSize]byte
|
|
||||||
|
|
||||||
func (h rawHeader) Version() byte {
|
|
||||||
return h[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h rawHeader) Cmd() byte {
|
|
||||||
return h[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h rawHeader) Length() uint16 {
|
|
||||||
return binary.LittleEndian.Uint16(h[2:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h rawHeader) StreamID() uint32 {
|
|
||||||
return binary.LittleEndian.Uint32(h[4:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h rawHeader) String() string {
|
|
||||||
return fmt.Sprintf("Version:%d Cmd:%d StreamID:%d Length:%d",
|
|
||||||
h.Version(), h.Cmd(), h.StreamID(), h.Length())
|
|
||||||
}
|
|
||||||
|
|
||||||
// updHeader is a byte array representation of cmdUPD
|
|
||||||
type updHeader [szCmdUPD]byte
|
|
||||||
|
|
||||||
func (h updHeader) Consumed() uint32 {
|
|
||||||
return binary.LittleEndian.Uint32(h[:])
|
|
||||||
}
|
|
||||||
func (h updHeader) Window() uint32 {
|
|
||||||
return binary.LittleEndian.Uint32(h[4:])
|
|
||||||
}
|
|
128
pkg/smux/mux.go
128
pkg/smux/mux.go
@ -1,128 +0,0 @@
|
|||||||
// MIT License
|
|
||||||
//
|
|
||||||
// Copyright (c) 2016-2017 xtaci
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in all
|
|
||||||
// copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
// SOFTWARE.
|
|
||||||
|
|
||||||
package smux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config is used to tune the Smux session
|
|
||||||
type Config struct {
|
|
||||||
// SMUX Protocol version, support 1,2
|
|
||||||
Version int
|
|
||||||
|
|
||||||
// Disabled keepalive
|
|
||||||
KeepAliveDisabled bool
|
|
||||||
|
|
||||||
// KeepAliveInterval is how often to send a NOP command to the remote
|
|
||||||
KeepAliveInterval time.Duration
|
|
||||||
|
|
||||||
// KeepAliveTimeout is how long the session
|
|
||||||
// will be closed if no data has arrived
|
|
||||||
KeepAliveTimeout time.Duration
|
|
||||||
|
|
||||||
// MaxFrameSize is used to control the maximum
|
|
||||||
// frame size to sent to the remote
|
|
||||||
MaxFrameSize int
|
|
||||||
|
|
||||||
// MaxReceiveBuffer is used to control the maximum
|
|
||||||
// number of data in the buffer pool
|
|
||||||
MaxReceiveBuffer int
|
|
||||||
|
|
||||||
// MaxStreamBuffer is used to control the maximum
|
|
||||||
// number of data per stream
|
|
||||||
MaxStreamBuffer int
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultConfig is used to return a default configuration
|
|
||||||
func DefaultConfig() *Config {
|
|
||||||
return &Config{
|
|
||||||
Version: 1,
|
|
||||||
KeepAliveInterval: 10 * time.Second,
|
|
||||||
KeepAliveTimeout: 30 * time.Second,
|
|
||||||
MaxFrameSize: 32768,
|
|
||||||
MaxReceiveBuffer: 4194304,
|
|
||||||
MaxStreamBuffer: 65536,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyConfig is used to verify the sanity of configuration
|
|
||||||
func VerifyConfig(config *Config) error {
|
|
||||||
if !(config.Version == 1 || config.Version == 2) {
|
|
||||||
return errors.New("unsupported protocol version")
|
|
||||||
}
|
|
||||||
if !config.KeepAliveDisabled {
|
|
||||||
if config.KeepAliveInterval == 0 {
|
|
||||||
return errors.New("keep-alive interval must be positive")
|
|
||||||
}
|
|
||||||
if config.KeepAliveTimeout < config.KeepAliveInterval {
|
|
||||||
return fmt.Errorf("keep-alive timeout must be larger than keep-alive interval")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if config.MaxFrameSize <= 0 {
|
|
||||||
return errors.New("max frame size must be positive")
|
|
||||||
}
|
|
||||||
if config.MaxFrameSize > 65535 {
|
|
||||||
return errors.New("max frame size must not be larger than 65535")
|
|
||||||
}
|
|
||||||
if config.MaxReceiveBuffer <= 0 {
|
|
||||||
return errors.New("max receive buffer must be positive")
|
|
||||||
}
|
|
||||||
if config.MaxStreamBuffer <= 0 {
|
|
||||||
return errors.New("max stream buffer must be positive")
|
|
||||||
}
|
|
||||||
if config.MaxStreamBuffer > config.MaxReceiveBuffer {
|
|
||||||
return errors.New("max stream buffer must not be larger than max receive buffer")
|
|
||||||
}
|
|
||||||
if config.MaxStreamBuffer > math.MaxInt32 {
|
|
||||||
return errors.New("max stream buffer cannot be larger than 2147483647")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server is used to initialize a new server-side connection.
|
|
||||||
func Server(conn io.ReadWriteCloser, config *Config) (*Session, error) {
|
|
||||||
if config == nil {
|
|
||||||
config = DefaultConfig()
|
|
||||||
}
|
|
||||||
if err := VerifyConfig(config); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return newSession(config, conn, false), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client is used to initialize a new client-side connection.
|
|
||||||
func Client(conn io.ReadWriteCloser, config *Config) (*Session, error) {
|
|
||||||
if config == nil {
|
|
||||||
config = DefaultConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := VerifyConfig(config); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return newSession(config, conn, true), nil
|
|
||||||
}
|
|
@ -1,621 +0,0 @@
|
|||||||
// MIT License
|
|
||||||
//
|
|
||||||
// Copyright (c) 2016-2017 xtaci
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in all
|
|
||||||
// copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
// SOFTWARE.
|
|
||||||
|
|
||||||
package smux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"container/heap"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultAcceptBacklog = 1024
|
|
||||||
maxShaperSize = 1024
|
|
||||||
openCloseTimeout = 30 * time.Second // Timeout for opening/closing streams
|
|
||||||
)
|
|
||||||
|
|
||||||
// CLASSID represents the class of a frame
|
|
||||||
type CLASSID int
|
|
||||||
|
|
||||||
const (
|
|
||||||
CLSCTRL CLASSID = iota // prioritized control signal
|
|
||||||
CLSDATA
|
|
||||||
)
|
|
||||||
|
|
||||||
// timeoutError representing timeouts for operations such as accept, read and write
|
|
||||||
//
|
|
||||||
// To better cooperate with the standard library, timeoutError should implement the standard library's `net.Error`.
|
|
||||||
//
|
|
||||||
// For example, using smux to implement net.Listener and work with http.Server, the keep-alive connection (*smux.Stream) will be unexpectedly closed.
|
|
||||||
// For more details, see https://github.com/xtaci/smux/pull/99.
|
|
||||||
type timeoutError struct{}
|
|
||||||
|
|
||||||
func (timeoutError) Error() string { return "timeout" }
|
|
||||||
func (timeoutError) Temporary() bool { return true }
|
|
||||||
func (timeoutError) Timeout() bool { return true }
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrInvalidProtocol = errors.New("invalid protocol")
|
|
||||||
ErrConsumed = errors.New("peer consumed more than sent")
|
|
||||||
ErrGoAway = errors.New("stream id overflows, should start a new connection")
|
|
||||||
ErrTimeout net.Error = &timeoutError{}
|
|
||||||
ErrWouldBlock = errors.New("operation would block on IO")
|
|
||||||
)
|
|
||||||
|
|
||||||
// writeRequest represents a request to write a frame
|
|
||||||
type writeRequest struct {
|
|
||||||
class CLASSID
|
|
||||||
frame Frame
|
|
||||||
seq uint32
|
|
||||||
result chan writeResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeResult represents the result of a write request
|
|
||||||
type writeResult struct {
|
|
||||||
n int
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Session defines a multiplexed connection for streams
|
|
||||||
type Session struct {
|
|
||||||
conn io.ReadWriteCloser
|
|
||||||
|
|
||||||
config *Config
|
|
||||||
nextStreamID uint32 // next stream identifier
|
|
||||||
nextStreamIDLock sync.Mutex
|
|
||||||
|
|
||||||
bucket int32 // token bucket
|
|
||||||
bucketNotify chan struct{} // used for waiting for tokens
|
|
||||||
|
|
||||||
streams map[uint32]*stream // all streams in this session
|
|
||||||
streamLock sync.Mutex // locks streams
|
|
||||||
|
|
||||||
die chan struct{} // flag session has died
|
|
||||||
dieOnce sync.Once
|
|
||||||
|
|
||||||
// socket error handling
|
|
||||||
socketReadError atomic.Value
|
|
||||||
socketWriteError atomic.Value
|
|
||||||
chSocketReadError chan struct{}
|
|
||||||
chSocketWriteError chan struct{}
|
|
||||||
socketReadErrorOnce sync.Once
|
|
||||||
socketWriteErrorOnce sync.Once
|
|
||||||
|
|
||||||
// smux protocol errors
|
|
||||||
protoError atomic.Value
|
|
||||||
chProtoError chan struct{}
|
|
||||||
protoErrorOnce sync.Once
|
|
||||||
|
|
||||||
chAccepts chan *stream
|
|
||||||
|
|
||||||
dataReady int32 // flag data has arrived
|
|
||||||
|
|
||||||
goAway int32 // flag id exhausted
|
|
||||||
|
|
||||||
deadline atomic.Value
|
|
||||||
|
|
||||||
requestID uint32 // Monotonic increasing write request ID
|
|
||||||
shaper chan writeRequest // a shaper for writing
|
|
||||||
writes chan writeRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session {
|
|
||||||
s := new(Session)
|
|
||||||
s.die = make(chan struct{})
|
|
||||||
s.conn = conn
|
|
||||||
s.config = config
|
|
||||||
s.streams = make(map[uint32]*stream)
|
|
||||||
s.chAccepts = make(chan *stream, defaultAcceptBacklog)
|
|
||||||
s.bucket = int32(config.MaxReceiveBuffer)
|
|
||||||
s.bucketNotify = make(chan struct{}, 1)
|
|
||||||
s.shaper = make(chan writeRequest)
|
|
||||||
s.writes = make(chan writeRequest)
|
|
||||||
s.chSocketReadError = make(chan struct{})
|
|
||||||
s.chSocketWriteError = make(chan struct{})
|
|
||||||
s.chProtoError = make(chan struct{})
|
|
||||||
|
|
||||||
if client {
|
|
||||||
s.nextStreamID = 1
|
|
||||||
} else {
|
|
||||||
s.nextStreamID = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
go s.shaperLoop()
|
|
||||||
go s.recvLoop()
|
|
||||||
go s.sendLoop()
|
|
||||||
if !config.KeepAliveDisabled {
|
|
||||||
go s.keepalive()
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenStream is used to create a new stream
|
|
||||||
func (s *Session) OpenStream() (*Stream, error) {
|
|
||||||
if s.IsClosed() {
|
|
||||||
return nil, io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate stream id
|
|
||||||
s.nextStreamIDLock.Lock()
|
|
||||||
if s.goAway > 0 {
|
|
||||||
s.nextStreamIDLock.Unlock()
|
|
||||||
return nil, ErrGoAway
|
|
||||||
}
|
|
||||||
|
|
||||||
s.nextStreamID += 2
|
|
||||||
sid := s.nextStreamID
|
|
||||||
if sid == sid%2 { // stream-id overflows
|
|
||||||
s.goAway = 1
|
|
||||||
s.nextStreamIDLock.Unlock()
|
|
||||||
return nil, ErrGoAway
|
|
||||||
}
|
|
||||||
s.nextStreamIDLock.Unlock()
|
|
||||||
|
|
||||||
stream := newStream(sid, s.config.MaxFrameSize, s)
|
|
||||||
|
|
||||||
if _, err := s.writeControlFrame(newFrame(byte(s.config.Version), cmdSYN, sid)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.streamLock.Lock()
|
|
||||||
defer s.streamLock.Unlock()
|
|
||||||
select {
|
|
||||||
case <-s.chSocketReadError:
|
|
||||||
return nil, s.socketReadError.Load().(error)
|
|
||||||
case <-s.chSocketWriteError:
|
|
||||||
return nil, s.socketWriteError.Load().(error)
|
|
||||||
case <-s.die:
|
|
||||||
return nil, io.ErrClosedPipe
|
|
||||||
default:
|
|
||||||
s.streams[sid] = stream
|
|
||||||
wrapper := &Stream{stream: stream}
|
|
||||||
// NOTE(x): disabled finalizer for issue #997
|
|
||||||
/*
|
|
||||||
runtime.SetFinalizer(wrapper, func(s *Stream) {
|
|
||||||
s.Close()
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
return wrapper, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open returns a generic ReadWriteCloser
|
|
||||||
func (s *Session) Open() (io.ReadWriteCloser, error) {
|
|
||||||
return s.OpenStream()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcceptStream is used to block until the next available stream
|
|
||||||
// is ready to be accepted.
|
|
||||||
func (s *Session) AcceptStream() (*Stream, error) {
|
|
||||||
var deadline <-chan time.Time
|
|
||||||
if d, ok := s.deadline.Load().(time.Time); ok && !d.IsZero() {
|
|
||||||
timer := time.NewTimer(time.Until(d))
|
|
||||||
defer timer.Stop()
|
|
||||||
deadline = timer.C
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case stream := <-s.chAccepts:
|
|
||||||
wrapper := &Stream{stream: stream}
|
|
||||||
runtime.SetFinalizer(wrapper, func(s *Stream) {
|
|
||||||
s.Close()
|
|
||||||
})
|
|
||||||
return wrapper, nil
|
|
||||||
case <-deadline:
|
|
||||||
return nil, ErrTimeout
|
|
||||||
case <-s.chSocketReadError:
|
|
||||||
return nil, s.socketReadError.Load().(error)
|
|
||||||
case <-s.chProtoError:
|
|
||||||
return nil, s.protoError.Load().(error)
|
|
||||||
case <-s.die:
|
|
||||||
return nil, io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept Returns a generic ReadWriteCloser instead of smux.Stream
|
|
||||||
func (s *Session) Accept() (io.ReadWriteCloser, error) {
|
|
||||||
return s.AcceptStream()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close is used to close the session and all streams.
|
|
||||||
func (s *Session) Close() error {
|
|
||||||
var once bool
|
|
||||||
s.dieOnce.Do(func() {
|
|
||||||
close(s.die)
|
|
||||||
once = true
|
|
||||||
})
|
|
||||||
|
|
||||||
if once {
|
|
||||||
s.streamLock.Lock()
|
|
||||||
for k := range s.streams {
|
|
||||||
s.streams[k].sessionClose()
|
|
||||||
}
|
|
||||||
s.streamLock.Unlock()
|
|
||||||
return s.conn.Close()
|
|
||||||
} else {
|
|
||||||
return io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseChan can be used by someone who wants to be notified immediately when this
|
|
||||||
// session is closed
|
|
||||||
func (s *Session) CloseChan() <-chan struct{} {
|
|
||||||
return s.die
|
|
||||||
}
|
|
||||||
|
|
||||||
// notifyBucket notifies recvLoop that bucket is available
|
|
||||||
func (s *Session) notifyBucket() {
|
|
||||||
select {
|
|
||||||
case s.bucketNotify <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) notifyReadError(err error) {
|
|
||||||
s.socketReadErrorOnce.Do(func() {
|
|
||||||
s.socketReadError.Store(err)
|
|
||||||
close(s.chSocketReadError)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) notifyWriteError(err error) {
|
|
||||||
s.socketWriteErrorOnce.Do(func() {
|
|
||||||
s.socketWriteError.Store(err)
|
|
||||||
close(s.chSocketWriteError)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) notifyProtoError(err error) {
|
|
||||||
s.protoErrorOnce.Do(func() {
|
|
||||||
s.protoError.Store(err)
|
|
||||||
close(s.chProtoError)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsClosed does a safe check to see if we have shutdown
|
|
||||||
func (s *Session) IsClosed() bool {
|
|
||||||
select {
|
|
||||||
case <-s.die:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumStreams returns the number of currently open streams
|
|
||||||
func (s *Session) NumStreams() int {
|
|
||||||
if s.IsClosed() {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
s.streamLock.Lock()
|
|
||||||
defer s.streamLock.Unlock()
|
|
||||||
return len(s.streams)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDeadline sets a deadline used by Accept* calls.
|
|
||||||
// A zero time value disables the deadline.
|
|
||||||
func (s *Session) SetDeadline(t time.Time) error {
|
|
||||||
s.deadline.Store(t)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LocalAddr satisfies net.Conn interface
|
|
||||||
func (s *Session) LocalAddr() net.Addr {
|
|
||||||
if ts, ok := s.conn.(interface {
|
|
||||||
LocalAddr() net.Addr
|
|
||||||
}); ok {
|
|
||||||
return ts.LocalAddr()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoteAddr satisfies net.Conn interface
|
|
||||||
func (s *Session) RemoteAddr() net.Addr {
|
|
||||||
if ts, ok := s.conn.(interface {
|
|
||||||
RemoteAddr() net.Addr
|
|
||||||
}); ok {
|
|
||||||
return ts.RemoteAddr()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// notify the session that a stream has closed
|
|
||||||
func (s *Session) streamClosed(sid uint32) {
|
|
||||||
s.streamLock.Lock()
|
|
||||||
if stream, ok := s.streams[sid]; ok {
|
|
||||||
n := stream.recycleTokens()
|
|
||||||
if n > 0 { // return remaining tokens to the bucket
|
|
||||||
if atomic.AddInt32(&s.bucket, int32(n)) > 0 {
|
|
||||||
s.notifyBucket()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete(s.streams, sid)
|
|
||||||
}
|
|
||||||
s.streamLock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// returnTokens is called by stream to return token after read
|
|
||||||
func (s *Session) returnTokens(n int) {
|
|
||||||
if atomic.AddInt32(&s.bucket, int32(n)) > 0 {
|
|
||||||
s.notifyBucket()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// recvLoop keeps on reading from underlying connection if tokens are available
|
|
||||||
func (s *Session) recvLoop() {
|
|
||||||
var hdr rawHeader
|
|
||||||
var updHdr updHeader
|
|
||||||
|
|
||||||
for {
|
|
||||||
for atomic.LoadInt32(&s.bucket) <= 0 && !s.IsClosed() {
|
|
||||||
select {
|
|
||||||
case <-s.bucketNotify:
|
|
||||||
case <-s.die:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// read header first
|
|
||||||
if _, err := io.ReadFull(s.conn, hdr[:]); err == nil {
|
|
||||||
atomic.StoreInt32(&s.dataReady, 1)
|
|
||||||
if hdr.Version() != byte(s.config.Version) {
|
|
||||||
s.notifyProtoError(ErrInvalidProtocol)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sid := hdr.StreamID()
|
|
||||||
switch hdr.Cmd() {
|
|
||||||
case cmdNOP:
|
|
||||||
case cmdSYN: // stream opening
|
|
||||||
s.streamLock.Lock()
|
|
||||||
if _, ok := s.streams[sid]; !ok {
|
|
||||||
stream := newStream(sid, s.config.MaxFrameSize, s)
|
|
||||||
s.streams[sid] = stream
|
|
||||||
select {
|
|
||||||
case s.chAccepts <- stream:
|
|
||||||
case <-s.die:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.streamLock.Unlock()
|
|
||||||
case cmdFIN: // stream closing
|
|
||||||
s.streamLock.Lock()
|
|
||||||
if stream, ok := s.streams[sid]; ok {
|
|
||||||
stream.fin()
|
|
||||||
stream.notifyReadEvent()
|
|
||||||
}
|
|
||||||
s.streamLock.Unlock()
|
|
||||||
case cmdPSH: // data frame
|
|
||||||
if hdr.Length() > 0 {
|
|
||||||
newbuf := pool.GetBuffer(int(hdr.Length()))
|
|
||||||
if written, err := io.ReadFull(s.conn, newbuf); err == nil {
|
|
||||||
s.streamLock.Lock()
|
|
||||||
if stream, ok := s.streams[sid]; ok {
|
|
||||||
stream.pushBytes(newbuf)
|
|
||||||
// a stream used some token
|
|
||||||
atomic.AddInt32(&s.bucket, -int32(written))
|
|
||||||
stream.notifyReadEvent()
|
|
||||||
} else {
|
|
||||||
// data directed to a missing/closed stream, recycle the buffer immediately.
|
|
||||||
pool.PutBuffer(newbuf)
|
|
||||||
}
|
|
||||||
s.streamLock.Unlock()
|
|
||||||
} else {
|
|
||||||
s.notifyReadError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case cmdUPD: // a window update signal
|
|
||||||
if _, err := io.ReadFull(s.conn, updHdr[:]); err == nil {
|
|
||||||
s.streamLock.Lock()
|
|
||||||
if stream, ok := s.streams[sid]; ok {
|
|
||||||
stream.update(updHdr.Consumed(), updHdr.Window())
|
|
||||||
}
|
|
||||||
s.streamLock.Unlock()
|
|
||||||
} else {
|
|
||||||
s.notifyReadError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
s.notifyProtoError(ErrInvalidProtocol)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
s.notifyReadError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// keepalive sends NOP frame to peer to keep the connection alive, and detect dead peers
|
|
||||||
func (s *Session) keepalive() {
|
|
||||||
tickerPing := time.NewTicker(s.config.KeepAliveInterval)
|
|
||||||
tickerTimeout := time.NewTicker(s.config.KeepAliveTimeout)
|
|
||||||
defer tickerPing.Stop()
|
|
||||||
defer tickerTimeout.Stop()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-tickerPing.C:
|
|
||||||
s.writeFrameInternal(newFrame(byte(s.config.Version), cmdNOP, 0), tickerPing.C, CLSCTRL)
|
|
||||||
s.notifyBucket() // force a signal to the recvLoop
|
|
||||||
case <-tickerTimeout.C:
|
|
||||||
if !atomic.CompareAndSwapInt32(&s.dataReady, 1, 0) {
|
|
||||||
// recvLoop may block while bucket is 0, in this case,
|
|
||||||
// session should not be closed.
|
|
||||||
if atomic.LoadInt32(&s.bucket) > 0 {
|
|
||||||
s.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case <-s.die:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// shaperLoop implements a priority queue for write requests,
|
|
||||||
// some control messages are prioritized over data messages
|
|
||||||
func (s *Session) shaperLoop() {
|
|
||||||
var reqs shaperHeap
|
|
||||||
var next writeRequest
|
|
||||||
var chWrite chan writeRequest
|
|
||||||
var chShaper chan writeRequest
|
|
||||||
|
|
||||||
for {
|
|
||||||
// chWrite is not available until it has packet to send
|
|
||||||
if len(reqs) > 0 {
|
|
||||||
chWrite = s.writes
|
|
||||||
next = heap.Pop(&reqs).(writeRequest)
|
|
||||||
} else {
|
|
||||||
chWrite = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// control heap size, chShaper is not available until packets are less than maximum allowed
|
|
||||||
if len(reqs) >= maxShaperSize {
|
|
||||||
chShaper = nil
|
|
||||||
} else {
|
|
||||||
chShaper = s.shaper
|
|
||||||
}
|
|
||||||
|
|
||||||
// assertion on non nil
|
|
||||||
if chShaper == nil && chWrite == nil {
|
|
||||||
panic("both channel are nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-s.die:
|
|
||||||
return
|
|
||||||
case r := <-chShaper:
|
|
||||||
if chWrite != nil { // next is valid, reshape
|
|
||||||
heap.Push(&reqs, next)
|
|
||||||
}
|
|
||||||
heap.Push(&reqs, r)
|
|
||||||
case chWrite <- next:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendLoop sends frames to the underlying connection
|
|
||||||
func (s *Session) sendLoop() {
|
|
||||||
var buf []byte
|
|
||||||
var n int
|
|
||||||
var err error
|
|
||||||
var vec [][]byte // vector for writeBuffers
|
|
||||||
|
|
||||||
bw, ok := s.conn.(interface {
|
|
||||||
WriteBuffers(v [][]byte) (n int, err error)
|
|
||||||
})
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
buf = make([]byte, headerSize)
|
|
||||||
vec = make([][]byte, 2)
|
|
||||||
} else {
|
|
||||||
buf = make([]byte, (1<<16)+headerSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-s.die:
|
|
||||||
return
|
|
||||||
case request := <-s.writes:
|
|
||||||
buf[0] = request.frame.ver
|
|
||||||
buf[1] = request.frame.cmd
|
|
||||||
binary.LittleEndian.PutUint16(buf[2:], uint16(len(request.frame.data)))
|
|
||||||
binary.LittleEndian.PutUint32(buf[4:], request.frame.sid)
|
|
||||||
|
|
||||||
// support for scatter-gather I/O
|
|
||||||
if len(vec) > 0 {
|
|
||||||
vec[0] = buf[:headerSize]
|
|
||||||
vec[1] = request.frame.data
|
|
||||||
n, err = bw.WriteBuffers(vec)
|
|
||||||
} else {
|
|
||||||
copy(buf[headerSize:], request.frame.data)
|
|
||||||
n, err = s.conn.Write(buf[:headerSize+len(request.frame.data)])
|
|
||||||
}
|
|
||||||
|
|
||||||
n -= headerSize
|
|
||||||
if n < 0 {
|
|
||||||
n = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
result := writeResult{
|
|
||||||
n: n,
|
|
||||||
err: err,
|
|
||||||
}
|
|
||||||
|
|
||||||
request.result <- result
|
|
||||||
close(request.result)
|
|
||||||
|
|
||||||
// store conn error
|
|
||||||
if err != nil {
|
|
||||||
s.notifyWriteError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeControlFrame writes the control frame to the underlying connection
|
|
||||||
// and returns the number of bytes written if successful
|
|
||||||
func (s *Session) writeControlFrame(f Frame) (n int, err error) {
|
|
||||||
timer := time.NewTimer(openCloseTimeout)
|
|
||||||
defer timer.Stop()
|
|
||||||
|
|
||||||
return s.writeFrameInternal(f, timer.C, CLSCTRL)
|
|
||||||
}
|
|
||||||
|
|
||||||
// internal writeFrame version to support deadline used in keepalive
|
|
||||||
func (s *Session) writeFrameInternal(f Frame, deadline <-chan time.Time, class CLASSID) (int, error) {
|
|
||||||
req := writeRequest{
|
|
||||||
class: class,
|
|
||||||
frame: f,
|
|
||||||
seq: atomic.AddUint32(&s.requestID, 1),
|
|
||||||
result: make(chan writeResult, 1),
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case s.shaper <- req:
|
|
||||||
case <-s.die:
|
|
||||||
return 0, io.ErrClosedPipe
|
|
||||||
case <-s.chSocketWriteError:
|
|
||||||
return 0, s.socketWriteError.Load().(error)
|
|
||||||
case <-deadline:
|
|
||||||
return 0, ErrTimeout
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case result := <-req.result:
|
|
||||||
return result.n, result.err
|
|
||||||
case <-s.die:
|
|
||||||
return 0, io.ErrClosedPipe
|
|
||||||
case <-s.chSocketWriteError:
|
|
||||||
return 0, s.socketWriteError.Load().(error)
|
|
||||||
case <-deadline:
|
|
||||||
return 0, ErrTimeout
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
// MIT License
|
|
||||||
//
|
|
||||||
// Copyright (c) 2016-2017 xtaci
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in all
|
|
||||||
// copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
// SOFTWARE.
|
|
||||||
|
|
||||||
package smux
|
|
||||||
|
|
||||||
// _itimediff returns the time difference between two uint32 values.
|
|
||||||
// The result is a signed 32-bit integer representing the difference between 'later' and 'earlier'.
|
|
||||||
func _itimediff(later, earlier uint32) int32 {
|
|
||||||
return (int32)(later - earlier)
|
|
||||||
}
|
|
||||||
|
|
||||||
// shaperHeap is a min-heap of writeRequest.
|
|
||||||
// It orders writeRequests by class first, then by sequence number within the same class.
|
|
||||||
type shaperHeap []writeRequest
|
|
||||||
|
|
||||||
func (h shaperHeap) Len() int { return len(h) }
|
|
||||||
|
|
||||||
// Less determines the ordering of elements in the heap.
|
|
||||||
// Requests are ordered by their class first. If two requests have the same class,
|
|
||||||
// they are ordered by their sequence numbers.
|
|
||||||
func (h shaperHeap) Less(i, j int) bool {
|
|
||||||
if h[i].class != h[j].class {
|
|
||||||
return h[i].class < h[j].class
|
|
||||||
}
|
|
||||||
return _itimediff(h[j].seq, h[i].seq) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h shaperHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
|
||||||
func (h *shaperHeap) Push(x interface{}) { *h = append(*h, x.(writeRequest)) }
|
|
||||||
|
|
||||||
func (h *shaperHeap) Pop() interface{} {
|
|
||||||
old := *h
|
|
||||||
n := len(old)
|
|
||||||
x := old[n-1]
|
|
||||||
*h = old[0 : n-1]
|
|
||||||
return x
|
|
||||||
}
|
|
@ -1,615 +0,0 @@
|
|||||||
// MIT License
|
|
||||||
//
|
|
||||||
// Copyright (c) 2016-2017 xtaci
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in all
|
|
||||||
// copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
// SOFTWARE.
|
|
||||||
|
|
||||||
package smux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
// wrapper for GC
|
|
||||||
type Stream struct {
|
|
||||||
*stream
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream implements net.Conn
|
|
||||||
type stream struct {
|
|
||||||
id uint32 // Stream identifier
|
|
||||||
sess *Session
|
|
||||||
|
|
||||||
buffers [][]byte // the sequential buffers of stream
|
|
||||||
heads [][]byte // slice heads of the buffers above, kept for recycle
|
|
||||||
|
|
||||||
bufferLock sync.Mutex // Mutex to protect access to buffers
|
|
||||||
frameSize int // Maximum frame size for the stream
|
|
||||||
|
|
||||||
// notify a read event
|
|
||||||
chReadEvent chan struct{}
|
|
||||||
|
|
||||||
// flag the stream has closed
|
|
||||||
die chan struct{}
|
|
||||||
dieOnce sync.Once // Ensures die channel is closed only once
|
|
||||||
|
|
||||||
// FIN command
|
|
||||||
chFinEvent chan struct{}
|
|
||||||
finEventOnce sync.Once // Ensures chFinEvent is closed only once
|
|
||||||
|
|
||||||
// deadlines
|
|
||||||
readDeadline atomic.Value
|
|
||||||
writeDeadline atomic.Value
|
|
||||||
|
|
||||||
// per stream sliding window control
|
|
||||||
numRead uint32 // count num of bytes read
|
|
||||||
numWritten uint32 // count num of bytes written
|
|
||||||
incr uint32 // bytes sent since last window update
|
|
||||||
|
|
||||||
// UPD command
|
|
||||||
peerConsumed uint32 // num of bytes the peer has consumed
|
|
||||||
peerWindow uint32 // peer window, initialized to 256KB, updated by peer
|
|
||||||
chUpdate chan struct{} // notify of remote data consuming and window update
|
|
||||||
}
|
|
||||||
|
|
||||||
// newStream initializes and returns a new Stream.
|
|
||||||
func newStream(id uint32, frameSize int, sess *Session) *stream {
|
|
||||||
s := new(stream)
|
|
||||||
s.id = id
|
|
||||||
s.chReadEvent = make(chan struct{}, 1)
|
|
||||||
s.chUpdate = make(chan struct{}, 1)
|
|
||||||
s.frameSize = frameSize
|
|
||||||
s.sess = sess
|
|
||||||
s.die = make(chan struct{})
|
|
||||||
s.chFinEvent = make(chan struct{})
|
|
||||||
s.peerWindow = initialPeerWindow // set to initial window size
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID returns the stream's unique identifier.
|
|
||||||
func (s *stream) ID() uint32 {
|
|
||||||
return s.id
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads data from the stream into the provided buffer.
|
|
||||||
func (s *stream) Read(b []byte) (n int, err error) {
|
|
||||||
for {
|
|
||||||
n, err = s.tryRead(b)
|
|
||||||
if err == ErrWouldBlock {
|
|
||||||
if ew := s.waitRead(); ew != nil {
|
|
||||||
return 0, ew
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tryRead attempts to read data from the stream without blocking.
|
|
||||||
func (s *stream) tryRead(b []byte) (n int, err error) {
|
|
||||||
if s.sess.config.Version == 2 {
|
|
||||||
return s.tryReadv2(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(b) == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// A critical section to copy data from buffers to
|
|
||||||
s.bufferLock.Lock()
|
|
||||||
if len(s.buffers) > 0 {
|
|
||||||
n = copy(b, s.buffers[0])
|
|
||||||
s.buffers[0] = s.buffers[0][n:]
|
|
||||||
if len(s.buffers[0]) == 0 {
|
|
||||||
s.buffers[0] = nil
|
|
||||||
s.buffers = s.buffers[1:]
|
|
||||||
// full recycle
|
|
||||||
pool.PutBuffer(s.heads[0])
|
|
||||||
s.heads = s.heads[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.bufferLock.Unlock()
|
|
||||||
|
|
||||||
if n > 0 {
|
|
||||||
s.sess.returnTokens(n)
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-s.die:
|
|
||||||
return 0, io.EOF
|
|
||||||
default:
|
|
||||||
return 0, ErrWouldBlock
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tryReadv2 is the non-blocking version of Read for version 2 streams.
|
|
||||||
func (s *stream) tryReadv2(b []byte) (n int, err error) {
|
|
||||||
if len(b) == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var notifyConsumed uint32
|
|
||||||
s.bufferLock.Lock()
|
|
||||||
if len(s.buffers) > 0 {
|
|
||||||
n = copy(b, s.buffers[0])
|
|
||||||
s.buffers[0] = s.buffers[0][n:]
|
|
||||||
if len(s.buffers[0]) == 0 {
|
|
||||||
s.buffers[0] = nil
|
|
||||||
s.buffers = s.buffers[1:]
|
|
||||||
// full recycle
|
|
||||||
pool.PutBuffer(s.heads[0])
|
|
||||||
s.heads = s.heads[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// in an ideal environment:
|
|
||||||
// if more than half of buffer has consumed, send read ack to peer
|
|
||||||
// based on round-trip time of ACK, continous flowing data
|
|
||||||
// won't slow down due to waiting for ACK, as long as the
|
|
||||||
// consumer keeps on reading data.
|
|
||||||
//
|
|
||||||
// s.numRead == n implies that it's the initial reading
|
|
||||||
s.numRead += uint32(n)
|
|
||||||
s.incr += uint32(n)
|
|
||||||
|
|
||||||
// for initial reading, send window update
|
|
||||||
if s.incr >= uint32(s.sess.config.MaxStreamBuffer/2) || s.numRead == uint32(n) {
|
|
||||||
notifyConsumed = s.numRead
|
|
||||||
s.incr = 0 // reset couting for next window update
|
|
||||||
}
|
|
||||||
s.bufferLock.Unlock()
|
|
||||||
|
|
||||||
if n > 0 {
|
|
||||||
s.sess.returnTokens(n)
|
|
||||||
|
|
||||||
// send window update if necessary
|
|
||||||
if notifyConsumed > 0 {
|
|
||||||
err := s.sendWindowUpdate(notifyConsumed)
|
|
||||||
return n, err
|
|
||||||
} else {
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-s.die:
|
|
||||||
return 0, io.EOF
|
|
||||||
default:
|
|
||||||
return 0, ErrWouldBlock
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteTo implements io.WriteTo
|
|
||||||
// WriteTo writes data to w until there's no more data to write or when an error occurs.
|
|
||||||
// The return value n is the number of bytes written. Any error encountered during the write is also returned.
|
|
||||||
// WriteTo calls Write in a loop until there is no more data to write or when an error occurs.
|
|
||||||
// If the underlying stream is a v2 stream, it will send window update to peer when necessary.
|
|
||||||
// If the underlying stream is a v1 stream, it will not send window update to peer.
|
|
||||||
func (s *stream) WriteTo(w io.Writer) (n int64, err error) {
|
|
||||||
if s.sess.config.Version == 2 {
|
|
||||||
return s.writeTov2(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
var buf []byte
|
|
||||||
s.bufferLock.Lock()
|
|
||||||
if len(s.buffers) > 0 {
|
|
||||||
buf = s.buffers[0]
|
|
||||||
s.buffers = s.buffers[1:]
|
|
||||||
s.heads = s.heads[1:]
|
|
||||||
}
|
|
||||||
s.bufferLock.Unlock()
|
|
||||||
|
|
||||||
if buf != nil {
|
|
||||||
nw, ew := w.Write(buf)
|
|
||||||
// NOTE: WriteTo is a reader, so we need to return tokens here
|
|
||||||
s.sess.returnTokens(len(buf))
|
|
||||||
pool.PutBuffer(buf)
|
|
||||||
if nw > 0 {
|
|
||||||
n += int64(nw)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ew != nil {
|
|
||||||
return n, ew
|
|
||||||
}
|
|
||||||
} else if ew := s.waitRead(); ew != nil {
|
|
||||||
return n, ew
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check comments in WriteTo
|
|
||||||
func (s *stream) writeTov2(w io.Writer) (n int64, err error) {
|
|
||||||
for {
|
|
||||||
var notifyConsumed uint32
|
|
||||||
var buf []byte
|
|
||||||
s.bufferLock.Lock()
|
|
||||||
if len(s.buffers) > 0 {
|
|
||||||
buf = s.buffers[0]
|
|
||||||
s.buffers = s.buffers[1:]
|
|
||||||
s.heads = s.heads[1:]
|
|
||||||
}
|
|
||||||
s.numRead += uint32(len(buf))
|
|
||||||
s.incr += uint32(len(buf))
|
|
||||||
if s.incr >= uint32(s.sess.config.MaxStreamBuffer/2) || s.numRead == uint32(len(buf)) {
|
|
||||||
notifyConsumed = s.numRead
|
|
||||||
s.incr = 0
|
|
||||||
}
|
|
||||||
s.bufferLock.Unlock()
|
|
||||||
|
|
||||||
if buf != nil {
|
|
||||||
nw, ew := w.Write(buf)
|
|
||||||
// NOTE: WriteTo is a reader, so we need to return tokens here
|
|
||||||
s.sess.returnTokens(len(buf))
|
|
||||||
pool.PutBuffer(buf)
|
|
||||||
if nw > 0 {
|
|
||||||
n += int64(nw)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ew != nil {
|
|
||||||
return n, ew
|
|
||||||
}
|
|
||||||
|
|
||||||
if notifyConsumed > 0 {
|
|
||||||
if err := s.sendWindowUpdate(notifyConsumed); err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if ew := s.waitRead(); ew != nil {
|
|
||||||
return n, ew
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendWindowUpdate sends a window update frame to the peer.
|
|
||||||
func (s *stream) sendWindowUpdate(consumed uint32) error {
|
|
||||||
var timer *time.Timer
|
|
||||||
var deadline <-chan time.Time
|
|
||||||
if d, ok := s.readDeadline.Load().(time.Time); ok && !d.IsZero() {
|
|
||||||
timer = time.NewTimer(time.Until(d))
|
|
||||||
defer timer.Stop()
|
|
||||||
deadline = timer.C
|
|
||||||
}
|
|
||||||
|
|
||||||
frame := newFrame(byte(s.sess.config.Version), cmdUPD, s.id)
|
|
||||||
var hdr updHeader
|
|
||||||
binary.LittleEndian.PutUint32(hdr[:], consumed)
|
|
||||||
binary.LittleEndian.PutUint32(hdr[4:], uint32(s.sess.config.MaxStreamBuffer))
|
|
||||||
frame.data = hdr[:]
|
|
||||||
_, err := s.sess.writeFrameInternal(frame, deadline, CLSCTRL)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitRead blocks until a read event occurs or a deadline is reached.
|
|
||||||
func (s *stream) waitRead() error {
|
|
||||||
var timer *time.Timer
|
|
||||||
var deadline <-chan time.Time
|
|
||||||
if d, ok := s.readDeadline.Load().(time.Time); ok && !d.IsZero() {
|
|
||||||
timer = time.NewTimer(time.Until(d))
|
|
||||||
defer timer.Stop()
|
|
||||||
deadline = timer.C
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-s.chReadEvent: // notify some data has arrived, or closed
|
|
||||||
return nil
|
|
||||||
case <-s.chFinEvent:
|
|
||||||
// BUGFIX(xtaci): Fix for https://github.com/xtaci/smux/issues/82
|
|
||||||
s.bufferLock.Lock()
|
|
||||||
defer s.bufferLock.Unlock()
|
|
||||||
if len(s.buffers) > 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return io.EOF
|
|
||||||
case <-s.sess.chSocketReadError:
|
|
||||||
return s.sess.socketReadError.Load().(error)
|
|
||||||
case <-s.sess.chProtoError:
|
|
||||||
return s.sess.protoError.Load().(error)
|
|
||||||
case <-deadline:
|
|
||||||
return ErrTimeout
|
|
||||||
case <-s.die:
|
|
||||||
return io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write implements net.Conn
|
|
||||||
//
|
|
||||||
// Note that the behavior when multiple goroutines write concurrently is not deterministic,
|
|
||||||
// frames may interleave in random way.
|
|
||||||
func (s *stream) Write(b []byte) (n int, err error) {
|
|
||||||
if s.sess.config.Version == 2 {
|
|
||||||
return s.writeV2(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
var deadline <-chan time.Time
|
|
||||||
if d, ok := s.writeDeadline.Load().(time.Time); ok && !d.IsZero() {
|
|
||||||
timer := time.NewTimer(time.Until(d))
|
|
||||||
defer timer.Stop()
|
|
||||||
deadline = timer.C
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if stream has closed
|
|
||||||
select {
|
|
||||||
case <-s.chFinEvent: // passive closing
|
|
||||||
return 0, io.EOF
|
|
||||||
case <-s.die:
|
|
||||||
return 0, io.ErrClosedPipe
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// frame split and transmit
|
|
||||||
sent := 0
|
|
||||||
frame := newFrame(byte(s.sess.config.Version), cmdPSH, s.id)
|
|
||||||
bts := b
|
|
||||||
for len(bts) > 0 {
|
|
||||||
sz := len(bts)
|
|
||||||
if sz > s.frameSize {
|
|
||||||
sz = s.frameSize
|
|
||||||
}
|
|
||||||
frame.data = bts[:sz]
|
|
||||||
bts = bts[sz:]
|
|
||||||
n, err := s.sess.writeFrameInternal(frame, deadline, CLSDATA)
|
|
||||||
s.numWritten++
|
|
||||||
sent += n
|
|
||||||
if err != nil {
|
|
||||||
return sent, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sent, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeV2 writes data to the stream for version 2 streams.
|
|
||||||
func (s *stream) writeV2(b []byte) (n int, err error) {
|
|
||||||
// check empty input
|
|
||||||
if len(b) == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if stream has closed
|
|
||||||
select {
|
|
||||||
case <-s.chFinEvent:
|
|
||||||
return 0, io.EOF
|
|
||||||
case <-s.die:
|
|
||||||
return 0, io.ErrClosedPipe
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// create write deadline timer
|
|
||||||
var deadline <-chan time.Time
|
|
||||||
if d, ok := s.writeDeadline.Load().(time.Time); ok && !d.IsZero() {
|
|
||||||
timer := time.NewTimer(time.Until(d))
|
|
||||||
defer timer.Stop()
|
|
||||||
deadline = timer.C
|
|
||||||
}
|
|
||||||
|
|
||||||
// frame split and transmit process
|
|
||||||
sent := 0
|
|
||||||
frame := newFrame(byte(s.sess.config.Version), cmdPSH, s.id)
|
|
||||||
|
|
||||||
for {
|
|
||||||
// per stream sliding window control
|
|
||||||
// [.... [consumed... numWritten] ... win... ]
|
|
||||||
// [.... [consumed...................+rmtwnd]]
|
|
||||||
var bts []byte
|
|
||||||
// note:
|
|
||||||
// even if uint32 overflow, this math still works:
|
|
||||||
// eg1: uint32(0) - uint32(math.MaxUint32) = 1
|
|
||||||
// eg2: int32(uint32(0) - uint32(1)) = -1
|
|
||||||
//
|
|
||||||
// basicially, you can take it as a MODULAR ARITHMETIC
|
|
||||||
inflight := int32(atomic.LoadUint32(&s.numWritten) - atomic.LoadUint32(&s.peerConsumed))
|
|
||||||
if inflight < 0 { // security check for malformed data
|
|
||||||
return 0, ErrConsumed
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure you understand 'win' is calculated in modular arithmetic(2^32(4GB))
|
|
||||||
win := int32(atomic.LoadUint32(&s.peerWindow)) - inflight
|
|
||||||
|
|
||||||
if win > 0 {
|
|
||||||
// determine how many bytes to send
|
|
||||||
if win > int32(len(b)) {
|
|
||||||
bts = b
|
|
||||||
b = nil
|
|
||||||
} else {
|
|
||||||
bts = b[:win]
|
|
||||||
b = b[win:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// frame split and transmit
|
|
||||||
for len(bts) > 0 {
|
|
||||||
// splitting frame
|
|
||||||
sz := len(bts)
|
|
||||||
if sz > s.frameSize {
|
|
||||||
sz = s.frameSize
|
|
||||||
}
|
|
||||||
frame.data = bts[:sz]
|
|
||||||
bts = bts[sz:]
|
|
||||||
|
|
||||||
// transmit of frame
|
|
||||||
n, err := s.sess.writeFrameInternal(frame, deadline, CLSDATA)
|
|
||||||
atomic.AddUint32(&s.numWritten, uint32(sz))
|
|
||||||
sent += n
|
|
||||||
if err != nil {
|
|
||||||
return sent, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there is any data left to be sent,
|
|
||||||
// wait until stream closes, window changes or deadline reached
|
|
||||||
// this blocking behavior will back propagate flow control to upper layer.
|
|
||||||
if len(b) > 0 {
|
|
||||||
select {
|
|
||||||
case <-s.chFinEvent:
|
|
||||||
return 0, io.EOF
|
|
||||||
case <-s.die:
|
|
||||||
return sent, io.ErrClosedPipe
|
|
||||||
case <-deadline:
|
|
||||||
return sent, ErrTimeout
|
|
||||||
case <-s.sess.chSocketWriteError:
|
|
||||||
return sent, s.sess.socketWriteError.Load().(error)
|
|
||||||
case <-s.chUpdate: // notify of remote data consuming and window update
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return sent, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements net.Conn
|
|
||||||
func (s *stream) Close() error {
|
|
||||||
var once bool
|
|
||||||
var err error
|
|
||||||
s.dieOnce.Do(func() {
|
|
||||||
close(s.die)
|
|
||||||
once = true
|
|
||||||
})
|
|
||||||
|
|
||||||
if once {
|
|
||||||
// send FIN in order
|
|
||||||
f := newFrame(byte(s.sess.config.Version), cmdFIN, s.id)
|
|
||||||
|
|
||||||
timer := time.NewTimer(openCloseTimeout)
|
|
||||||
defer timer.Stop()
|
|
||||||
|
|
||||||
_, err = s.sess.writeFrameInternal(f, timer.C, CLSDATA)
|
|
||||||
s.sess.streamClosed(s.id)
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
return io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDieCh returns a readonly chan which can be readable
|
|
||||||
// when the stream is to be closed.
|
|
||||||
func (s *stream) GetDieCh() <-chan struct{} {
|
|
||||||
return s.die
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetReadDeadline sets the read deadline as defined by
|
|
||||||
// net.Conn.SetReadDeadline.
|
|
||||||
// A zero time value disables the deadline.
|
|
||||||
func (s *stream) SetReadDeadline(t time.Time) error {
|
|
||||||
s.readDeadline.Store(t)
|
|
||||||
s.notifyReadEvent()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetWriteDeadline sets the write deadline as defined by
|
|
||||||
// net.Conn.SetWriteDeadline.
|
|
||||||
// A zero time value disables the deadline.
|
|
||||||
func (s *stream) SetWriteDeadline(t time.Time) error {
|
|
||||||
s.writeDeadline.Store(t)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDeadline sets both read and write deadlines as defined by
|
|
||||||
// net.Conn.SetDeadline.
|
|
||||||
// A zero time value disables the deadlines.
|
|
||||||
func (s *stream) SetDeadline(t time.Time) error {
|
|
||||||
if err := s.SetReadDeadline(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := s.SetWriteDeadline(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// session closes
|
|
||||||
func (s *stream) sessionClose() { s.dieOnce.Do(func() { close(s.die) }) }
|
|
||||||
|
|
||||||
// LocalAddr satisfies net.Conn interface
|
|
||||||
func (s *stream) LocalAddr() net.Addr {
|
|
||||||
if ts, ok := s.sess.conn.(interface {
|
|
||||||
LocalAddr() net.Addr
|
|
||||||
}); ok {
|
|
||||||
return ts.LocalAddr()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoteAddr satisfies net.Conn interface
|
|
||||||
func (s *stream) RemoteAddr() net.Addr {
|
|
||||||
if ts, ok := s.sess.conn.(interface {
|
|
||||||
RemoteAddr() net.Addr
|
|
||||||
}); ok {
|
|
||||||
return ts.RemoteAddr()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// pushBytes append buf to buffers
|
|
||||||
func (s *stream) pushBytes(buf []byte) (written int, err error) {
|
|
||||||
s.bufferLock.Lock()
|
|
||||||
s.buffers = append(s.buffers, buf)
|
|
||||||
s.heads = append(s.heads, buf)
|
|
||||||
s.bufferLock.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// recycleTokens transform remaining bytes to tokens(will truncate buffer)
|
|
||||||
func (s *stream) recycleTokens() (n int) {
|
|
||||||
s.bufferLock.Lock()
|
|
||||||
for k := range s.buffers {
|
|
||||||
n += len(s.buffers[k])
|
|
||||||
pool.PutBuffer(s.heads[k])
|
|
||||||
}
|
|
||||||
s.buffers = nil
|
|
||||||
s.heads = nil
|
|
||||||
s.bufferLock.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// notify read event
|
|
||||||
func (s *stream) notifyReadEvent() {
|
|
||||||
select {
|
|
||||||
case s.chReadEvent <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update command
|
|
||||||
func (s *stream) update(consumed uint32, window uint32) {
|
|
||||||
atomic.StoreUint32(&s.peerConsumed, consumed)
|
|
||||||
atomic.StoreUint32(&s.peerWindow, window)
|
|
||||||
select {
|
|
||||||
case s.chUpdate <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mark this stream has been closed in protocol
|
|
||||||
func (s *stream) fin() {
|
|
||||||
s.finEventOnce.Do(func() {
|
|
||||||
close(s.chFinEvent)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package sockopt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Options is the options struct.
|
|
||||||
type Options struct {
|
|
||||||
bindIface *net.Interface
|
|
||||||
reuseAddr bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Option is the function paramater.
|
|
||||||
type Option func(opts *Options)
|
|
||||||
|
|
||||||
// Bind sets the bind interface option.
|
|
||||||
func Bind(intf *net.Interface) Option { return func(opts *Options) { opts.bindIface = intf } }
|
|
||||||
|
|
||||||
// ReuseAddr sets the reuse addr option.
|
|
||||||
func ReuseAddr() Option { return func(opts *Options) { opts.reuseAddr = true } }
|
|
||||||
|
|
||||||
// Control returns a control function for the net.Dialer and net.ListenConfig.
|
|
||||||
func Control(opts ...Option) func(network, address string, c syscall.RawConn) error {
|
|
||||||
option := &Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(option)
|
|
||||||
}
|
|
||||||
|
|
||||||
return control(option)
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package sockopt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func control(opt *Options) func(network, address string, c syscall.RawConn) error {
|
|
||||||
return func(network, address string, c syscall.RawConn) (err error) {
|
|
||||||
return c.Control(func(fd uintptr) {
|
|
||||||
|
|
||||||
if opt.bindIface != nil {
|
|
||||||
switch network {
|
|
||||||
case "tcp4", "udp4":
|
|
||||||
unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, opt.bindIface.Index)
|
|
||||||
case "tcp6", "udp6":
|
|
||||||
unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, opt.bindIface.Index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if opt.reuseAddr {
|
|
||||||
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
|
|
||||||
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package sockopt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func control(opt *Options) func(network, address string, c syscall.RawConn) error {
|
|
||||||
return func(network, address string, c syscall.RawConn) (err error) {
|
|
||||||
return c.Control(func(fd uintptr) {
|
|
||||||
|
|
||||||
if opt.bindIface != nil {
|
|
||||||
unix.BindToDevice(int(fd), opt.bindIface.Name)
|
|
||||||
}
|
|
||||||
if opt.reuseAddr {
|
|
||||||
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
|
|
||||||
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
//go:build !linux && !darwin
|
|
||||||
|
|
||||||
package sockopt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
func control(opt *Options) func(string, string, syscall.RawConn) error { return nil }
|
|
206
proxy/conn.go
206
proxy/conn.go
@ -1,206 +0,0 @@
|
|||||||
package proxy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// TCPBufSize is the size of tcp buffer.
|
|
||||||
TCPBufSize = 32 << 10
|
|
||||||
|
|
||||||
// UDPBufSize is the size of udp buffer.
|
|
||||||
UDPBufSize = 2 << 10
|
|
||||||
)
|
|
||||||
|
|
||||||
// Conn is a connection with buffered reader.
|
|
||||||
type Conn struct {
|
|
||||||
r *bufio.Reader
|
|
||||||
net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConn returns a new conn.
|
|
||||||
func NewConn(c net.Conn) *Conn {
|
|
||||||
if conn, ok := c.(*Conn); ok {
|
|
||||||
return conn
|
|
||||||
}
|
|
||||||
return &Conn{pool.GetBufReader(c), c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reader returns the internal bufio.Reader.
|
|
||||||
func (c *Conn) Reader() *bufio.Reader { return c.r }
|
|
||||||
func (c *Conn) Read(p []byte) (int, error) { return c.r.Read(p) }
|
|
||||||
|
|
||||||
// Peek returns the next n bytes without advancing the reader.
|
|
||||||
func (c *Conn) Peek(n int) ([]byte, error) { return c.r.Peek(n) }
|
|
||||||
|
|
||||||
// WriteTo implements io.WriterTo.
|
|
||||||
func (c *Conn) WriteTo(w io.Writer) (n int64, err error) { return c.r.WriteTo(w) }
|
|
||||||
|
|
||||||
// Close closes the Conn.
|
|
||||||
func (c *Conn) Close() error {
|
|
||||||
pool.PutBufReader(c.r)
|
|
||||||
return c.Conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Relay relays between left and right.
|
|
||||||
func Relay(left, right net.Conn) error {
|
|
||||||
var err, err1 error
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
var wait = 5 * time.Second
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
_, err1 = Copy(right, left)
|
|
||||||
right.SetReadDeadline(time.Now().Add(wait)) // unblock read on right
|
|
||||||
}()
|
|
||||||
|
|
||||||
_, err = Copy(left, right)
|
|
||||||
left.SetReadDeadline(time.Now().Add(wait)) // unblock read on left
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
if err1 != nil && !errors.Is(err1, os.ErrDeadlineExceeded) {
|
|
||||||
return err1
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && !errors.Is(err, os.ErrDeadlineExceeded) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy copies from src to dst.
|
|
||||||
func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
|
|
||||||
dst = underlyingWriter(dst)
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "linux", "windows", "dragonfly", "freebsd", "solaris":
|
|
||||||
if _, ok := dst.(*net.TCPConn); ok && worthTry(src) {
|
|
||||||
if wt, ok := src.(io.WriterTo); ok {
|
|
||||||
return wt.WriteTo(dst)
|
|
||||||
}
|
|
||||||
if rt, ok := dst.(io.ReaderFrom); ok {
|
|
||||||
return rt.ReadFrom(src)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return CopyBuffer(dst, src)
|
|
||||||
}
|
|
||||||
|
|
||||||
func underlyingWriter(c io.Writer) io.Writer {
|
|
||||||
if wrap, ok := c.(*Conn); ok {
|
|
||||||
return wrap.Conn
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func worthTry(src io.Reader) bool {
|
|
||||||
switch v := src.(type) {
|
|
||||||
case *net.TCPConn, *net.UnixConn:
|
|
||||||
return true
|
|
||||||
case *io.LimitedReader:
|
|
||||||
return worthTry(v.R)
|
|
||||||
case *Conn:
|
|
||||||
return worthTry(v.Conn)
|
|
||||||
case *os.File:
|
|
||||||
fi, err := v.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return fi.Mode().IsRegular()
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyN copies n bytes (or until an error) from src to dst.
|
|
||||||
func CopyN(dst io.Writer, src io.Reader, n int64) (written int64, err error) {
|
|
||||||
written, err = Copy(dst, io.LimitReader(src, n))
|
|
||||||
if written == n {
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
if written < n && err == nil {
|
|
||||||
// src stopped early; must have been EOF.
|
|
||||||
err = io.EOF
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyBuffer copies from src to dst with a userspace buffer.
|
|
||||||
func CopyBuffer(dst io.Writer, src io.Reader) (written int64, err error) {
|
|
||||||
size := TCPBufSize
|
|
||||||
if l, ok := src.(*io.LimitedReader); ok && int64(size) > l.N {
|
|
||||||
if l.N < 1 {
|
|
||||||
size = 1
|
|
||||||
} else {
|
|
||||||
size = int(l.N)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := pool.GetBuffer(size)
|
|
||||||
defer pool.PutBuffer(buf)
|
|
||||||
|
|
||||||
for {
|
|
||||||
nr, er := src.Read(buf)
|
|
||||||
if nr > 0 {
|
|
||||||
nw, ew := dst.Write(buf[0:nr])
|
|
||||||
if nw > 0 {
|
|
||||||
written += int64(nw)
|
|
||||||
}
|
|
||||||
if ew != nil {
|
|
||||||
err = ew
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if nr != nw {
|
|
||||||
err = io.ErrShortWrite
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if er != nil {
|
|
||||||
if er != io.EOF {
|
|
||||||
err = er
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
if writeTo != nil {
|
|
||||||
addr = writeTo
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dst.WriteTo(buf[:n], addr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,49 +3,34 @@ package proxy
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
"github.com/nadoo/glider/common/log"
|
||||||
// ErrNotSupported indicates that the operation is not supported
|
|
||||||
ErrNotSupported = errors.New("not supported")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Dialer is used to create connection.
|
// Dialer is used to create connection.
|
||||||
type Dialer interface {
|
type Dialer interface {
|
||||||
TCPDialer
|
|
||||||
UDPDialer
|
|
||||||
}
|
|
||||||
|
|
||||||
// TCPDialer is used to create tcp connection.
|
|
||||||
type TCPDialer interface {
|
|
||||||
// Addr is the dialer's addr
|
// Addr is the dialer's addr
|
||||||
Addr() string
|
Addr() string
|
||||||
|
|
||||||
// Dial connects to the given address
|
// Dial connects to the given address
|
||||||
Dial(network, addr string) (c net.Conn, err error)
|
Dial(network, addr string) (c net.Conn, err error)
|
||||||
}
|
|
||||||
|
|
||||||
// UDPDialer is used to create udp PacketConn.
|
|
||||||
type UDPDialer interface {
|
|
||||||
// Addr is the dialer's addr
|
|
||||||
Addr() string
|
|
||||||
|
|
||||||
// DialUDP connects to the given address
|
// DialUDP connects to the given address
|
||||||
DialUDP(network, addr string) (pc net.PacketConn, err error)
|
DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialerCreator is a function to create dialers.
|
// DialerCreator is a function to create dialers.
|
||||||
type DialerCreator func(s string, dialer Dialer) (Dialer, error)
|
type DialerCreator func(s string, dialer Dialer) (Dialer, error)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dialerCreators = make(map[string]DialerCreator)
|
dialerMap = make(map[string]DialerCreator)
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegisterDialer is used to register a dialer.
|
// RegisterDialer is used to register a dialer.
|
||||||
func RegisterDialer(name string, c DialerCreator) {
|
func RegisterDialer(name string, c DialerCreator) {
|
||||||
dialerCreators[strings.ToLower(name)] = c
|
dialerMap[name] = c
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialerFromURL calls the registered creator to create dialers.
|
// DialerFromURL calls the registered creator to create dialers.
|
||||||
@ -55,25 +40,16 @@ func DialerFromURL(s string, dialer Dialer) (Dialer, error) {
|
|||||||
return nil, errors.New("DialerFromURL: dialer cannot be nil")
|
return nil, errors.New("DialerFromURL: dialer cannot be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(s, "://") {
|
u, err := url.Parse(s)
|
||||||
s = s + "://"
|
if err != nil {
|
||||||
|
log.F("parse err: %s", err)
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
scheme := s[:strings.Index(s, ":")]
|
c, ok := dialerMap[strings.ToLower(u.Scheme)]
|
||||||
c, ok := dialerCreators[strings.ToLower(scheme)]
|
|
||||||
if ok {
|
if ok {
|
||||||
return c(s, dialer)
|
return c(s, dialer)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("unknown scheme '" + scheme + "'")
|
return nil, errors.New("unknown scheme '" + u.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, " ")
|
|
||||||
}
|
}
|
||||||
|
111
proxy/direct.go
111
proxy/direct.go
@ -1,68 +1,54 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/sockopt"
|
"github.com/nadoo/glider/common/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Direct proxy.
|
// Direct proxy
|
||||||
type Direct struct {
|
type Direct struct {
|
||||||
iface *net.Interface // interface specified by user
|
iface *net.Interface // interface specified by user
|
||||||
ip net.IP
|
ip net.IP
|
||||||
dialTimeout time.Duration
|
|
||||||
relayTimeout time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
// Default dialer
|
||||||
RegisterDialer("direct", NewDirectDialer)
|
var Default = &Direct{}
|
||||||
}
|
|
||||||
|
|
||||||
// NewDirect returns a Direct dialer.
|
// NewDirect returns a Direct dialer
|
||||||
func NewDirect(intface string, dialTimeout, relayTimeout time.Duration) (*Direct, error) {
|
func NewDirect(intface string) (*Direct, error) {
|
||||||
d := &Direct{dialTimeout: dialTimeout, relayTimeout: relayTimeout}
|
if intface == "" {
|
||||||
|
return &Direct{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(intface)
|
||||||
|
if ip != nil {
|
||||||
|
return &Direct{ip: ip}, nil
|
||||||
|
}
|
||||||
|
|
||||||
if intface != "" {
|
|
||||||
if addr, err := netip.ParseAddr(intface); err == nil {
|
|
||||||
d.ip = addr.AsSlice()
|
|
||||||
} else {
|
|
||||||
iface, err := net.InterfaceByName(intface)
|
iface, err := net.InterfaceByName(intface)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New(err.Error() + ": " + intface)
|
return nil, errors.New(err.Error() + ": " + intface)
|
||||||
}
|
}
|
||||||
d.iface = iface
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return d, nil
|
return &Direct{iface: iface}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDirectDialer returns a direct dialer.
|
// Addr returns forwarder's address
|
||||||
func NewDirectDialer(s string, d Dialer) (Dialer, error) {
|
|
||||||
if d == nil {
|
|
||||||
return NewDirect("", time.Duration(3)*time.Second, time.Duration(3)*time.Second)
|
|
||||||
}
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Addr returns forwarder's address.
|
|
||||||
func (d *Direct) Addr() string { return "DIRECT" }
|
func (d *Direct) Addr() string { return "DIRECT" }
|
||||||
|
|
||||||
// Dial connects to the address addr on the network net
|
// Dial connects to the address addr on the network net
|
||||||
func (d *Direct) Dial(network, addr string) (c net.Conn, err error) {
|
func (d *Direct) Dial(network, addr string) (c net.Conn, err error) {
|
||||||
if d.iface == nil || d.ip != nil {
|
if d.iface == nil || d.ip != nil {
|
||||||
c, err = d.dial(network, addr, d.ip)
|
c, err = dial(network, addr, d.ip)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ip := range d.IFaceIPs() {
|
for _, ip := range d.IFaceIPs() {
|
||||||
c, err = d.dial(network, addr, ip)
|
c, err = dial(network, addr, ip)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
d.ip = ip
|
d.ip = ip
|
||||||
break
|
break
|
||||||
@ -77,7 +63,11 @@ func (d *Direct) Dial(network, addr string) (c net.Conn, err error) {
|
|||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Direct) dial(network, addr string, localIP net.IP) (net.Conn, error) {
|
func dial(network, addr string, localIP net.IP) (net.Conn, error) {
|
||||||
|
if network == "uot" {
|
||||||
|
network = "udp"
|
||||||
|
}
|
||||||
|
|
||||||
var la net.Addr
|
var la net.Addr
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
@ -86,11 +76,7 @@ func (d *Direct) dial(network, addr string, localIP net.IP) (net.Conn, error) {
|
|||||||
la = &net.UDPAddr{IP: localIP}
|
la = &net.UDPAddr{IP: localIP}
|
||||||
}
|
}
|
||||||
|
|
||||||
dialer := &net.Dialer{LocalAddr: la, Timeout: d.dialTimeout}
|
dialer := &net.Dialer{LocalAddr: la}
|
||||||
if d.iface != nil {
|
|
||||||
dialer.Control = sockopt.Control(sockopt.Bind(d.iface))
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := dialer.Dial(network, addr)
|
c, err := dialer.Dial(network, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -100,52 +86,37 @@ func (d *Direct) dial(network, addr string, localIP net.IP) (net.Conn, error) {
|
|||||||
c.SetKeepAlive(true)
|
c.SetKeepAlive(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.relayTimeout > 0 {
|
|
||||||
c.SetDeadline(time.Now().Add(d.relayTimeout))
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP connects to the given address.
|
// DialUDP connects to the given address
|
||||||
func (d *Direct) DialUDP(network, addr string) (net.PacketConn, error) {
|
func (d *Direct) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||||
var la string
|
// TODO: support specifying local interface
|
||||||
|
la := ""
|
||||||
if d.ip != nil {
|
if d.ip != nil {
|
||||||
la = net.JoinHostPort(d.ip.String(), "0")
|
la = d.ip.String() + ":0"
|
||||||
}
|
}
|
||||||
|
|
||||||
lc := &net.ListenConfig{}
|
pc, err := net.ListenPacket(network, la)
|
||||||
if d.iface != nil {
|
if err != nil {
|
||||||
lc.Control = sockopt.Control(sockopt.Bind(d.iface))
|
log.F("ListenPacket error: %s", err)
|
||||||
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return lc.ListenPacket(context.Background(), network, la)
|
uAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||||
|
return pc, uAddr, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// IFaceIPs returns ip addresses according to the specified interface.
|
// IFaceIPs returns ip addresses according to the specified interface
|
||||||
func (d *Direct) IFaceIPs() (ips []net.IP) {
|
func (d *Direct) IFaceIPs() (ips []net.IP) {
|
||||||
ipNets, err := d.iface.Addrs()
|
ipnets, err := d.iface.Addrs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, ipNet := range ipNets {
|
|
||||||
ips = append(ips, ipNet.(*net.IPNet).IP) //!ip.IsLinkLocalUnicast()
|
for _, ipnet := range ipnets {
|
||||||
|
ips = append(ips, ipnet.(*net.IPNet).IP) //!ip.IsLinkLocalUnicast()
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
AddUsage("direct", `
|
|
||||||
Direct scheme:
|
|
||||||
direct://
|
|
||||||
|
|
||||||
Only needed when you want to specify the outgoing interface:
|
|
||||||
glider -verbose -listen :8443 -forward direct://#interface=eth0
|
|
||||||
|
|
||||||
Or load balance multiple interfaces directly:
|
|
||||||
glider -verbose -listen :8443 -forward direct://#interface=eth0 -forward direct://#interface=eth1 -strategy rr
|
|
||||||
|
|
||||||
Or you can use the high availability mode:
|
|
||||||
glider -verbose -listen :8443 -forward direct://#interface=eth0&priority=100 -forward direct://#interface=eth1&priority=200 -strategy ha
|
|
||||||
`)
|
|
||||||
}
|
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
"github.com/nadoo/glider/common/conn"
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
"github.com/nadoo/glider/common/log"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,16 +33,14 @@ func (s *HTTP) Dial(network, addr string) (net.Conn, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := pool.GetBytesBuffer()
|
var buf bytes.Buffer
|
||||||
defer pool.PutBytesBuffer(buf)
|
|
||||||
|
|
||||||
buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n")
|
buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n")
|
||||||
buf.WriteString("Host: " + addr + "\r\n")
|
buf.WriteString("Host: " + addr + "\r\n")
|
||||||
buf.WriteString("Proxy-Connection: Keep-Alive\r\n")
|
buf.WriteString("Proxy-Connection: Keep-Alive\r\n")
|
||||||
|
|
||||||
if s.user != "" && s.password != "" {
|
if s.user != "" && s.password != "" {
|
||||||
auth := s.user + ":" + s.password
|
auth := s.user + ":" + s.password
|
||||||
buf.WriteString("Proxy-Authorization: Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) + "\r\n")
|
buf.Write([]byte("Proxy-Authorization: Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) + "\r\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// header ended
|
// header ended
|
||||||
@ -51,7 +50,7 @@ func (s *HTTP) Dial(network, addr string) (net.Conn, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
c := proxy.NewConn(rc)
|
c := conn.NewConn(rc)
|
||||||
tpr := textproto.NewReader(c.Reader())
|
tpr := textproto.NewReader(c.Reader())
|
||||||
line, err := tpr.ReadLine()
|
line, err := tpr.ReadLine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -77,6 +76,6 @@ func (s *HTTP) Dial(network, addr string) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
// DialUDP connects to the given address via the proxy.
|
||||||
func (s *HTTP) DialUDP(network, addr string) (pc net.PacketConn, err error) {
|
func (s *HTTP) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
|
||||||
return nil, proxy.ErrNotSupported
|
return nil, nil, errors.New("http client does not support udp")
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,13 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"io"
|
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
"github.com/nadoo/glider/common/log"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -81,17 +81,17 @@ func cleanHeaders(header textproto.MIMEHeader) {
|
|||||||
header.Del("Upgrade")
|
header.Del("Upgrade")
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeStartLine(w io.Writer, s1, s2, s3 string) {
|
func writeStartLine(buf *bytes.Buffer, s1, s2, s3 string) {
|
||||||
io.WriteString(w, s1+" "+s2+" "+s3+"\r\n")
|
buf.WriteString(s1 + " " + s2 + " " + s3 + "\r\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeHeaders(w io.Writer, header textproto.MIMEHeader) {
|
func writeHeaders(buf *bytes.Buffer, header textproto.MIMEHeader) {
|
||||||
for key, values := range header {
|
for key, values := range header {
|
||||||
for _, v := range values {
|
for _, v := range values {
|
||||||
io.WriteString(w, key+": "+v+"\r\n")
|
buf.WriteString(key + ": " + v + "\r\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
io.WriteString(w, "\r\n")
|
buf.WriteString("\r\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractUserPass(auth string) (username, password string, ok bool) {
|
func extractUserPass(auth string) (username, password string, ok bool) {
|
||||||
@ -112,10 +112,3 @@ func extractUserPass(auth string) (username, password string, ok bool) {
|
|||||||
|
|
||||||
return s[:idx], s[idx+1:], true
|
return s[:idx], s[idx+1:], true
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
proxy.AddUsage("http", `
|
|
||||||
Http scheme:
|
|
||||||
http://[user:pass@]host:port
|
|
||||||
`)
|
|
||||||
}
|
|
||||||
|
@ -3,16 +3,16 @@ package http
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"errors"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
"github.com/nadoo/glider/common/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Methods are http methods from rfc.
|
// Methods are http methods from rfc.
|
||||||
// https://www.rfc-editor.org/rfc/rfc2616, http methods must be uppercase
|
// https://www.ietf.org/rfc/rfc2616.txt, http methods must be uppercase
|
||||||
var Methods = [...][]byte{
|
var Methods = [...][]byte{
|
||||||
[]byte("GET"),
|
[]byte("GET"),
|
||||||
[]byte("POST"),
|
[]byte("POST"),
|
||||||
@ -46,7 +46,7 @@ func parseRequest(r *bufio.Reader) (*request, error) {
|
|||||||
|
|
||||||
method, uri, proto, ok := parseStartLine(line)
|
method, uri, proto, ok := parseStartLine(line)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("error in parseStartLine: %s", line)
|
return nil, errors.New("error in parseStartLine")
|
||||||
}
|
}
|
||||||
|
|
||||||
header, err := tpr.ReadMIMEHeader()
|
header, err := tpr.ReadMIMEHeader()
|
||||||
@ -60,26 +60,14 @@ func parseRequest(r *bufio.Reader) (*request, error) {
|
|||||||
cleanHeaders(header)
|
cleanHeaders(header)
|
||||||
header.Set("Connection", "close")
|
header.Set("Connection", "close")
|
||||||
|
|
||||||
// https://github.com/golang/go/blob/dcf0929de6a12103a8fd7097abd6e797188c366d/src/net/http/request.go#L1047
|
|
||||||
justAuthority := method == "CONNECT" && !strings.HasPrefix(uri, "/")
|
|
||||||
if justAuthority {
|
|
||||||
uri = "http://" + uri
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := url.ParseRequestURI(uri)
|
u, err := url.ParseRequestURI(uri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[http] parse request url error: %s, uri: %s", err, uri)
|
log.F("[http] parse request url error: %s, uri: %s", err, uri)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if justAuthority {
|
var tgt = u.Host
|
||||||
// Strip the bogus "http://" back off.
|
if !strings.Contains(u.Host, ":") {
|
||||||
u.Scheme = ""
|
|
||||||
uri = uri[7:]
|
|
||||||
}
|
|
||||||
|
|
||||||
tgt := u.Host
|
|
||||||
if !strings.Contains(tgt, ":") {
|
|
||||||
tgt += ":80"
|
tgt += ":80"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,10 +82,12 @@ func parseRequest(r *bufio.Reader) (*request, error) {
|
|||||||
|
|
||||||
if u.IsAbs() {
|
if u.IsAbs() {
|
||||||
req.absuri = u.String()
|
req.absuri = u.String()
|
||||||
u.Scheme, u.Host = "", ""
|
u.Scheme = ""
|
||||||
|
u.Host = ""
|
||||||
req.ruri = u.String()
|
req.ruri = u.String()
|
||||||
} else {
|
} else {
|
||||||
req.ruri = u.String()
|
req.ruri = u.String()
|
||||||
|
|
||||||
base, err := url.Parse("http://" + header.Get("Host"))
|
base, err := url.Parse("http://" + header.Get("Host"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -109,12 +99,16 @@ func parseRequest(r *bufio.Reader) (*request, error) {
|
|||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *request) WriteBuf(buf *bytes.Buffer) {
|
func (r *request) Marshal() []byte {
|
||||||
writeStartLine(buf, r.method, r.ruri, r.proto)
|
var buf bytes.Buffer
|
||||||
writeHeaders(buf, r.header)
|
writeStartLine(&buf, r.method, r.ruri, r.proto)
|
||||||
|
writeHeaders(&buf, r.header)
|
||||||
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *request) WriteAbsBuf(buf *bytes.Buffer) {
|
func (r *request) MarshalAbs() []byte {
|
||||||
writeStartLine(buf, r.method, r.absuri, r.proto)
|
var buf bytes.Buffer
|
||||||
writeHeaders(buf, r.header)
|
writeStartLine(&buf, r.method, r.absuri, r.proto)
|
||||||
|
writeHeaders(&buf, r.header)
|
||||||
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
"github.com/nadoo/glider/common/conn"
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
"github.com/nadoo/glider/common/log"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ func NewHTTPServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
|||||||
func (s *HTTP) ListenAndServe() {
|
func (s *HTTP) ListenAndServe() {
|
||||||
l, err := net.Listen("tcp", s.addr)
|
l, err := net.Listen("tcp", s.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("[http] failed to listen on %s: %v", s.addr, err)
|
log.F("[http] failed to listen on %s: %v", s.addr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer l.Close()
|
defer l.Close()
|
||||||
@ -42,33 +43,37 @@ func (s *HTTP) ListenAndServe() {
|
|||||||
|
|
||||||
// Serve serves a connection.
|
// Serve serves a connection.
|
||||||
func (s *HTTP) Serve(cc net.Conn) {
|
func (s *HTTP) Serve(cc net.Conn) {
|
||||||
if c, ok := cc.(*net.TCPConn); ok {
|
defer cc.Close()
|
||||||
c.SetKeepAlive(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
c := proxy.NewConn(cc)
|
var c *conn.Conn
|
||||||
defer c.Close()
|
switch ccc := cc.(type) {
|
||||||
|
case *net.TCPConn:
|
||||||
|
ccc.SetKeepAlive(true)
|
||||||
|
c = conn.NewConn(ccc)
|
||||||
|
case *conn.Conn:
|
||||||
|
c = ccc
|
||||||
|
}
|
||||||
|
|
||||||
req, err := parseRequest(c.Reader())
|
req, err := parseRequest(c.Reader())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[http] can not parse request from %s, error: %v", c.RemoteAddr(), err)
|
log.F("[http] can not parse request from %s", c.RemoteAddr())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.pretend {
|
if s.pretend {
|
||||||
fmt.Fprintf(c, "%s 404 Not Found\r\nServer: nginx\r\n\r\n404 Not Found\r\n", req.proto)
|
fmt.Fprintf(c, "%s 404 Not Found\r\nServer: nginx\r\n\r\n404 Not Found\r\n", req.proto)
|
||||||
log.F("[http] %s <-> %s, pretend as web server", c.RemoteAddr().String(), s.Addr())
|
log.F("[http] %s <-> %s,pretend as web server", c.RemoteAddr().String(), s.Addr())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.servRequest(req, c)
|
s.servRequest(req, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTP) servRequest(req *request, c *proxy.Conn) {
|
func (s *HTTP) servRequest(req *request, c *conn.Conn) {
|
||||||
// Auth
|
// Auth
|
||||||
if s.user != "" && s.password != "" {
|
if s.user != "" && s.password != "" {
|
||||||
if user, pass, ok := extractUserPass(req.auth); !ok || user != s.user || pass != s.password {
|
if user, pass, ok := extractUserPass(req.auth); !ok || user != s.user || pass != s.password {
|
||||||
io.WriteString(c, "HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic\r\n\r\n")
|
c.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic\r\n\r\n"))
|
||||||
log.F("[http] auth failed from %s, auth info: %s:%s", c.RemoteAddr(), user, pass)
|
log.F("[http] auth failed from %s, auth info: %s:%s", c.RemoteAddr(), user, pass)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -83,42 +88,38 @@ func (s *HTTP) servRequest(req *request, c *proxy.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTP) servHTTPS(r *request, c net.Conn) {
|
func (s *HTTP) servHTTPS(r *request, c net.Conn) {
|
||||||
rc, dialer, err := s.proxy.Dial("tcp", r.uri)
|
rc, p, err := s.proxy.Dial("tcp", r.uri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
io.WriteString(c, r.proto+" 502 ERROR\r\n\r\n")
|
c.Write([]byte(r.proto + " 502 ERROR\r\n\r\n"))
|
||||||
log.F("[http] %s <-> %s [c] via %s, error in dial: %v", c.RemoteAddr(), r.uri, dialer.Addr(), err)
|
log.F("[http] %s <-> %s [c] via %s, error in dial: %v", c.RemoteAddr(), r.uri, p, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer rc.Close()
|
defer rc.Close()
|
||||||
|
|
||||||
io.WriteString(c, "HTTP/1.1 200 Connection established\r\n\r\n")
|
c.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
|
||||||
|
|
||||||
log.F("[http] %s <-> %s [c] via %s", c.RemoteAddr(), r.uri, dialer.Addr())
|
log.F("[http] %s <-> %s [c] via %s", c.RemoteAddr(), r.uri, p)
|
||||||
|
|
||||||
if err = proxy.Relay(c, rc); err != nil {
|
_, _, err = conn.Relay(c, rc)
|
||||||
log.F("[http] %s <-> %s via %s, relay error: %v", c.RemoteAddr(), r.uri, dialer.Addr(), err)
|
if err != nil {
|
||||||
// record remote conn failure only
|
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||||
if !strings.Contains(err.Error(), s.addr) {
|
return // ignore i/o timeout
|
||||||
s.proxy.Record(dialer, false)
|
|
||||||
}
|
}
|
||||||
|
log.F("[http] relay error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTP) servHTTP(req *request, c *proxy.Conn) {
|
func (s *HTTP) servHTTP(req *request, c *conn.Conn) {
|
||||||
rc, dialer, err := s.proxy.Dial("tcp", req.target)
|
rc, p, err := s.proxy.Dial("tcp", req.target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(c, "%s 502 ERROR\r\n\r\n", req.proto)
|
fmt.Fprintf(c, "%s 502 ERROR\r\n\r\n", req.proto)
|
||||||
log.F("[http] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), req.target, dialer.Addr(), err)
|
log.F("[http] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), req.target, p, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer rc.Close()
|
defer rc.Close()
|
||||||
|
|
||||||
buf := pool.GetBytesBuffer()
|
|
||||||
defer pool.PutBytesBuffer(buf)
|
|
||||||
|
|
||||||
// send request to remote server
|
// send request to remote server
|
||||||
req.WriteBuf(buf)
|
_, err = rc.Write(req.Marshal())
|
||||||
_, err = rc.Write(buf.Bytes())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -126,15 +127,13 @@ func (s *HTTP) servHTTP(req *request, c *proxy.Conn) {
|
|||||||
// copy the left request bytes to remote server. eg. length specificed or chunked body.
|
// copy the left request bytes to remote server. eg. length specificed or chunked body.
|
||||||
go func() {
|
go func() {
|
||||||
if _, err := c.Reader().Peek(1); err == nil {
|
if _, err := c.Reader().Peek(1); err == nil {
|
||||||
proxy.Copy(rc, c)
|
io.Copy(rc, c)
|
||||||
rc.SetDeadline(time.Now())
|
rc.SetDeadline(time.Now())
|
||||||
c.SetDeadline(time.Now())
|
c.SetDeadline(time.Now())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
r := pool.GetBufReader(rc)
|
r := bufio.NewReader(rc)
|
||||||
defer pool.PutBufReader(r)
|
|
||||||
|
|
||||||
tpr := textproto.NewReader(r)
|
tpr := textproto.NewReader(r)
|
||||||
line, err := tpr.ReadLine()
|
line, err := tpr.ReadLine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -155,12 +154,12 @@ func (s *HTTP) servHTTP(req *request, c *proxy.Conn) {
|
|||||||
header.Set("Proxy-Connection", "close")
|
header.Set("Proxy-Connection", "close")
|
||||||
header.Set("Connection", "close")
|
header.Set("Connection", "close")
|
||||||
|
|
||||||
buf.Reset()
|
var buf bytes.Buffer
|
||||||
writeStartLine(buf, proto, code, status)
|
writeStartLine(&buf, proto, code, status)
|
||||||
writeHeaders(buf, header)
|
writeHeaders(&buf, header)
|
||||||
|
|
||||||
log.F("[http] %s <-> %s via %s", c.RemoteAddr(), req.target, dialer.Addr())
|
log.F("[http] %s <-> %s", c.RemoteAddr(), req.target)
|
||||||
c.Write(buf.Bytes())
|
c.Write(buf.Bytes())
|
||||||
|
|
||||||
proxy.Copy(c, r)
|
io.Copy(c, r)
|
||||||
}
|
}
|
||||||
|
133
proxy/kcp/kcp.go
133
proxy/kcp/kcp.go
@ -3,16 +3,15 @@ package kcp
|
|||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
kcp "github.com/xtaci/kcp-go/v5"
|
kcp "github.com/xtaci/kcp-go"
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
"github.com/nadoo/glider/common/log"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,7 +24,6 @@ type KCP struct {
|
|||||||
key string
|
key string
|
||||||
crypt string
|
crypt string
|
||||||
block kcp.BlockCrypt
|
block kcp.BlockCrypt
|
||||||
mode string
|
|
||||||
|
|
||||||
dataShards int
|
dataShards int
|
||||||
parityShards int
|
parityShards int
|
||||||
@ -82,28 +80,14 @@ func NewKCP(s string, d proxy.Dialer, p proxy.Proxy) (*KCP, error) {
|
|||||||
addr: addr,
|
addr: addr,
|
||||||
key: key,
|
key: key,
|
||||||
crypt: crypt,
|
crypt: crypt,
|
||||||
mode: query.Get("mode"),
|
|
||||||
dataShards: int(dataShards),
|
dataShards: int(dataShards),
|
||||||
parityShards: int(parityShards),
|
parityShards: int(parityShards),
|
||||||
}
|
}
|
||||||
|
|
||||||
if k.crypt != "" {
|
if k.crypt != "" {
|
||||||
k.block, err = block(k.crypt, k.key)
|
pass := pbkdf2.Key([]byte(k.key), []byte("kcp-go"), 4096, 32, sha1.New)
|
||||||
if err != nil {
|
var block kcp.BlockCrypt
|
||||||
return nil, fmt.Errorf("[kcp] error: %s", err)
|
switch k.crypt {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if k.mode == "" {
|
|
||||||
k.mode = "fast"
|
|
||||||
}
|
|
||||||
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func block(crypt, key string) (block kcp.BlockCrypt, err error) {
|
|
||||||
pass := pbkdf2.Key([]byte(key), []byte("kcp-go"), 4096, 32, sha1.New)
|
|
||||||
switch crypt {
|
|
||||||
case "sm4":
|
case "sm4":
|
||||||
block, _ = kcp.NewSM4BlockCrypt(pass[:16])
|
block, _ = kcp.NewSM4BlockCrypt(pass[:16])
|
||||||
case "tea":
|
case "tea":
|
||||||
@ -131,9 +115,13 @@ func block(crypt, key string) (block kcp.BlockCrypt, err error) {
|
|||||||
case "salsa20":
|
case "salsa20":
|
||||||
block, _ = kcp.NewSalsa20BlockCrypt(pass)
|
block, _ = kcp.NewSalsa20BlockCrypt(pass)
|
||||||
default:
|
default:
|
||||||
err = errors.New("unknown crypt type '" + crypt + "'")
|
return nil, errors.New("[kcp] unknown crypt type '" + k.crypt + "'")
|
||||||
}
|
}
|
||||||
return block, err
|
|
||||||
|
k.block = block
|
||||||
|
}
|
||||||
|
|
||||||
|
return k, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewKCPDialer returns a kcp proxy dialer.
|
// NewKCPDialer returns a kcp proxy dialer.
|
||||||
@ -143,18 +131,23 @@ func NewKCPDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
|||||||
|
|
||||||
// NewKCPServer returns a kcp proxy server.
|
// NewKCPServer returns a kcp proxy server.
|
||||||
func NewKCPServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
func NewKCPServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||||
schemes := strings.SplitN(s, ",", 2)
|
transport := strings.Split(s, ",")
|
||||||
k, err := NewKCP(schemes[0], nil, p)
|
|
||||||
|
// prepare transport listener
|
||||||
|
// TODO: check here
|
||||||
|
if len(transport) < 2 {
|
||||||
|
return nil, errors.New("[kcp] malformd listener:" + s)
|
||||||
|
}
|
||||||
|
|
||||||
|
k, err := NewKCP(transport[0], nil, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(schemes) > 1 {
|
k.server, err = proxy.ServerFromURL(transport[1], p)
|
||||||
k.server, err = proxy.ServerFromURL(schemes[1], p)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return k, nil
|
return k, nil
|
||||||
}
|
}
|
||||||
@ -163,7 +156,7 @@ func NewKCPServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
|||||||
func (s *KCP) ListenAndServe() {
|
func (s *KCP) ListenAndServe() {
|
||||||
l, err := kcp.ListenWithOptions(s.addr, s.block, s.dataShards, s.parityShards)
|
l, err := kcp.ListenWithOptions(s.addr, s.block, s.dataShards, s.parityShards)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("[kcp] failed to listen on %s: %v", s.addr, err)
|
log.F("[kcp] failed to listen on %s: %v", s.addr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer l.Close()
|
defer l.Close()
|
||||||
@ -177,7 +170,13 @@ func (s *KCP) ListenAndServe() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
s.setParams(c)
|
// TODO: change them to customizable later?
|
||||||
|
c.SetStreamMode(true)
|
||||||
|
c.SetWriteDelay(false)
|
||||||
|
c.SetNoDelay(0, 30, 2, 1)
|
||||||
|
c.SetWindowSize(1024, 1024)
|
||||||
|
c.SetMtu(1350)
|
||||||
|
c.SetACKNoDelay(true)
|
||||||
|
|
||||||
go s.Serve(c)
|
go s.Serve(c)
|
||||||
}
|
}
|
||||||
@ -185,30 +184,11 @@ func (s *KCP) ListenAndServe() {
|
|||||||
|
|
||||||
// Serve serves connections.
|
// Serve serves connections.
|
||||||
func (s *KCP) Serve(c net.Conn) {
|
func (s *KCP) Serve(c net.Conn) {
|
||||||
|
// we know the internal server will close the connection after serve
|
||||||
|
// defer c.Close()
|
||||||
|
|
||||||
if s.server != nil {
|
if s.server != nil {
|
||||||
s.server.Serve(c)
|
s.server.Serve(c)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
rc, dialer, err := s.proxy.Dial("tcp", "")
|
|
||||||
if err != nil {
|
|
||||||
log.F("[kcp] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), s.addr, dialer.Addr(), err)
|
|
||||||
s.proxy.Record(dialer, false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer rc.Close()
|
|
||||||
|
|
||||||
log.F("[kcp] %s <-> %s", c.RemoteAddr(), dialer.Addr())
|
|
||||||
|
|
||||||
if err = proxy.Relay(c, rc); err != nil {
|
|
||||||
log.F("[kcp] %s <-> %s, relay error: %v", c.RemoteAddr(), dialer.Addr(), err)
|
|
||||||
// record remote conn failure only
|
|
||||||
if !strings.Contains(err.Error(), s.addr) {
|
|
||||||
s.proxy.Record(dialer, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,7 +209,13 @@ func (s *KCP) Dial(network, addr string) (net.Conn, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.setParams(c)
|
// TODO: change them to customizable later?
|
||||||
|
c.SetStreamMode(true)
|
||||||
|
c.SetWriteDelay(false)
|
||||||
|
c.SetNoDelay(0, 30, 2, 1)
|
||||||
|
c.SetWindowSize(1024, 1024)
|
||||||
|
c.SetMtu(1350)
|
||||||
|
c.SetACKNoDelay(true)
|
||||||
|
|
||||||
c.SetDSCP(0)
|
c.SetDSCP(0)
|
||||||
c.SetReadBuffer(4194304)
|
c.SetReadBuffer(4194304)
|
||||||
@ -239,43 +225,6 @@ func (s *KCP) Dial(network, addr string) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
// DialUDP connects to the given address via the proxy.
|
||||||
func (s *KCP) DialUDP(network, addr string) (net.PacketConn, error) {
|
func (s *KCP) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||||
return nil, proxy.ErrNotSupported
|
return nil, nil, errors.New("kcp client does not support udp now")
|
||||||
}
|
|
||||||
|
|
||||||
func (s *KCP) setParams(c *kcp.UDPSession) {
|
|
||||||
// TODO: change them to customizable later?
|
|
||||||
c.SetStreamMode(true)
|
|
||||||
c.SetWriteDelay(false)
|
|
||||||
|
|
||||||
switch s.mode {
|
|
||||||
case "normal":
|
|
||||||
c.SetNoDelay(0, 40, 2, 1)
|
|
||||||
case "fast":
|
|
||||||
c.SetNoDelay(0, 30, 2, 1)
|
|
||||||
case "fast2":
|
|
||||||
c.SetNoDelay(1, 20, 2, 1)
|
|
||||||
case "fast3":
|
|
||||||
c.SetNoDelay(1, 10, 2, 1)
|
|
||||||
default:
|
|
||||||
log.F("[kcp] unkonw mode: %s, use fast mode instead", s.mode)
|
|
||||||
c.SetNoDelay(0, 30, 2, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.SetWindowSize(1024, 1024)
|
|
||||||
c.SetMtu(1350)
|
|
||||||
c.SetACKNoDelay(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
proxy.AddUsage("kcp", `
|
|
||||||
KCP scheme:
|
|
||||||
kcp://CRYPT:KEY@host:port[?dataShards=NUM&parityShards=NUM&mode=MODE]
|
|
||||||
|
|
||||||
Available crypt types for KCP:
|
|
||||||
none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20
|
|
||||||
|
|
||||||
Available modes for KCP:
|
|
||||||
fast, fast2, fast3, normal, default: fast
|
|
||||||
`)
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package mixed
|
package mixed
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
"github.com/nadoo/glider/common/conn"
|
||||||
|
"github.com/nadoo/glider/common/log"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
"github.com/nadoo/glider/proxy/http"
|
"github.com/nadoo/glider/proxy/http"
|
||||||
"github.com/nadoo/glider/proxy/socks5"
|
"github.com/nadoo/glider/proxy/socks5"
|
||||||
@ -60,11 +62,11 @@ func (m *Mixed) ListenAndServe() {
|
|||||||
|
|
||||||
l, err := net.Listen("tcp", m.addr)
|
l, err := net.Listen("tcp", m.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("[mixed] failed to listen on %s: %v", m.addr, err)
|
log.F("[mixed] failed to listen on %s: %v", m.addr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.F("[mixed] http & socks5 server listening TCP on %s", m.addr)
|
log.F("[mixed] listening TCP on %s", m.addr)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
c, err := l.Accept()
|
c, err := l.Accept()
|
||||||
@ -79,12 +81,38 @@ func (m *Mixed) ListenAndServe() {
|
|||||||
|
|
||||||
// Serve serves connections.
|
// Serve serves connections.
|
||||||
func (m *Mixed) Serve(c net.Conn) {
|
func (m *Mixed) Serve(c net.Conn) {
|
||||||
conn := proxy.NewConn(c)
|
defer c.Close()
|
||||||
if head, err := conn.Peek(1); err == nil {
|
|
||||||
|
if c, ok := c.(*net.TCPConn); ok {
|
||||||
|
c.SetKeepAlive(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
cc := conn.NewConn(c)
|
||||||
|
|
||||||
|
head, err := cc.Peek(1)
|
||||||
|
if err != nil {
|
||||||
|
// log.F("[mixed] socks5 peek error: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check socks5, client send socksversion: 5 as the first byte
|
||||||
if head[0] == socks5.Version {
|
if head[0] == socks5.Version {
|
||||||
m.socks5Server.Serve(conn)
|
m.socks5Server.Serve(cc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
head, err = cc.Peek(8)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[mixed] http peek error: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, method := range http.Methods {
|
||||||
|
if bytes.HasPrefix(head, method) {
|
||||||
|
m.httpServer.Serve(cc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.httpServer.Serve(conn)
|
|
||||||
|
log.F("[mixed] unknown request from %s, ignored", c.RemoteAddr())
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,11 @@ package obfs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTPObfs struct
|
// HTTPObfs struct
|
||||||
@ -43,19 +42,16 @@ func (p *HTTPObfs) NewConn(c net.Conn) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *HTTPObfsConn) writeHeader() (int, error) {
|
func (c *HTTPObfsConn) writeHeader() (int, error) {
|
||||||
buf := pool.GetBytesBuffer()
|
buf := new(bytes.Buffer)
|
||||||
defer pool.PutBytesBuffer(buf)
|
|
||||||
|
|
||||||
buf.WriteString("GET " + c.obfsURI + " HTTP/1.1\r\n")
|
buf.WriteString("GET " + c.obfsURI + " HTTP/1.1\r\n")
|
||||||
buf.WriteString("Host: " + c.obfsHost + "\r\n")
|
buf.WriteString("Host: " + c.obfsHost + "\r\n")
|
||||||
buf.WriteString("User-Agent: " + c.obfsUA + "\r\n")
|
buf.WriteString("User-Agent: " + c.obfsUA + "\r\n")
|
||||||
buf.WriteString("Upgrade: websocket\r\n")
|
buf.WriteString("Upgrade: websocket\r\n")
|
||||||
buf.WriteString("Connection: Upgrade\r\n")
|
buf.WriteString("Connection: Upgrade\r\n")
|
||||||
|
|
||||||
b := pool.GetBuffer(16)
|
p := make([]byte, 16)
|
||||||
rand.Read(b)
|
rand.Read(p)
|
||||||
buf.WriteString("Sec-WebSocket-Key: " + base64.StdEncoding.EncodeToString(b) + "\r\n")
|
buf.WriteString("Sec-WebSocket-Key: " + base64.StdEncoding.EncodeToString(p) + "\r\n")
|
||||||
pool.PutBuffer(b)
|
|
||||||
|
|
||||||
buf.WriteString("\r\n")
|
buf.WriteString("\r\n")
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
"github.com/nadoo/glider/common/log"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -106,16 +106,6 @@ func (s *Obfs) Dial(network, addr string) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
// DialUDP connects to the given address via the proxy.
|
||||||
func (s *Obfs) DialUDP(network, addr string) (net.PacketConn, error) {
|
func (s *Obfs) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||||
return nil, proxy.ErrNotSupported
|
return nil, nil, errors.New("obfs client does not support udp now")
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
proxy.AddUsage("simple-obfs", `
|
|
||||||
Simple-Obfs scheme:
|
|
||||||
simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA]
|
|
||||||
|
|
||||||
Available types for simple-obfs:
|
|
||||||
http, tls
|
|
||||||
`)
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
// https://www.rfc-editor.org/rfc/rfc5246
|
// https://www.ietf.org/rfc/rfc5246.txt
|
||||||
// https://golang.org/src/crypto/tls/handshake_messages.go
|
// https://golang.org/src/crypto/tls/handshake_messages.go
|
||||||
|
|
||||||
// NOTE:
|
// NOTE:
|
||||||
|
// https://github.com/shadowsocks/simple-obfs/blob/master/src/obfs_tls.c
|
||||||
// The official obfs-server only checks 6 static bytes of client hello packet,
|
// The official obfs-server only checks 6 static bytes of client hello packet,
|
||||||
// so if we send a malformed packet, e.g: set a wrong length number of extensions,
|
// so if we send a malformed packet, e.g: set a wrong length number of extensions,
|
||||||
// obfs-server will treat it as a correct packet, but in wireshak, it's malformed.
|
// obfs-server will treat it as a correct packet, but in wireshak, it's malformed.
|
||||||
@ -16,8 +17,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -42,13 +41,18 @@ type TLSObfsConn struct {
|
|||||||
net.Conn
|
net.Conn
|
||||||
reqSent bool
|
reqSent bool
|
||||||
reader *bufio.Reader
|
reader *bufio.Reader
|
||||||
buf [lenSize]byte
|
buf []byte
|
||||||
leftBytes int
|
leftBytes int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConn returns a new obfs connection
|
// NewConn returns a new obfs connection
|
||||||
func (p *TLSObfs) NewConn(c net.Conn) (net.Conn, error) {
|
func (p *TLSObfs) NewConn(c net.Conn) (net.Conn, error) {
|
||||||
cc := &TLSObfsConn{Conn: c, TLSObfs: p}
|
cc := &TLSObfsConn{
|
||||||
|
Conn: c,
|
||||||
|
TLSObfs: p,
|
||||||
|
buf: make([]byte, lenSize),
|
||||||
|
}
|
||||||
|
|
||||||
return cc, nil
|
return cc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,14 +62,14 @@ func (c *TLSObfsConn) Write(b []byte) (int, error) {
|
|||||||
return c.handshake(b)
|
return c.handshake(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := pool.GetBytesBuffer()
|
|
||||||
defer pool.PutBytesBuffer(buf)
|
|
||||||
|
|
||||||
n := len(b)
|
n := len(b)
|
||||||
for i := 0; i < n; i += chunkSize {
|
for i := 0; i < n; i += chunkSize {
|
||||||
buf.Reset()
|
end := i + chunkSize
|
||||||
end := min(i+chunkSize, n)
|
if end > n {
|
||||||
|
end = n
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
buf.Write([]byte{0x17, 0x03, 0x03})
|
buf.Write([]byte{0x17, 0x03, 0x03})
|
||||||
binary.Write(buf, binary.BigEndian, uint16(len(b[i:end])))
|
binary.Write(buf, binary.BigEndian, uint16(len(b[i:end])))
|
||||||
buf.Write(b[i:end])
|
buf.Write(b[i:end])
|
||||||
@ -120,18 +124,10 @@ func (c *TLSObfsConn) Read(b []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *TLSObfsConn) handshake(b []byte) (int, error) {
|
func (c *TLSObfsConn) handshake(b []byte) (int, error) {
|
||||||
buf := pool.GetBytesBuffer()
|
buf := new(bytes.Buffer)
|
||||||
defer pool.PutBytesBuffer(buf)
|
|
||||||
|
|
||||||
bufExt := pool.GetBytesBuffer()
|
|
||||||
defer pool.PutBytesBuffer(bufExt)
|
|
||||||
|
|
||||||
bufHello := pool.GetBytesBuffer()
|
|
||||||
defer pool.PutBytesBuffer(bufHello)
|
|
||||||
|
|
||||||
// prepare extension & clientHello content
|
// prepare extension & clientHello content
|
||||||
extension(b, c.obfsHost, bufExt)
|
bufExt, bufHello := extension(b, c.obfsHost), clientHello()
|
||||||
clientHello(bufHello)
|
|
||||||
|
|
||||||
// prepare lengths
|
// prepare lengths
|
||||||
extLen := bufExt.Len()
|
extLen := bufExt.Len()
|
||||||
@ -166,7 +162,6 @@ func (c *TLSObfsConn) handshake(b []byte) (int, error) {
|
|||||||
buf.Write(bufExt.Bytes())
|
buf.Write(bufExt.Bytes())
|
||||||
|
|
||||||
_, err := c.Conn.Write(buf.Bytes())
|
_, err := c.Conn.Write(buf.Bytes())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -174,7 +169,9 @@ func (c *TLSObfsConn) handshake(b []byte) (int, error) {
|
|||||||
return len(b), nil
|
return len(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func clientHello(buf *bytes.Buffer) {
|
func clientHello() *bytes.Buffer {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
// Version: TLS 1.2 (0x0303)
|
// Version: TLS 1.2 (0x0303)
|
||||||
buf.Write([]byte{0x03, 0x03})
|
buf.Write([]byte{0x03, 0x03})
|
||||||
|
|
||||||
@ -216,9 +213,13 @@ func clientHello(buf *bytes.Buffer) {
|
|||||||
buf.WriteByte(0x01)
|
buf.WriteByte(0x01)
|
||||||
// Compression Methods (1 method)
|
// Compression Methods (1 method)
|
||||||
buf.WriteByte(0x00)
|
buf.WriteByte(0x00)
|
||||||
|
|
||||||
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
func extension(b []byte, server string, buf *bytes.Buffer) {
|
func extension(b []byte, server string) *bytes.Buffer {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
// Extension: SessionTicket TLS
|
// Extension: SessionTicket TLS
|
||||||
buf.Write([]byte{0x00, 0x23}) // type
|
buf.Write([]byte{0x00, 0x23}) // type
|
||||||
// NOTE: send some data in sessionticket, the server will treat it as data too
|
// NOTE: send some data in sessionticket, the server will treat it as data too
|
||||||
@ -231,7 +232,7 @@ func extension(b []byte, server string, buf *bytes.Buffer) {
|
|||||||
binary.Write(buf, binary.BigEndian, uint16(len(server)+3)) // Server Name list length
|
binary.Write(buf, binary.BigEndian, uint16(len(server)+3)) // Server Name list length
|
||||||
buf.WriteByte(0x00) // Server Name Type: host_name (0)
|
buf.WriteByte(0x00) // Server Name Type: host_name (0)
|
||||||
binary.Write(buf, binary.BigEndian, uint16(len(server))) // Server Name length
|
binary.Write(buf, binary.BigEndian, uint16(len(server))) // Server Name length
|
||||||
buf.WriteString(server)
|
buf.Write([]byte(server))
|
||||||
|
|
||||||
// https://github.com/shadowsocks/simple-obfs/blob/7659eeccf473aa41eb294e92c32f8f60a8747325/src/obfs_tls.c#L88
|
// https://github.com/shadowsocks/simple-obfs/blob/7659eeccf473aa41eb294e92c32f8f60a8747325/src/obfs_tls.c#L88
|
||||||
// Extension: ec_point_formats (len=4)
|
// Extension: ec_point_formats (len=4)
|
||||||
@ -262,4 +263,6 @@ func extension(b []byte, server string, buf *bytes.Buffer) {
|
|||||||
// Extension: extended_master_secret (len=0)
|
// Extension: extended_master_secret (len=0)
|
||||||
buf.Write([]byte{0x00, 0x17}) // type
|
buf.Write([]byte{0x00, 0x17}) // type
|
||||||
binary.Write(buf, binary.BigEndian, uint16(0)) // length
|
binary.Write(buf, binary.BigEndian, uint16(0)) // length
|
||||||
|
|
||||||
|
return buf
|
||||||
}
|
}
|
||||||
|
@ -1,46 +1,15 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import "net"
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Proxy is a dialer manager.
|
// Proxy is a dialer manager
|
||||||
type Proxy interface {
|
type Proxy interface {
|
||||||
// Dial connects to the given address via the proxy.
|
// Dial connects to the given address via the proxy.
|
||||||
Dial(network, addr string) (c net.Conn, dialer Dialer, err error)
|
Dial(network, addr string) (c net.Conn, proxy string, err error)
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
// DialUDP connects to the given address via the proxy.
|
||||||
DialUDP(network, addr string) (pc net.PacketConn, dialer UDPDialer, err error)
|
DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error)
|
||||||
|
|
||||||
// Get the dialer by dstAddr.
|
// Get the dialer by dstAddr
|
||||||
NextDialer(dstAddr string) Dialer
|
NextDialer(dstAddr string) Dialer
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
@ -1,134 +0,0 @@
|
|||||||
package pxyproto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
|
||||||
"github.com/nadoo/glider/proxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
proxy.RegisterServer("pxyproto", NewPxyProtoServer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PxyProtoServer struct.
|
|
||||||
type PxyProtoServer struct {
|
|
||||||
addr string
|
|
||||||
proxy proxy.Proxy
|
|
||||||
server proxy.Server
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPxyProtoServer returns a PxyProtoServer struct.
|
|
||||||
func NewPxyProtoServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
|
||||||
schemes := strings.SplitN(s, ",", 2)
|
|
||||||
u, err := url.Parse(schemes[0])
|
|
||||||
if err != nil {
|
|
||||||
log.F("[pxyproto] parse url err: %s", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t := &PxyProtoServer{proxy: p, addr: u.Host}
|
|
||||||
if len(schemes) < 2 {
|
|
||||||
return nil, errors.New("[pxyproto] you must use pxyproto with a proxy server, e.g: pxyproto://:1234,http://")
|
|
||||||
}
|
|
||||||
|
|
||||||
t.server, err = proxy.ServerFromURL(schemes[1], p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenAndServe listens on server's addr and serves connections.
|
|
||||||
func (s *PxyProtoServer) ListenAndServe() {
|
|
||||||
l, err := net.Listen("tcp", s.addr)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("[pxyproto] failed to listen on %s: %v", s.addr, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer l.Close()
|
|
||||||
|
|
||||||
log.F("[pxyproto] listening TCP on %s", s.addr)
|
|
||||||
|
|
||||||
for {
|
|
||||||
c, err := l.Accept()
|
|
||||||
if err != nil {
|
|
||||||
log.F("[pxyproto] failed to accept: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
go s.Serve(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve serves a connection.
|
|
||||||
func (s *PxyProtoServer) Serve(cc net.Conn) {
|
|
||||||
c, err := newServerConn(cc)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[pxyproto] parse header failed, error: %v", err)
|
|
||||||
cc.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// log.F("[pxyproto] %s <-> %s <-> %s <-> %s",
|
|
||||||
// c.RemoteAddr(), c.LocalAddr(), cc.RemoteAddr(), cc.LocalAddr())
|
|
||||||
|
|
||||||
if s.server != nil {
|
|
||||||
s.server.Serve(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type serverConn struct {
|
|
||||||
*proxy.Conn
|
|
||||||
src, dst net.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func newServerConn(c net.Conn) (*serverConn, error) {
|
|
||||||
sc := &serverConn{
|
|
||||||
Conn: proxy.NewConn(c),
|
|
||||||
src: c.RemoteAddr(),
|
|
||||||
dst: c.LocalAddr(),
|
|
||||||
}
|
|
||||||
return sc, sc.parseHeader()
|
|
||||||
}
|
|
||||||
|
|
||||||
// "PROXY TCPx SRC_IP DST_IP SRC_PORT DST_PORT"
|
|
||||||
func (c *serverConn) parseHeader() error {
|
|
||||||
line, err := c.Conn.Reader().ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
line = strings.ReplaceAll(line, "\r\n", "")
|
|
||||||
// log.F("[pxyproto] req header: %s", line)
|
|
||||||
|
|
||||||
header := strings.Split(line, " ")
|
|
||||||
if len(header) != 6 {
|
|
||||||
return fmt.Errorf("invalid header: %s", line)
|
|
||||||
}
|
|
||||||
|
|
||||||
if header[0] != "PROXY" {
|
|
||||||
return fmt.Errorf("invalid header: %s", line)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.src, err = net.ResolveTCPAddr("tcp", net.JoinHostPort(header[2], header[4]))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parse header: %s, error: %v", line, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.dst, err = net.ResolveTCPAddr("tcp", net.JoinHostPort(header[3], header[5]))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parse header: %s, error: %v", line, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *serverConn) LocalAddr() net.Addr { return c.dst }
|
|
||||||
func (c *serverConn) RemoteAddr() net.Addr { return c.src }
|
|
@ -1,17 +1,28 @@
|
|||||||
|
// getOrigDst:
|
||||||
|
// https://github.com/shadowsocks/go-shadowsocks2/blob/master/tcp_linux.go#L30
|
||||||
|
|
||||||
package redir
|
package redir
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
"github.com/nadoo/glider/common/conn"
|
||||||
|
"github.com/nadoo/glider/common/log"
|
||||||
|
"github.com/nadoo/glider/common/socks"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SO_ORIGINAL_DST from linux/include/uapi/linux/netfilter_ipv4.h
|
||||||
|
SO_ORIGINAL_DST = 80
|
||||||
|
// IP6T_SO_ORIGINAL_DST from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h
|
||||||
|
IP6T_SO_ORIGINAL_DST = 80
|
||||||
|
)
|
||||||
|
|
||||||
// RedirProxy struct.
|
// RedirProxy struct.
|
||||||
type RedirProxy struct {
|
type RedirProxy struct {
|
||||||
proxy proxy.Proxy
|
proxy proxy.Proxy
|
||||||
@ -56,7 +67,7 @@ func NewRedir6Server(s string, p proxy.Proxy) (proxy.Server, error) {
|
|||||||
func (s *RedirProxy) ListenAndServe() {
|
func (s *RedirProxy) ListenAndServe() {
|
||||||
l, err := net.Listen("tcp", s.addr)
|
l, err := net.Listen("tcp", s.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("[redir] failed to listen on %s: %v", s.addr, err)
|
log.F("[redir] failed to listen on %s: %v", s.addr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,88 +85,99 @@ func (s *RedirProxy) ListenAndServe() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Serve serves connections.
|
// Serve serves connections.
|
||||||
func (s *RedirProxy) Serve(cc net.Conn) {
|
func (s *RedirProxy) Serve(c net.Conn) {
|
||||||
defer cc.Close()
|
defer c.Close()
|
||||||
|
|
||||||
c, ok := cc.(*net.TCPConn)
|
if c, ok := c.(*net.TCPConn); ok {
|
||||||
if !ok {
|
c.SetKeepAlive(true)
|
||||||
log.F("[redir] not a tcp connection, can not chain redir proxy")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.SetKeepAlive(true)
|
tgt, err := getOrigDst(c, s.ipv6)
|
||||||
tgtAddr, err := getOrigDst(c, s.ipv6)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[redir] failed to get target address: %v", err)
|
log.F("[redir] failed to get target address: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tgt := tgtAddr.String()
|
|
||||||
|
|
||||||
// loop request
|
// loop request
|
||||||
if c.LocalAddr().String() == tgt {
|
if c.LocalAddr().String() == tgt.String() {
|
||||||
log.F("[redir] %s <-> %s, unallowed request to redir port", c.RemoteAddr(), tgt)
|
log.F("[redir] %s <-> %s, unallowed request to redir port", c.RemoteAddr(), tgt)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rc, dialer, err := s.proxy.Dial("tcp", tgt)
|
rc, p, err := s.proxy.Dial("tcp", tgt.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[redir] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
log.F("[redir] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, p, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer rc.Close()
|
defer rc.Close()
|
||||||
|
|
||||||
log.F("[redir] %s <-> %s via %s", c.RemoteAddr(), tgt, dialer.Addr())
|
log.F("[redir] %s <-> %s via %s", c.RemoteAddr(), tgt, p)
|
||||||
|
|
||||||
if err = proxy.Relay(c, rc); err != nil {
|
_, _, err = conn.Relay(c, rc)
|
||||||
log.F("[redir] %s <-> %s via %s, relay error: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
if err != nil {
|
||||||
// record remote conn failure only
|
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||||
if !strings.Contains(err.Error(), s.addr) {
|
return // ignore i/o timeout
|
||||||
s.proxy.Record(dialer, false)
|
|
||||||
}
|
}
|
||||||
|
log.F("[redir] relay error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the original destination of a TCP connection.
|
// Get the original destination of a TCP connection.
|
||||||
func getOrigDst(c *net.TCPConn, ipv6 bool) (netip.AddrPort, error) {
|
func getOrigDst(conn net.Conn, ipv6 bool) (socks.Addr, error) {
|
||||||
rc, err := c.SyscallConn()
|
c, ok := conn.(*net.TCPConn)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("only work with TCP connection")
|
||||||
|
}
|
||||||
|
f, err := c.File()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return netip.AddrPort{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var addr netip.AddrPort
|
defer f.Close()
|
||||||
rc.Control(func(fd uintptr) {
|
|
||||||
|
fd := f.Fd()
|
||||||
|
|
||||||
|
// The File() call above puts both the original socket fd and the file fd in blocking mode.
|
||||||
|
// Set the file fd back to non-blocking mode and the original socket fd will become non-blocking as well.
|
||||||
|
// Otherwise blocking I/O will waste OS threads.
|
||||||
|
if err := syscall.SetNonblock(int(fd), true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if ipv6 {
|
if ipv6 {
|
||||||
addr, err = getorigdstIPv6(fd)
|
return getorigdstIPv6(fd)
|
||||||
} else {
|
|
||||||
addr, err = getorigdst(fd)
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
return addr, err
|
return getorigdst(fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c
|
// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c
|
||||||
func getorigdst(fd uintptr) (netip.AddrPort, error) {
|
func getorigdst(fd uintptr) (socks.Addr, error) {
|
||||||
const _SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h
|
raw := syscall.RawSockaddrInet4{}
|
||||||
var raw syscall.RawSockaddrInet4
|
|
||||||
siz := unsafe.Sizeof(raw)
|
siz := unsafe.Sizeof(raw)
|
||||||
if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, _SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil {
|
if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil {
|
||||||
return netip.AddrPort{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// NOTE: raw.Port is big-endian, just change it to little-endian
|
|
||||||
// TODO: improve here when we add big-endian $GOARCH support
|
addr := make([]byte, 1+net.IPv4len+2)
|
||||||
port := raw.Port<<8 | raw.Port>>8
|
addr[0] = socks.ATypIP4
|
||||||
return netip.AddrPortFrom(netip.AddrFrom4(raw.Addr), port), nil
|
copy(addr[1:1+net.IPv4len], raw.Addr[:])
|
||||||
|
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian
|
||||||
|
addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1]
|
||||||
|
return addr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call ipv6_getorigdst() from linux/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
|
// Call ipv6_getorigdst() from linux/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
|
||||||
func getorigdstIPv6(fd uintptr) (netip.AddrPort, error) {
|
func getorigdstIPv6(fd uintptr) (socks.Addr, error) {
|
||||||
const _IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h
|
raw := syscall.RawSockaddrInet6{}
|
||||||
var raw syscall.RawSockaddrInet6
|
|
||||||
siz := unsafe.Sizeof(raw)
|
siz := unsafe.Sizeof(raw)
|
||||||
if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IPV6, _IP6T_SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil {
|
if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil {
|
||||||
return netip.AddrPort{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// NOTE: raw.Port is big-endian, just change it to little-endian
|
|
||||||
// TODO: improve here when we add big-endian $GOARCH support
|
addr := make([]byte, 1+net.IPv6len+2)
|
||||||
port := raw.Port<<8 | raw.Port>>8
|
addr[0] = socks.ATypIP6
|
||||||
return netip.AddrPortFrom(netip.AddrFrom16(raw.Addr), port), nil
|
copy(addr[1:1+net.IPv6len], raw.Addr[:])
|
||||||
|
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian
|
||||||
|
addr[1+net.IPv6len], addr[1+net.IPv6len+1] = port[0], port[1]
|
||||||
|
return addr, nil
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// https://github.com/golang/go/blob/9e6b79a5dfb2f6fe4301ced956419a0da83bd025/src/syscall/syscall_linux_386.go#L196
|
// https://github.com/golang/go/blob/9e6b79a5dfb2f6fe4301ced956419a0da83bd025/src/syscall/syscall_linux_386.go#L196
|
||||||
const GETSOCKOPT = 15 // https://golang.org/src/syscall/syscall_linux_386.go#L183
|
const GETSOCKOPT = 15
|
||||||
|
|
||||||
func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error {
|
func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error {
|
||||||
var a [6]uintptr
|
var a [6]uintptr
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//go:build linux && !386
|
// +build linux,!386
|
||||||
|
|
||||||
package redir
|
package redir
|
||||||
|
|
||||||
|
@ -34,13 +34,6 @@ func (s *Reject) Dial(network, addr string) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
// DialUDP connects to the given address via the proxy.
|
||||||
func (s *Reject) DialUDP(network, addr string) (net.PacketConn, error) {
|
func (s *Reject) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||||
return nil, errors.New("REJECT")
|
return nil, nil, errors.New("REJECT")
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
proxy.AddUsage("reject", `
|
|
||||||
Reject scheme:
|
|
||||||
reject://
|
|
||||||
`)
|
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,13 @@ package proxy
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/common/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server interface.
|
// Server interface
|
||||||
type Server interface {
|
type Server interface {
|
||||||
// ListenAndServe sets up a listener and serve on it
|
// ListenAndServe sets up a listener and serve on it
|
||||||
ListenAndServe()
|
ListenAndServe()
|
||||||
@ -16,27 +18,22 @@ type Server interface {
|
|||||||
Serve(c net.Conn)
|
Serve(c net.Conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PacketServer interface.
|
// ServerCreator is a function to create proxy servers
|
||||||
type PacketServer interface {
|
|
||||||
ServePacket(pc net.PacketConn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServerCreator is a function to create proxy servers.
|
|
||||||
type ServerCreator func(s string, proxy Proxy) (Server, error)
|
type ServerCreator func(s string, proxy Proxy) (Server, error)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
serverCreators = make(map[string]ServerCreator)
|
serverMap = make(map[string]ServerCreator)
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegisterServer is used to register a proxy server.
|
// RegisterServer is used to register a proxy server
|
||||||
func RegisterServer(name string, c ServerCreator) {
|
func RegisterServer(name string, c ServerCreator) {
|
||||||
serverCreators[strings.ToLower(name)] = c
|
serverMap[name] = c
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerFromURL calls the registered creator to create proxy servers.
|
// ServerFromURL calls the registered creator to create proxy servers
|
||||||
// proxy can not be nil.
|
// dialer is the default upstream dialer so cannot be nil, we can use Default when calling this function
|
||||||
func ServerFromURL(s string, proxy Proxy) (Server, error) {
|
func ServerFromURL(s string, p Proxy) (Server, error) {
|
||||||
if proxy == nil {
|
if p == nil {
|
||||||
return nil, errors.New("ServerFromURL: dialer cannot be nil")
|
return nil, errors.New("ServerFromURL: dialer cannot be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,21 +41,16 @@ func ServerFromURL(s string, proxy Proxy) (Server, error) {
|
|||||||
s = "mixed://" + s
|
s = "mixed://" + s
|
||||||
}
|
}
|
||||||
|
|
||||||
scheme := s[:strings.Index(s, ":")]
|
u, err := url.Parse(s)
|
||||||
c, ok := serverCreators[strings.ToLower(scheme)]
|
if err != nil {
|
||||||
|
log.F("parse err: %s", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, ok := serverMap[strings.ToLower(u.Scheme)]
|
||||||
if ok {
|
if ok {
|
||||||
return c(s, proxy)
|
return c(s, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("unknown scheme '" + scheme + "'")
|
return nil, errors.New("unknown scheme '" + u.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, " ")
|
|
||||||
}
|
}
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
package smux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
|
||||||
"github.com/nadoo/glider/pkg/smux"
|
|
||||||
"github.com/nadoo/glider/proxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SmuxClient struct.
|
|
||||||
type SmuxClient struct {
|
|
||||||
dialer proxy.Dialer
|
|
||||||
addr string
|
|
||||||
mu sync.Mutex
|
|
||||||
session *smux.Session
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
proxy.RegisterDialer("smux", NewSmuxDialer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSmuxDialer returns a smux dialer.
|
|
||||||
func NewSmuxDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
|
||||||
u, err := url.Parse(s)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[smux] parse url err: %s", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &SmuxClient{
|
|
||||||
dialer: d,
|
|
||||||
addr: u.Host,
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Addr returns forwarder's address.
|
|
||||||
func (s *SmuxClient) Addr() string {
|
|
||||||
if s.addr == "" {
|
|
||||||
return s.dialer.Addr()
|
|
||||||
}
|
|
||||||
return s.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial connects to the address addr on the network net via the proxy.
|
|
||||||
func (s *SmuxClient) Dial(network, addr string) (net.Conn, error) {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
|
|
||||||
if s.session != nil {
|
|
||||||
if c, err := s.session.OpenStream(); err == nil {
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
s.session.Close()
|
|
||||||
}
|
|
||||||
if err := s.initConn(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return s.session.OpenStream()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
|
||||||
func (s *SmuxClient) DialUDP(network, addr string) (net.PacketConn, error) {
|
|
||||||
return nil, proxy.ErrNotSupported
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SmuxClient) initConn() error {
|
|
||||||
conn, err := s.dialer.Dial("tcp", s.addr)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[smux] dial to %s error: %s", s.addr, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.session, err = smux.Client(conn, nil)
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,119 +0,0 @@
|
|||||||
package smux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
|
||||||
"github.com/nadoo/glider/pkg/smux"
|
|
||||||
"github.com/nadoo/glider/proxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SmuxServer struct.
|
|
||||||
type SmuxServer struct {
|
|
||||||
proxy proxy.Proxy
|
|
||||||
addr string
|
|
||||||
server proxy.Server
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
proxy.RegisterServer("smux", NewSmuxServer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSmuxServer returns a smux transport layer before the real server.
|
|
||||||
func NewSmuxServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
|
||||||
schemes := strings.SplitN(s, ",", 2)
|
|
||||||
u, err := url.Parse(schemes[0])
|
|
||||||
if err != nil {
|
|
||||||
log.F("[smux] parse url err: %s", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
m := &SmuxServer{
|
|
||||||
proxy: p,
|
|
||||||
addr: u.Host,
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(schemes) > 1 {
|
|
||||||
m.server, err = proxy.ServerFromURL(schemes[1], p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenAndServe listens on server's addr and serves connections.
|
|
||||||
func (s *SmuxServer) ListenAndServe() {
|
|
||||||
l, err := net.Listen("tcp", s.addr)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("[smux] failed to listen on %s: %v", s.addr, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer l.Close()
|
|
||||||
|
|
||||||
log.F("[smux] listening mux on %s", s.addr)
|
|
||||||
|
|
||||||
for {
|
|
||||||
c, err := l.Accept()
|
|
||||||
if err != nil {
|
|
||||||
log.F("[smux] failed to accept: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
go s.Serve(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve serves a connection.
|
|
||||||
func (s *SmuxServer) Serve(c net.Conn) {
|
|
||||||
// we know the internal server will close the connection after serve
|
|
||||||
// defer c.Close()
|
|
||||||
|
|
||||||
session, err := smux.Server(c, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[smux] failed to create session: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
// Accept a stream
|
|
||||||
stream, err := session.AcceptStream()
|
|
||||||
if err != nil {
|
|
||||||
session.Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
go s.ServeStream(stream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeStream serves a smux stream.
|
|
||||||
func (s *SmuxServer) ServeStream(c *smux.Stream) {
|
|
||||||
if s.server != nil {
|
|
||||||
s.server.Serve(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
rc, dialer, err := s.proxy.Dial("tcp", "")
|
|
||||||
if err != nil {
|
|
||||||
log.F("[smux] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), s.addr, dialer.Addr(), err)
|
|
||||||
s.proxy.Record(dialer, false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer rc.Close()
|
|
||||||
|
|
||||||
log.F("[smux] %s <-> %s", c.RemoteAddr(), dialer.Addr())
|
|
||||||
|
|
||||||
if err = proxy.Relay(c, rc); err != nil {
|
|
||||||
log.F("[smux] %s <-> %s, relay error: %v", c.RemoteAddr(), dialer.Addr(), err)
|
|
||||||
// record remote conn failure only
|
|
||||||
if !strings.Contains(err.Error(), s.addr) {
|
|
||||||
s.proxy.Record(dialer, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package smux
|
|
||||||
|
|
||||||
import "github.com/nadoo/glider/proxy"
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
proxy.AddUsage("smux", `
|
|
||||||
Smux scheme:
|
|
||||||
smux://host:port
|
|
||||||
`)
|
|
||||||
}
|
|
@ -1,195 +0,0 @@
|
|||||||
// https://www.openssh.com/txt/socks4.protocol
|
|
||||||
|
|
||||||
// socks4 client
|
|
||||||
|
|
||||||
package socks4
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
|
||||||
"github.com/nadoo/glider/proxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Version is socks4 version number.
|
|
||||||
Version = 4
|
|
||||||
// ConnectCommand connect command byte
|
|
||||||
ConnectCommand = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
// SOCKS4 is a base socks4 struct.
|
|
||||||
type SOCKS4 struct {
|
|
||||||
dialer proxy.Dialer
|
|
||||||
addr string
|
|
||||||
socks4a bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
proxy.RegisterDialer("socks4", NewSocks4Dialer)
|
|
||||||
proxy.RegisterDialer("socks4a", NewSocks4Dialer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSOCKS4 returns a socks4 proxy.
|
|
||||||
func NewSOCKS4(s string, dialer proxy.Dialer) (*SOCKS4, error) {
|
|
||||||
u, err := url.Parse(s)
|
|
||||||
if err != nil {
|
|
||||||
log.F("parse err: %s", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
h := &SOCKS4{
|
|
||||||
dialer: dialer,
|
|
||||||
addr: u.Host,
|
|
||||||
socks4a: u.Scheme == "socks4a",
|
|
||||||
}
|
|
||||||
|
|
||||||
return h, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSocks4Dialer returns a socks4 proxy dialer.
|
|
||||||
func NewSocks4Dialer(s string, dialer proxy.Dialer) (proxy.Dialer, error) {
|
|
||||||
return NewSOCKS4(s, dialer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Addr returns forwarder's address.
|
|
||||||
func (s *SOCKS4) Addr() string {
|
|
||||||
if s.addr == "" {
|
|
||||||
return s.dialer.Addr()
|
|
||||||
}
|
|
||||||
return s.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial connects to the address addr on the network net via the SOCKS4 proxy.
|
|
||||||
func (s *SOCKS4) Dial(network, addr string) (net.Conn, error) {
|
|
||||||
switch network {
|
|
||||||
case "tcp", "tcp4":
|
|
||||||
default:
|
|
||||||
return nil, errors.New("[socks4] no support for connection type " + network)
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := s.dialer.Dial(network, s.addr)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[socks4] dial to %s error: %s", s.addr, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.connect(c, addr); err != nil {
|
|
||||||
c.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
|
||||||
func (s *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) {
|
|
||||||
ips, err := net.LookupIP(host)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(ips) == 0 {
|
|
||||||
err = errors.New("[socks4] Cannot resolve host: " + host)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ip = ips[0].To4()
|
|
||||||
if len(ip) != net.IPv4len {
|
|
||||||
err = errors.New("[socks4] IPv6 is not supported by socks4")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// connect takes an existing connection to a socks4 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 *SOCKS4) connect(conn net.Conn, target string) error {
|
|
||||||
host, portStr, err := net.SplitHostPort(target)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
port, err := strconv.ParseUint(portStr, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("[socks4] failed to parse port number: " + portStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseBufSize = 8 + 1 // 1 is the len(userid)
|
|
||||||
bufSize := baseBufSize
|
|
||||||
var ip net.IP
|
|
||||||
if ip = net.ParseIP(host); ip == nil {
|
|
||||||
if s.socks4a {
|
|
||||||
// The client should set the first three bytes of DSTIP to NULL
|
|
||||||
// and the last byte to a non-zero value.
|
|
||||||
ip = []byte{0, 0, 0, 1}
|
|
||||||
bufSize += len(host) + 1
|
|
||||||
} else {
|
|
||||||
ip, err = s.lookupIP(host)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ip = ip.To4()
|
|
||||||
if ip == nil {
|
|
||||||
return errors.New("[socks4] IPv6 is not supported by socks4")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// taken from https://github.com/h12w/socks/blob/master/socks.go and https://en.wikipedia.org/wiki/SOCKS
|
|
||||||
buf := pool.GetBuffer(bufSize)
|
|
||||||
defer pool.PutBuffer(buf)
|
|
||||||
copy(buf, []byte{
|
|
||||||
Version,
|
|
||||||
ConnectCommand,
|
|
||||||
byte(port >> 8), // higher byte of destination port
|
|
||||||
byte(port), // lower byte of destination port (big endian)
|
|
||||||
ip[0], ip[1], ip[2], ip[3],
|
|
||||||
0, // user id
|
|
||||||
})
|
|
||||||
if s.socks4a {
|
|
||||||
copy(buf[baseBufSize:], host)
|
|
||||||
buf[len(buf)-1] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := pool.GetBuffer(8)
|
|
||||||
defer pool.PutBuffer(resp)
|
|
||||||
|
|
||||||
if _, err := conn.Write(buf); err != nil {
|
|
||||||
return errors.New("[socks4] failed to write greeting to socks4 proxy at " + s.addr + ": " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, resp); err != nil {
|
|
||||||
return errors.New("[socks4] failed to read greeting from socks4 proxy at " + s.addr + ": " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
switch resp[1] {
|
|
||||||
case 0x5a:
|
|
||||||
// request granted
|
|
||||||
case 0x5b:
|
|
||||||
err = errors.New("[socks4] connection request rejected or failed")
|
|
||||||
case 0x5c:
|
|
||||||
err = errors.New("[socks4] connection request request failed because client is not running identd (or not reachable from the server)")
|
|
||||||
case 0x5d:
|
|
||||||
err = errors.New("[socks4] connection request request failed because client's identd could not confirm the user ID in the request")
|
|
||||||
default:
|
|
||||||
err = errors.New("[socks4] connection request failed, unknown error")
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
proxy.AddUsage("socks4", `
|
|
||||||
Socks4 scheme:
|
|
||||||
socks4://host:port
|
|
||||||
`)
|
|
||||||
}
|
|
@ -1,179 +0,0 @@
|
|||||||
package socks5
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
|
||||||
"github.com/nadoo/glider/pkg/socks"
|
|
||||||
"github.com/nadoo/glider/proxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Addr returns forwarder's address.
|
|
||||||
func (s *Socks5) Addr() string {
|
|
||||||
if s.addr == "" {
|
|
||||||
return s.dialer.Addr()
|
|
||||||
}
|
|
||||||
return s.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial connects to the address addr on the network net via the SOCKS5 proxy.
|
|
||||||
func (s *Socks5) Dial(network, addr string) (net.Conn, error) {
|
|
||||||
c, err := s.dial(network, s.addr)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[socks5]: dial to %s error: %s", s.addr, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := s.connect(c, addr, socks.CmdConnect); err != nil {
|
|
||||||
c.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Socks5) dial(network, addr string) (net.Conn, error) {
|
|
||||||
switch network {
|
|
||||||
case "tcp", "tcp6", "tcp4":
|
|
||||||
default:
|
|
||||||
return nil, errors.New("[socks5]: no support for connection type " + network)
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := s.dialer.Dial(network, s.addr)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[socks5]: dial to %s error: %s", s.addr, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
|
||||||
func (s *Socks5) DialUDP(network, addr string) (pc net.PacketConn, err error) {
|
|
||||||
c, err := s.dial("tcp", s.addr)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[socks5] dialudp dial tcp to %s error: %s", s.addr, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var uAddr socks.Addr
|
|
||||||
if uAddr, err = s.connect(c, addr, socks.CmdUDPAssociate); err != nil {
|
|
||||||
c.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := pool.GetBuffer(socks.MaxAddrLen)
|
|
||||||
defer pool.PutBuffer(buf)
|
|
||||||
|
|
||||||
uAddress := uAddr.String()
|
|
||||||
h, p, _ := net.SplitHostPort(uAddress)
|
|
||||||
// if returned bind ip is unspecified
|
|
||||||
if ip, err := netip.ParseAddr(h); err == nil && ip.IsUnspecified() {
|
|
||||||
// indicate using conventional addr
|
|
||||||
h, _, _ = net.SplitHostPort(s.addr)
|
|
||||||
uAddress = net.JoinHostPort(h, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
pc, err = s.dialer.DialUDP(network, uAddress)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[socks5] dialudp to %s error: %s", uAddress, err)
|
|
||||||
return nil, 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) {
|
|
||||||
// the size here is just an estimate
|
|
||||||
buf := pool.GetBuffer(socks.MaxAddrLen)
|
|
||||||
defer pool.PutBuffer(buf)
|
|
||||||
|
|
||||||
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 {
|
|
||||||
buf = append(buf, 1 /* num auth methods */, socks.AuthNone)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := conn.Write(buf); err != nil {
|
|
||||||
return addr, errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
|
||||||
return addr, errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
|
||||||
}
|
|
||||||
if buf[0] != Version {
|
|
||||||
return addr, errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
|
|
||||||
}
|
|
||||||
if buf[1] == 0xff {
|
|
||||||
return addr, errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf[1] == socks.AuthPassword {
|
|
||||||
buf = buf[:0]
|
|
||||||
buf = append(buf, 1 /* password protocol version */)
|
|
||||||
buf = append(buf, uint8(len(s.user)))
|
|
||||||
buf = append(buf, s.user...)
|
|
||||||
buf = append(buf, uint8(len(s.password)))
|
|
||||||
buf = append(buf, s.password...)
|
|
||||||
|
|
||||||
if _, err := conn.Write(buf); err != nil {
|
|
||||||
return addr, errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
|
||||||
return addr, errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf[1] != 0 {
|
|
||||||
return addr, errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = buf[:0]
|
|
||||||
buf = append(buf, Version, cmd, 0 /* reserved */)
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
|
|
||||||
// read VER REP RSV
|
|
||||||
if _, err := io.ReadFull(conn, buf[:3]); err != nil {
|
|
||||||
return addr, errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
failure := "unknown error"
|
|
||||||
if int(buf[1]) < len(socks.Errors) {
|
|
||||||
failure = socks.Errors[buf[1]].Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(failure) > 0 {
|
|
||||||
return addr, errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
|
|
||||||
}
|
|
||||||
|
|
||||||
return socks.ReadAddr(conn)
|
|
||||||
}
|
|
@ -1,40 +1,42 @@
|
|||||||
package socks5
|
package socks5
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
"github.com/nadoo/glider/common/log"
|
||||||
"github.com/nadoo/glider/pkg/socks"
|
"github.com/nadoo/glider/common/socks"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PktConn .
|
// PktConn .
|
||||||
type PktConn struct {
|
type PktConn struct {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
|
|
||||||
|
writeAddr net.Addr // write to and read from addr
|
||||||
|
|
||||||
|
tgtAddr socks.Addr
|
||||||
|
tgtHeader bool
|
||||||
|
|
||||||
ctrlConn net.Conn // tcp control conn
|
ctrlConn net.Conn // tcp control conn
|
||||||
writeTo net.Addr // write to and read from addr
|
|
||||||
target socks.Addr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPktConn returns a PktConn, the writeAddr must be *net.UDPAddr or *net.UnixAddr.
|
// NewPktConn returns a PktConn
|
||||||
func NewPktConn(c net.PacketConn, writeAddr net.Addr, targetAddr socks.Addr, ctrlConn net.Conn) *PktConn {
|
func NewPktConn(c net.PacketConn, writeAddr net.Addr, tgtAddr socks.Addr, tgtHeader bool, ctrlConn net.Conn) *PktConn {
|
||||||
pc := &PktConn{
|
pc := &PktConn{
|
||||||
PacketConn: c,
|
PacketConn: c,
|
||||||
writeTo: writeAddr,
|
writeAddr: writeAddr,
|
||||||
target: targetAddr,
|
tgtAddr: tgtAddr,
|
||||||
ctrlConn: ctrlConn,
|
tgtHeader: tgtHeader,
|
||||||
}
|
ctrlConn: ctrlConn}
|
||||||
|
|
||||||
if ctrlConn != nil {
|
if ctrlConn != nil {
|
||||||
go func() {
|
go func() {
|
||||||
buf := pool.GetBuffer(1)
|
buf := make([]byte, 1)
|
||||||
defer pool.PutBuffer(buf)
|
|
||||||
for {
|
for {
|
||||||
_, err := ctrlConn.Read(buf)
|
_, err := ctrlConn.Read(buf)
|
||||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// log.F("[socks5] dialudp udp associate end")
|
log.F("[socks5] dialudp udp associate end")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -43,78 +45,48 @@ func NewPktConn(c net.PacketConn, writeAddr net.Addr, targetAddr socks.Addr, ctr
|
|||||||
return pc
|
return pc
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFrom overrides the original function from net.PacketConn.
|
// ReadFrom overrides the original function from net.PacketConn
|
||||||
func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
n, _, target, err := pc.readFrom(b)
|
if !pc.tgtHeader {
|
||||||
return n, target, err
|
return pc.PacketConn.ReadFrom(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pc *PktConn) readFrom(b []byte) (int, net.Addr, net.Addr, error) {
|
|
||||||
buf := pool.GetBuffer(len(b))
|
|
||||||
defer pool.PutBuffer(buf)
|
|
||||||
|
|
||||||
|
buf := make([]byte, len(b))
|
||||||
n, raddr, err := pc.PacketConn.ReadFrom(buf)
|
n, raddr, err := pc.PacketConn.ReadFrom(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, raddr, nil, err
|
return n, raddr, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if n < 3 {
|
// https://tools.ietf.org/html/rfc1928#section-7
|
||||||
return n, raddr, nil, errors.New("not enough size to get addr")
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc1928#section-7
|
|
||||||
// +----+------+------+----------+----------+----------+
|
// +----+------+------+----------+----------+----------+
|
||||||
// |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
|
// |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
|
||||||
// +----+------+------+----------+----------+----------+
|
// +----+------+------+----------+----------+----------+
|
||||||
// | 2 | 1 | 1 | Variable | 2 | Variable |
|
// | 2 | 1 | 1 | Variable | 2 | Variable |
|
||||||
// +----+------+------+----------+----------+----------+
|
// +----+------+------+----------+----------+----------+
|
||||||
tgtAddr := socks.SplitAddr(buf[3:n])
|
tgtAddr := socks.SplitAddr(buf[3:])
|
||||||
if tgtAddr == nil {
|
copy(b, buf[3+len(tgtAddr):])
|
||||||
return n, raddr, nil, errors.New("can not get target addr")
|
|
||||||
|
//test
|
||||||
|
if pc.writeAddr == nil {
|
||||||
|
pc.writeAddr = raddr
|
||||||
}
|
}
|
||||||
|
|
||||||
target, err := net.ResolveUDPAddr("udp", tgtAddr.String())
|
if pc.tgtAddr == nil {
|
||||||
if err != nil {
|
pc.tgtAddr = tgtAddr
|
||||||
return n, raddr, nil, errors.New("wrong target addr")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if pc.writeTo == nil {
|
return n - len(tgtAddr) - 3, raddr, err
|
||||||
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])
|
|
||||||
return n, raddr, target, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteTo overrides the original function from net.PacketConn.
|
// WriteTo overrides the original function from net.PacketConn
|
||||||
func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||||
target := pc.target
|
if !pc.tgtHeader {
|
||||||
if addr != nil {
|
return pc.PacketConn.WriteTo(b, addr)
|
||||||
target = socks.ParseAddr(addr.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if target == nil {
|
buf := append([]byte{0, 0, 0}, pc.tgtAddr...)
|
||||||
return 0, errors.New("invalid addr")
|
buf = append(buf, b[:]...)
|
||||||
}
|
return pc.PacketConn.WriteTo(buf, pc.writeAddr)
|
||||||
|
|
||||||
buf := pool.GetBytesBuffer()
|
|
||||||
defer pool.PutBytesBuffer(buf)
|
|
||||||
|
|
||||||
buf.Write([]byte{0, 0, 0})
|
|
||||||
tgtLen, _ := buf.Write(target)
|
|
||||||
buf.Write(b)
|
|
||||||
|
|
||||||
n, err := pc.PacketConn.WriteTo(buf.Bytes(), pc.writeTo)
|
|
||||||
if n > tgtLen+3 {
|
|
||||||
return n - tgtLen - 3, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close .
|
// Close .
|
||||||
|
@ -1,299 +0,0 @@
|
|||||||
package socks5
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
|
||||||
"github.com/nadoo/glider/pkg/socks"
|
|
||||||
"github.com/nadoo/glider/proxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenAndServe serves socks5 requests.
|
|
||||||
func (s *Socks5) ListenAndServe() {
|
|
||||||
go s.ListenAndServeUDP()
|
|
||||||
s.ListenAndServeTCP()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenAndServeTCP listen and serve on tcp port.
|
|
||||||
func (s *Socks5) ListenAndServeTCP() {
|
|
||||||
l, err := net.Listen("tcp", s.addr)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("[socks5] failed to listen on %s: %v", s.addr, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.F("[socks5] listening TCP on %s", s.addr)
|
|
||||||
|
|
||||||
for {
|
|
||||||
c, err := l.Accept()
|
|
||||||
if err != nil {
|
|
||||||
log.F("[socks5] failed to accept: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
go s.Serve(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve serves a connection.
|
|
||||||
func (s *Socks5) Serve(c net.Conn) {
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
if c, ok := c.(*net.TCPConn); ok {
|
|
||||||
c.SetKeepAlive(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
tgt, err := s.handshake(c)
|
|
||||||
if err != nil {
|
|
||||||
// UDP: keep the connection until disconnect then free the UDP socket
|
|
||||||
if err == socks.Errors[9] {
|
|
||||||
buf := pool.GetBuffer(1)
|
|
||||||
defer pool.PutBuffer(buf)
|
|
||||||
// block here
|
|
||||||
for {
|
|
||||||
_, err := c.Read(buf)
|
|
||||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// log.F("[socks5] servetcp udp associate end")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.F("[socks5] failed in handshake with %s: %v", c.RemoteAddr(), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rc, dialer, err := s.proxy.Dial("tcp", tgt.String())
|
|
||||||
if err != nil {
|
|
||||||
log.F("[socks5] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer rc.Close()
|
|
||||||
|
|
||||||
log.F("[socks5] %s <-> %s via %s", c.RemoteAddr(), tgt, dialer.Addr())
|
|
||||||
|
|
||||||
if err = proxy.Relay(c, rc); err != nil {
|
|
||||||
log.F("[socks5] %s <-> %s via %s, relay error: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
|
||||||
// record remote conn failure only
|
|
||||||
if !strings.Contains(err.Error(), s.addr) {
|
|
||||||
s.proxy.Record(dialer, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenAndServeUDP serves udp requests.
|
|
||||||
func (s *Socks5) ListenAndServeUDP() {
|
|
||||||
lc, err := net.ListenPacket("udp", s.addr)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("[socks5] failed to listen on UDP %s: %v", s.addr, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer lc.Close()
|
|
||||||
|
|
||||||
log.F("[socks5] listening UDP on %s", s.addr)
|
|
||||||
|
|
||||||
s.ServePacket(lc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServePacket implements proxy.PacketServer.
|
|
||||||
func (s *Socks5) ServePacket(pc net.PacketConn) {
|
|
||||||
for {
|
|
||||||
c := NewPktConn(pc, nil, nil, nil)
|
|
||||||
buf := pool.GetBuffer(proxy.UDPBufSize)
|
|
||||||
|
|
||||||
n, srcAddr, dstAddr, err := c.readFrom(buf)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[socks5u] remote read error: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var session *Session
|
|
||||||
sessionKey := srcAddr.String()
|
|
||||||
|
|
||||||
v, ok := nm.Load(sessionKey)
|
|
||||||
if !ok || v == nil {
|
|
||||||
session = newSession(sessionKey, srcAddr, dstAddr, c)
|
|
||||||
nm.Store(sessionKey, session)
|
|
||||||
go s.serveSession(session)
|
|
||||||
} else {
|
|
||||||
session = v.(*Session)
|
|
||||||
}
|
|
||||||
|
|
||||||
session.msgCh <- message{dstAddr, buf[:n]}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Socks5) serveSession(session *Session) {
|
|
||||||
dstPC, dialer, err := s.proxy.DialUDP("udp", session.srcPC.target.String())
|
|
||||||
if err != nil {
|
|
||||||
log.F("[socks5u] remote dial error: %v", err)
|
|
||||||
nm.Delete(session.key)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer dstPC.Close()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
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.target, dialer.Addr())
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case msg := <-session.msgCh:
|
|
||||||
_, err = dstPC.WriteTo(msg.msg, msg.dst)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[socks5u] writeTo %s error: %v", msg.dst, err)
|
|
||||||
}
|
|
||||||
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 message
|
|
||||||
finCh chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSession(key string, src, dst net.Addr, srcPC *PktConn) *Session {
|
|
||||||
return &Session{key, src, dst, srcPC, make(chan message, 32), make(chan struct{})}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handshake fast-tracks SOCKS initialization to get target address to connect.
|
|
||||||
func (s *Socks5) handshake(c net.Conn) (socks.Addr, error) {
|
|
||||||
// Read RFC 1928 for request and reply structure and sizes
|
|
||||||
buf := pool.GetBuffer(socks.MaxAddrLen)
|
|
||||||
defer pool.PutBuffer(buf)
|
|
||||||
|
|
||||||
// read VER, NMETHODS, METHODS
|
|
||||||
if _, err := io.ReadFull(c, buf[:2]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
nmethods := buf[1]
|
|
||||||
if _, err := io.ReadFull(c, buf[:nmethods]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// write VER METHOD
|
|
||||||
if s.user != "" && s.password != "" {
|
|
||||||
_, err := c.Write([]byte{Version, socks.AuthPassword})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.ReadFull(c, buf[:2])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get username
|
|
||||||
userLen := int(buf[1])
|
|
||||||
if userLen <= 0 {
|
|
||||||
c.Write([]byte{1, 1})
|
|
||||||
return nil, errors.New("auth failed: wrong username length")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(c, buf[:userLen]); err != nil {
|
|
||||||
return nil, errors.New("auth failed: cannot get username")
|
|
||||||
}
|
|
||||||
user := string(buf[:userLen])
|
|
||||||
|
|
||||||
// Get password
|
|
||||||
_, err = c.Read(buf[:1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("auth failed: cannot get password len")
|
|
||||||
}
|
|
||||||
|
|
||||||
passLen := int(buf[0])
|
|
||||||
if passLen <= 0 {
|
|
||||||
c.Write([]byte{1, 1})
|
|
||||||
return nil, errors.New("auth failed: wrong password length")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.ReadFull(c, buf[:passLen])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("auth failed: cannot get password")
|
|
||||||
}
|
|
||||||
pass := string(buf[:passLen])
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
if user != s.user || pass != s.password {
|
|
||||||
_, err = c.Write([]byte{1, 1})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return nil, errors.New("auth failed, authinfo: " + user + ":" + pass)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Response auth state
|
|
||||||
_, err = c.Write([]byte{1, 0})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if _, err := c.Write([]byte{Version, socks.AuthNone}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// read VER CMD RSV ATYP DST.ADDR DST.PORT
|
|
||||||
if _, err := io.ReadFull(c, buf[:3]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cmd := buf[1]
|
|
||||||
addr, err := socks.ReadAddr(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch cmd {
|
|
||||||
case socks.CmdConnect:
|
|
||||||
_, 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]
|
|
||||||
}
|
|
||||||
err = socks.Errors[9]
|
|
||||||
default:
|
|
||||||
return nil, socks.Errors[7]
|
|
||||||
}
|
|
||||||
|
|
||||||
return addr, err // skip VER, CMD, RSV fields
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
// https://www.rfc-editor.org/rfc/rfc1928
|
// https://tools.ietf.org/html/rfc1928
|
||||||
|
|
||||||
// socks5 client:
|
// socks5 client:
|
||||||
// https://github.com/golang/net/tree/master/proxy
|
// https://github.com/golang/net/tree/master/proxy
|
||||||
@ -10,9 +10,17 @@
|
|||||||
package socks5
|
package socks5
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
"github.com/nadoo/glider/common/conn"
|
||||||
|
"github.com/nadoo/glider/common/log"
|
||||||
|
"github.com/nadoo/glider/common/socks"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,7 +36,12 @@ type Socks5 struct {
|
|||||||
password string
|
password string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSocks5 returns a Proxy that makes SOCKS v5 connections to the given address.
|
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)
|
// with an optional username and password. (RFC 1928)
|
||||||
func NewSocks5(s string, d proxy.Dialer, p proxy.Proxy) (*Socks5, error) {
|
func NewSocks5(s string, d proxy.Dialer, p proxy.Proxy) (*Socks5, error) {
|
||||||
u, err := url.Parse(s)
|
u, err := url.Parse(s)
|
||||||
@ -52,9 +65,464 @@ func NewSocks5(s string, d proxy.Dialer, p proxy.Proxy) (*Socks5, error) {
|
|||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
// NewSocks5Dialer returns a socks5 proxy dialer.
|
||||||
proxy.AddUsage("socks5", `
|
func NewSocks5Dialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||||
Socks5 scheme:
|
return NewSocks5(s, d, nil)
|
||||||
socks5://[user:pass@]host:port
|
}
|
||||||
`)
|
|
||||||
|
// NewSocks5Server returns a socks5 proxy server.
|
||||||
|
func NewSocks5Server(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||||
|
return NewSocks5(s, nil, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe serves socks5 requests.
|
||||||
|
func (s *Socks5) ListenAndServe() {
|
||||||
|
go s.ListenAndServeUDP()
|
||||||
|
s.ListenAndServeTCP()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServeTCP listen and serve on tcp port.
|
||||||
|
func (s *Socks5) ListenAndServeTCP() {
|
||||||
|
l, err := net.Listen("tcp", s.addr)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[socks5] failed to listen on %s: %v", s.addr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.F("[socks5] listening TCP on %s", s.addr)
|
||||||
|
|
||||||
|
for {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.F("[socks5] failed to accept: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
go s.Serve(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve serves a connection.
|
||||||
|
func (s *Socks5) Serve(cc net.Conn) {
|
||||||
|
defer cc.Close()
|
||||||
|
|
||||||
|
var c *conn.Conn
|
||||||
|
switch ccc := cc.(type) {
|
||||||
|
case *net.TCPConn:
|
||||||
|
ccc.SetKeepAlive(true)
|
||||||
|
c = conn.NewConn(ccc)
|
||||||
|
case *conn.Conn:
|
||||||
|
c = ccc
|
||||||
|
}
|
||||||
|
|
||||||
|
tgt, err := s.handshake(c)
|
||||||
|
if err != nil {
|
||||||
|
// UDP: keep the connection until disconnect then free the UDP socket
|
||||||
|
if err == socks.Errors[9] {
|
||||||
|
buf := make([]byte, 1)
|
||||||
|
// block here
|
||||||
|
for {
|
||||||
|
_, err := c.Read(buf)
|
||||||
|
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// log.F("[socks5] servetcp udp associate end")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.F("[socks5] failed in handshake with %s: %v", c.RemoteAddr(), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rc, p, err := s.proxy.Dial("tcp", tgt.String())
|
||||||
|
if err != nil {
|
||||||
|
log.F("[socks5] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, p, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
log.F("[socks5] %s <-> %s via %s", c.RemoteAddr(), tgt, p)
|
||||||
|
|
||||||
|
_, _, err = conn.Relay(c, rc)
|
||||||
|
if err != nil {
|
||||||
|
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||||
|
return // ignore i/o timeout
|
||||||
|
}
|
||||||
|
log.F("[socks5] relay error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServeUDP serves udp requests.
|
||||||
|
func (s *Socks5) ListenAndServeUDP() {
|
||||||
|
lc, err := net.ListenPacket("udp", s.addr)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[socks5-udp] failed to listen on %s: %v", s.addr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer lc.Close()
|
||||||
|
|
||||||
|
log.F("[socks5-udp] listening UDP on %s", s.addr)
|
||||||
|
|
||||||
|
var nm sync.Map
|
||||||
|
buf := make([]byte, conn.UDPBufSize)
|
||||||
|
|
||||||
|
for {
|
||||||
|
c := NewPktConn(lc, nil, nil, true, nil)
|
||||||
|
|
||||||
|
n, raddr, err := c.ReadFrom(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[socks5-udp] remote read error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var pc *PktConn
|
||||||
|
v, ok := nm.Load(raddr.String())
|
||||||
|
if !ok && v == nil {
|
||||||
|
if c.tgtAddr == nil {
|
||||||
|
log.F("[socks5-udp] can not get target address, not a valid request")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
lpc, nextHop, err := s.proxy.DialUDP("udp", c.tgtAddr.String())
|
||||||
|
if err != nil {
|
||||||
|
log.F("[socks5-udp] remote dial error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pc = NewPktConn(lpc, nextHop, nil, false, nil)
|
||||||
|
nm.Store(raddr.String(), pc)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
conn.RelayUDP(c, raddr, pc, 2*time.Minute)
|
||||||
|
pc.Close()
|
||||||
|
nm.Delete(raddr.String())
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.F("[socks5-udp] %s <-> %s", raddr, c.tgtAddr)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
pc = v.(*PktConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = pc.WriteTo(buf[:n], pc.writeAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[socks5-udp] remote write error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.F("[socks5-udp] %s <-> %s", raddr, c.tgtAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr returns forwarder's address.
|
||||||
|
func (s *Socks5) Addr() string {
|
||||||
|
if s.addr == "" {
|
||||||
|
return s.dialer.Addr()
|
||||||
|
}
|
||||||
|
return s.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the address addr on the network net via the SOCKS5 proxy.
|
||||||
|
func (s *Socks5) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "tcp", "tcp6", "tcp4":
|
||||||
|
default:
|
||||||
|
return nil, errors.New("[socks5]: no support for connection type " + network)
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := s.dialer.Dial(network, s.addr)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[socks5]: dial to %s error: %s", s.addr, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.connect(c, addr); err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialUDP connects to the given address via the proxy.
|
||||||
|
func (s *Socks5) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
|
||||||
|
c, err := s.dialer.Dial("tcp", s.addr)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[socks5] dialudp dial tcp to %s error: %s", s.addr, err)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// send VER, NMETHODS, METHODS
|
||||||
|
c.Write([]byte{Version, 1, 0})
|
||||||
|
|
||||||
|
buf := make([]byte, socks.MaxAddrLen)
|
||||||
|
// read VER METHOD
|
||||||
|
if _, err := io.ReadFull(c, buf[:2]); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dstAddr := socks.ParseAddr(addr)
|
||||||
|
// write VER CMD RSV ATYP DST.ADDR DST.PORT
|
||||||
|
c.Write(append([]byte{Version, socks.CmdUDPAssociate, 0}, dstAddr...))
|
||||||
|
|
||||||
|
// read VER REP RSV ATYP BND.ADDR BND.PORT
|
||||||
|
if _, err := io.ReadFull(c, buf[:3]); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rep := buf[1]
|
||||||
|
if rep != 0 {
|
||||||
|
log.F("[socks5] server reply: %d, not succeeded", rep)
|
||||||
|
return nil, nil, errors.New("server connect failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
uAddr, err := socks.ReadAddrBuf(c, buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pc, nextHop, err := s.dialer.DialUDP(network, uAddr.String())
|
||||||
|
if err != nil {
|
||||||
|
log.F("[socks5] dialudp to %s error: %s", uAddr.String(), err)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pkc := NewPktConn(pc, nextHop, dstAddr, true, c)
|
||||||
|
return pkc, nextHop, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect takes an existing connection to a socks5 proxy server,
|
||||||
|
// and commands the server to extend that connection to target,
|
||||||
|
// which must be a canonical address with a host and port.
|
||||||
|
func (s *Socks5) connect(conn net.Conn, target string) error {
|
||||||
|
host, portStr, err := net.SplitHostPort(target)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("proxy: failed to parse port number: " + portStr)
|
||||||
|
}
|
||||||
|
if port < 1 || port > 0xffff {
|
||||||
|
return errors.New("proxy: port number out of range: " + portStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the size here is just an estimate
|
||||||
|
buf := make([]byte, 0, 6+len(host))
|
||||||
|
|
||||||
|
buf = append(buf, Version)
|
||||||
|
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
|
||||||
|
buf = append(buf, 2 /* num auth methods */, socks.AuthNone, socks.AuthPassword)
|
||||||
|
} else {
|
||||||
|
buf = append(buf, 1 /* num auth methods */, socks.AuthNone)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := conn.Write(buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
if buf[0] != Version {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
|
||||||
|
}
|
||||||
|
if buf[1] == 0xff {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[1] == socks.AuthPassword {
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = append(buf, 1 /* password protocol version */)
|
||||||
|
buf = append(buf, uint8(len(s.user)))
|
||||||
|
buf = append(buf, s.user...)
|
||||||
|
buf = append(buf, uint8(len(s.password)))
|
||||||
|
buf = append(buf, s.password...)
|
||||||
|
|
||||||
|
if _, err := conn.Write(buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[1] != 0 {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = append(buf, Version, socks.CmdConnect, 0 /* reserved */)
|
||||||
|
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
buf = append(buf, socks.ATypIP4)
|
||||||
|
ip = ip4
|
||||||
|
} else {
|
||||||
|
buf = append(buf, socks.ATypIP6)
|
||||||
|
}
|
||||||
|
buf = append(buf, ip...)
|
||||||
|
} else {
|
||||||
|
if len(host) > 255 {
|
||||||
|
return errors.New("proxy: destination hostname too long: " + host)
|
||||||
|
}
|
||||||
|
buf = append(buf, socks.ATypDomain)
|
||||||
|
buf = append(buf, byte(len(host)))
|
||||||
|
buf = append(buf, host...)
|
||||||
|
}
|
||||||
|
buf = append(buf, byte(port>>8), byte(port))
|
||||||
|
|
||||||
|
if _, err := conn.Write(buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
failure := "unknown error"
|
||||||
|
if int(buf[1]) < len(socks.Errors) {
|
||||||
|
failure = socks.Errors[buf[1]].Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(failure) > 0 {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesToDiscard := 0
|
||||||
|
switch buf[3] {
|
||||||
|
case socks.ATypIP4:
|
||||||
|
bytesToDiscard = net.IPv4len
|
||||||
|
case socks.ATypIP6:
|
||||||
|
bytesToDiscard = net.IPv6len
|
||||||
|
case socks.ATypDomain:
|
||||||
|
_, err := io.ReadFull(conn, buf[:1])
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
bytesToDiscard = int(buf[0])
|
||||||
|
default:
|
||||||
|
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cap(buf) < bytesToDiscard {
|
||||||
|
buf = make([]byte, bytesToDiscard)
|
||||||
|
} else {
|
||||||
|
buf = buf[:bytesToDiscard]
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(conn, buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also need to discard the port number
|
||||||
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handshake fast-tracks SOCKS initialization to get target address to connect.
|
||||||
|
func (s *Socks5) handshake(rw io.ReadWriter) (socks.Addr, error) {
|
||||||
|
// Read RFC 1928 for request and reply structure and sizes
|
||||||
|
buf := make([]byte, socks.MaxAddrLen)
|
||||||
|
// read VER, NMETHODS, METHODS
|
||||||
|
if _, err := io.ReadFull(rw, buf[:2]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nmethods := buf[1]
|
||||||
|
if _, err := io.ReadFull(rw, buf[:nmethods]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// write VER METHOD
|
||||||
|
if s.user != "" && s.password != "" {
|
||||||
|
_, err := rw.Write([]byte{Version, socks.AuthPassword})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.ReadFull(rw, buf[:2])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get username
|
||||||
|
userLen := int(buf[1])
|
||||||
|
if userLen <= 0 {
|
||||||
|
rw.Write([]byte{1, 1})
|
||||||
|
return nil, errors.New("auth failed: wrong username length")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(rw, buf[:userLen]); err != nil {
|
||||||
|
return nil, errors.New("auth failed: cannot get username")
|
||||||
|
}
|
||||||
|
user := string(buf[:userLen])
|
||||||
|
|
||||||
|
// Get password
|
||||||
|
_, err = rw.Read(buf[:1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("auth failed: cannot get password len")
|
||||||
|
}
|
||||||
|
|
||||||
|
passLen := int(buf[0])
|
||||||
|
if passLen <= 0 {
|
||||||
|
rw.Write([]byte{1, 1})
|
||||||
|
return nil, errors.New("auth failed: wrong password length")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.ReadFull(rw, buf[:passLen])
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("auth failed: cannot get password")
|
||||||
|
}
|
||||||
|
pass := string(buf[:passLen])
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
if user != s.user || pass != s.password {
|
||||||
|
_, err = rw.Write([]byte{1, 1})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, errors.New("auth failed, authinfo: " + user + ":" + pass)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response auth state
|
||||||
|
_, err = rw.Write([]byte{1, 0})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if _, err := rw.Write([]byte{Version, socks.AuthNone}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// read VER CMD RSV ATYP DST.ADDR DST.PORT
|
||||||
|
if _, err := io.ReadFull(rw, buf[:3]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmd := buf[1]
|
||||||
|
addr, err := socks.ReadAddrBuf(rw, buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch cmd {
|
||||||
|
case socks.CmdConnect:
|
||||||
|
_, err = rw.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) // SOCKS v5, reply succeeded
|
||||||
|
case socks.CmdUDPAssociate:
|
||||||
|
listenAddr := socks.ParseAddr(rw.(net.Conn).LocalAddr().String())
|
||||||
|
_, err = rw.Write(append([]byte{5, 0, 0}, listenAddr...)) // SOCKS v5, reply succeeded
|
||||||
|
if err != nil {
|
||||||
|
return nil, socks.Errors[7]
|
||||||
|
}
|
||||||
|
err = socks.Errors[9]
|
||||||
|
default:
|
||||||
|
return nil, socks.Errors[7]
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr, err // skip VER, CMD, RSV fields
|
||||||
}
|
}
|
||||||
|
@ -1,143 +0,0 @@
|
|||||||
package cipher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/proxy/ss/cipher/shadowaead"
|
|
||||||
"github.com/nadoo/glider/proxy/ss/cipher/shadowstream"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Cipher interface.
|
|
||||||
type Cipher interface {
|
|
||||||
StreamConnCipher
|
|
||||||
PacketConnCipher
|
|
||||||
}
|
|
||||||
|
|
||||||
// StreamConnCipher is the stream connection cipher.
|
|
||||||
type StreamConnCipher interface {
|
|
||||||
StreamConn(net.Conn) net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
// PacketConnCipher is the packet connection cipher.
|
|
||||||
type PacketConnCipher interface {
|
|
||||||
PacketConn(net.PacketConn) net.PacketConn
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrCipherNotSupported occurs when a cipher is not supported (likely because of security concerns).
|
|
||||||
var ErrCipherNotSupported = errors.New("cipher not supported")
|
|
||||||
|
|
||||||
// List of AEAD ciphers: key size in bytes and constructor
|
|
||||||
var aeadList = map[string]struct {
|
|
||||||
KeySize int
|
|
||||||
New func([]byte) (shadowaead.Cipher, error)
|
|
||||||
}{
|
|
||||||
"AEAD_AES_128_GCM": {16, shadowaead.AESGCM},
|
|
||||||
"AEAD_AES_192_GCM": {24, shadowaead.AESGCM},
|
|
||||||
"AEAD_AES_256_GCM": {32, shadowaead.AESGCM},
|
|
||||||
"AEAD_CHACHA20_POLY1305": {32, shadowaead.Chacha20Poly1305},
|
|
||||||
|
|
||||||
// http://shadowsocks.org/en/spec/AEAD-Ciphers.html
|
|
||||||
// not listed in spec
|
|
||||||
"AEAD_XCHACHA20_POLY1305": {32, shadowaead.XChacha20Poly1305},
|
|
||||||
}
|
|
||||||
|
|
||||||
// List of stream ciphers: key size in bytes and constructor
|
|
||||||
var streamList = map[string]struct {
|
|
||||||
KeySize int
|
|
||||||
New func(key []byte) (shadowstream.Cipher, error)
|
|
||||||
}{
|
|
||||||
"AES-128-CTR": {16, shadowstream.AESCTR},
|
|
||||||
"AES-192-CTR": {24, shadowstream.AESCTR},
|
|
||||||
"AES-256-CTR": {32, shadowstream.AESCTR},
|
|
||||||
"AES-128-CFB": {16, shadowstream.AESCFB},
|
|
||||||
"AES-192-CFB": {24, shadowstream.AESCFB},
|
|
||||||
"AES-256-CFB": {32, shadowstream.AESCFB},
|
|
||||||
"CHACHA20-IETF": {32, shadowstream.Chacha20IETF},
|
|
||||||
"XCHACHA20": {32, shadowstream.Xchacha20},
|
|
||||||
|
|
||||||
// http://shadowsocks.org/en/spec/Stream-Ciphers.html
|
|
||||||
// marked as "DO NOT USE"
|
|
||||||
"CHACHA20": {32, shadowstream.ChaCha20},
|
|
||||||
"RC4-MD5": {16, shadowstream.RC4MD5},
|
|
||||||
}
|
|
||||||
|
|
||||||
// PickCipher returns a Cipher of the given name. Derive key from password if given key is empty.
|
|
||||||
func PickCipher(name string, key []byte, password string) (Cipher, error) {
|
|
||||||
name = strings.ToUpper(name)
|
|
||||||
|
|
||||||
switch name {
|
|
||||||
case "DUMMY", "NONE":
|
|
||||||
return &dummy{}, nil
|
|
||||||
case "CHACHA20-IETF-POLY1305":
|
|
||||||
name = "AEAD_CHACHA20_POLY1305"
|
|
||||||
case "XCHACHA20-IETF-POLY1305":
|
|
||||||
name = "AEAD_XCHACHA20_POLY1305"
|
|
||||||
case "AES-128-GCM":
|
|
||||||
name = "AEAD_AES_128_GCM"
|
|
||||||
case "AES-192-GCM":
|
|
||||||
name = "AEAD_AES_192_GCM"
|
|
||||||
case "AES-256-GCM":
|
|
||||||
name = "AEAD_AES_256_GCM"
|
|
||||||
}
|
|
||||||
|
|
||||||
if choice, ok := aeadList[name]; ok {
|
|
||||||
if len(key) == 0 {
|
|
||||||
key = kdf(password, choice.KeySize)
|
|
||||||
}
|
|
||||||
if len(key) != choice.KeySize {
|
|
||||||
return nil, shadowaead.KeySizeError(choice.KeySize)
|
|
||||||
}
|
|
||||||
aead, err := choice.New(key)
|
|
||||||
return &aeadCipher{aead}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if choice, ok := streamList[name]; ok {
|
|
||||||
if len(key) == 0 {
|
|
||||||
key = kdf(password, choice.KeySize)
|
|
||||||
}
|
|
||||||
if len(key) != choice.KeySize {
|
|
||||||
return nil, shadowstream.KeySizeError(choice.KeySize)
|
|
||||||
}
|
|
||||||
ciph, err := choice.New(key)
|
|
||||||
return &streamCipher{ciph}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrCipherNotSupported
|
|
||||||
}
|
|
||||||
|
|
||||||
type aeadCipher struct{ shadowaead.Cipher }
|
|
||||||
|
|
||||||
func (aead *aeadCipher) StreamConn(c net.Conn) net.Conn { return shadowaead.NewConn(c, aead) }
|
|
||||||
func (aead *aeadCipher) PacketConn(c net.PacketConn) net.PacketConn {
|
|
||||||
return shadowaead.NewPacketConn(c, aead)
|
|
||||||
}
|
|
||||||
|
|
||||||
type streamCipher struct{ shadowstream.Cipher }
|
|
||||||
|
|
||||||
func (ciph *streamCipher) StreamConn(c net.Conn) net.Conn { return shadowstream.NewConn(c, ciph) }
|
|
||||||
func (ciph *streamCipher) PacketConn(c net.PacketConn) net.PacketConn {
|
|
||||||
return shadowstream.NewPacketConn(c, ciph)
|
|
||||||
}
|
|
||||||
|
|
||||||
// dummy cipher does not encrypt
|
|
||||||
type dummy struct{}
|
|
||||||
|
|
||||||
func (dummy) StreamConn(c net.Conn) net.Conn { return c }
|
|
||||||
func (dummy) PacketConn(c net.PacketConn) net.PacketConn { return c }
|
|
||||||
|
|
||||||
// key-derivation function from original Shadowsocks
|
|
||||||
func kdf(password string, keyLen int) []byte {
|
|
||||||
var b, prev []byte
|
|
||||||
h := md5.New()
|
|
||||||
for len(b) < keyLen {
|
|
||||||
h.Write(prev)
|
|
||||||
h.Write([]byte(password))
|
|
||||||
b = h.Sum(b)
|
|
||||||
prev = b[len(b)-h.Size():]
|
|
||||||
h.Reset()
|
|
||||||
}
|
|
||||||
return b[:keyLen]
|
|
||||||
}
|
|
@ -1,94 +0,0 @@
|
|||||||
package shadowaead
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/sha1"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
|
||||||
"golang.org/x/crypto/hkdf"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Cipher generates a pair of stream ciphers for encryption and decryption.
|
|
||||||
type Cipher interface {
|
|
||||||
KeySize() int
|
|
||||||
SaltSize() int
|
|
||||||
Encrypter(salt []byte) (cipher.AEAD, error)
|
|
||||||
Decrypter(salt []byte) (cipher.AEAD, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeySizeError is an error about the key size.
|
|
||||||
type KeySizeError int
|
|
||||||
|
|
||||||
func (e KeySizeError) Error() string {
|
|
||||||
return "key size error: need " + strconv.Itoa(int(e)) + " bytes"
|
|
||||||
}
|
|
||||||
|
|
||||||
func hkdfSHA1(secret, salt, info, outkey []byte) {
|
|
||||||
r := hkdf.New(sha1.New, secret, salt, info)
|
|
||||||
if _, err := io.ReadFull(r, outkey); err != nil {
|
|
||||||
panic(err) // should never happen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type metaCipher struct {
|
|
||||||
psk []byte
|
|
||||||
makeAEAD func(key []byte) (cipher.AEAD, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *metaCipher) KeySize() int { return len(a.psk) }
|
|
||||||
func (a *metaCipher) SaltSize() int {
|
|
||||||
if ks := a.KeySize(); ks > 16 {
|
|
||||||
return ks
|
|
||||||
}
|
|
||||||
return 16
|
|
||||||
}
|
|
||||||
func (a *metaCipher) Encrypter(salt []byte) (cipher.AEAD, error) {
|
|
||||||
subkey := make([]byte, a.KeySize())
|
|
||||||
hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey)
|
|
||||||
return a.makeAEAD(subkey)
|
|
||||||
}
|
|
||||||
func (a *metaCipher) Decrypter(salt []byte) (cipher.AEAD, error) {
|
|
||||||
subkey := make([]byte, a.KeySize())
|
|
||||||
hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey)
|
|
||||||
return a.makeAEAD(subkey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func aesGCM(key []byte) (cipher.AEAD, error) {
|
|
||||||
blk, err := aes.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return cipher.NewGCM(blk)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AESGCM creates a new Cipher with a pre-shared key. len(psk) must be
|
|
||||||
// one of 16, 24, or 32 to select AES-128/196/256-GCM.
|
|
||||||
func AESGCM(psk []byte) (Cipher, error) {
|
|
||||||
switch l := len(psk); l {
|
|
||||||
case 16, 24, 32: // AES 128/196/256
|
|
||||||
default:
|
|
||||||
return nil, aes.KeySizeError(l)
|
|
||||||
}
|
|
||||||
return &metaCipher{psk: psk, makeAEAD: aesGCM}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk)
|
|
||||||
// must be 32.
|
|
||||||
func Chacha20Poly1305(psk []byte) (Cipher, error) {
|
|
||||||
if len(psk) != chacha20poly1305.KeySize {
|
|
||||||
return nil, KeySizeError(chacha20poly1305.KeySize)
|
|
||||||
}
|
|
||||||
return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.New}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// XChacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk)
|
|
||||||
// must be 32.
|
|
||||||
func XChacha20Poly1305(psk []byte) (Cipher, error) {
|
|
||||||
if len(psk) != chacha20poly1305.KeySize {
|
|
||||||
return nil, KeySizeError(chacha20poly1305.KeySize)
|
|
||||||
}
|
|
||||||
return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.NewX}, nil
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
package shadowaead
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type streamConn struct {
|
|
||||||
net.Conn
|
|
||||||
Cipher
|
|
||||||
r *reader
|
|
||||||
w *writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConn wraps a stream-oriented net.Conn with cipher.
|
|
||||||
func NewConn(c net.Conn, ciph Cipher) net.Conn { return &streamConn{Conn: c, Cipher: ciph} }
|
|
||||||
|
|
||||||
func (c *streamConn) initReader() error {
|
|
||||||
salt := make([]byte, c.SaltSize())
|
|
||||||
if _, err := io.ReadFull(c.Conn, salt); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
aead, err := c.Decrypter(salt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.r = newReader(c.Conn, aead)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *streamConn) Read(b []byte) (int, error) {
|
|
||||||
if c.r == nil {
|
|
||||||
if err := c.initReader(); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c.r.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *streamConn) initWriter() error {
|
|
||||||
salt := make([]byte, c.SaltSize())
|
|
||||||
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
aead, err := c.Encrypter(salt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = c.Conn.Write(salt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.w = newWriter(c.Conn, aead)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *streamConn) Write(b []byte) (int, error) {
|
|
||||||
if c.w == nil {
|
|
||||||
if err := c.initWriter(); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c.w.Write(b)
|
|
||||||
}
|
|
@ -1,97 +0,0 @@
|
|||||||
package shadowaead
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrShortPacket means that the packet is too short for a valid encrypted packet.
|
|
||||||
var ErrShortPacket = errors.New("short packet")
|
|
||||||
|
|
||||||
var _zerononce [128]byte // read-only. 128 bytes is more than enough.
|
|
||||||
|
|
||||||
// Pack encrypts plaintext using Cipher with a randomly generated salt and
|
|
||||||
// returns a slice of dst containing the encrypted packet and any error occurred.
|
|
||||||
// Ensure len(dst) >= ciph.SaltSize() + len(plaintext) + aead.Overhead().
|
|
||||||
func Pack(dst, plaintext []byte, ciph Cipher) ([]byte, error) {
|
|
||||||
saltSize := ciph.SaltSize()
|
|
||||||
salt := dst[:saltSize]
|
|
||||||
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
aead, err := ciph.Encrypter(salt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(dst) < saltSize+len(plaintext)+aead.Overhead() {
|
|
||||||
return nil, io.ErrShortBuffer
|
|
||||||
}
|
|
||||||
b := aead.Seal(dst[saltSize:saltSize], _zerononce[:aead.NonceSize()], plaintext, nil)
|
|
||||||
return dst[:saltSize+len(b)], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unpack decrypts pkt using Cipher and returns a slice of dst containing the decrypted payload and any error occurred.
|
|
||||||
// Ensure len(dst) >= len(pkt) - aead.SaltSize() - aead.Overhead().
|
|
||||||
func Unpack(dst, pkt []byte, ciph Cipher) ([]byte, error) {
|
|
||||||
saltSize := ciph.SaltSize()
|
|
||||||
if len(pkt) < saltSize {
|
|
||||||
return nil, ErrShortPacket
|
|
||||||
}
|
|
||||||
salt := pkt[:saltSize]
|
|
||||||
aead, err := ciph.Decrypter(salt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(pkt) < saltSize+aead.Overhead() {
|
|
||||||
return nil, ErrShortPacket
|
|
||||||
}
|
|
||||||
if saltSize+len(dst)+aead.Overhead() < len(pkt) {
|
|
||||||
return nil, io.ErrShortBuffer
|
|
||||||
}
|
|
||||||
b, err := aead.Open(dst[:0], _zerononce[:aead.NonceSize()], pkt[saltSize:], nil)
|
|
||||||
return b, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type packetConn struct {
|
|
||||||
net.PacketConn
|
|
||||||
Cipher
|
|
||||||
sync.Mutex
|
|
||||||
buf []byte // write lock
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPacketConn wraps a net.PacketConn with cipher
|
|
||||||
func NewPacketConn(c net.PacketConn, ciph Cipher) net.PacketConn {
|
|
||||||
const maxPacketSize = 64 * 1024
|
|
||||||
return &packetConn{PacketConn: c, Cipher: ciph, buf: make([]byte, maxPacketSize)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteTo encrypts b and write to addr using the embedded PacketConn.
|
|
||||||
func (c *packetConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
|
||||||
c.Lock()
|
|
||||||
defer c.Unlock()
|
|
||||||
buf, err := Pack(c.buf, b, c)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
_, err = c.PacketConn.WriteTo(buf, addr)
|
|
||||||
return len(b), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFrom reads from the embedded PacketConn and decrypts into b.
|
|
||||||
func (c *packetConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
|
||||||
n, addr, err := c.PacketConn.ReadFrom(b)
|
|
||||||
if err != nil {
|
|
||||||
return n, addr, err
|
|
||||||
}
|
|
||||||
bb, err := Unpack(b[c.Cipher.SaltSize():], b[:n], c)
|
|
||||||
if err != nil {
|
|
||||||
return n, addr, err
|
|
||||||
}
|
|
||||||
copy(b, bb)
|
|
||||||
return len(bb), addr, err
|
|
||||||
}
|
|
@ -1,150 +0,0 @@
|
|||||||
// protocol:
|
|
||||||
// format: [encrypted payload length] [Overhead] [encrypted payload] [Overhead]
|
|
||||||
// sizes: 2 bytes, aead.Overhead() bytes, n bytes, aead.Overhead() bytes
|
|
||||||
// max(n): 0x3FFF
|
|
||||||
|
|
||||||
package shadowaead
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/cipher"
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
lenSize = 2
|
|
||||||
// max payload size: 16383 from ss-libev aead.c: CHUNK_SIZE_MASK
|
|
||||||
maxPayload = 0x3FFF
|
|
||||||
// buf size, shoud enough to save lenSize + aead.Overhead() + maxPayload + aead.Overhead()
|
|
||||||
bufSize = 17 << 10
|
|
||||||
)
|
|
||||||
|
|
||||||
type writer struct {
|
|
||||||
io.Writer
|
|
||||||
cipher.AEAD
|
|
||||||
nonce [32]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWriter wraps an io.Writer with AEAD encryption.
|
|
||||||
func NewWriter(w io.Writer, aead cipher.AEAD) io.Writer { return newWriter(w, aead) }
|
|
||||||
|
|
||||||
func newWriter(w io.Writer, aead cipher.AEAD) *writer {
|
|
||||||
return &writer{Writer: w, AEAD: aead}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write encrypts p and writes to the embedded io.Writer.
|
|
||||||
func (w *writer) Write(p []byte) (n int, err error) {
|
|
||||||
buf := pool.GetBuffer(bufSize)
|
|
||||||
defer pool.PutBuffer(buf)
|
|
||||||
|
|
||||||
nonce := w.nonce[:w.NonceSize()]
|
|
||||||
encLenSize := lenSize + w.Overhead()
|
|
||||||
for nw := maxPayload; n < len(p); n += nw {
|
|
||||||
if left := len(p) - n; left < maxPayload {
|
|
||||||
nw = left
|
|
||||||
}
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint16(buf[:lenSize], uint16(nw))
|
|
||||||
w.Seal(buf[:0], nonce, buf[:lenSize], nil)
|
|
||||||
increment(nonce)
|
|
||||||
|
|
||||||
w.Seal(buf[:encLenSize], nonce, p[n:n+nw], nil)
|
|
||||||
increment(nonce)
|
|
||||||
|
|
||||||
if _, err = w.Writer.Write(buf[:encLenSize+nw+w.Overhead()]); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type reader struct {
|
|
||||||
io.Reader
|
|
||||||
cipher.AEAD
|
|
||||||
nonce [32]byte
|
|
||||||
buf []byte
|
|
||||||
offset int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReader wraps an io.Reader with AEAD decryption.
|
|
||||||
func NewReader(r io.Reader, aead cipher.AEAD) io.Reader { return newReader(r, aead) }
|
|
||||||
|
|
||||||
func newReader(r io.Reader, aead cipher.AEAD) *reader {
|
|
||||||
return &reader{Reader: r, AEAD: aead}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: len(p) MUST >= max payload size + AEAD overhead.
|
|
||||||
func (r *reader) read(p []byte) (int, error) {
|
|
||||||
nonce := r.nonce[:r.NonceSize()]
|
|
||||||
|
|
||||||
// read encrypted lenSize + overhead
|
|
||||||
p = p[:lenSize+r.Overhead()]
|
|
||||||
if _, err := io.ReadFull(r.Reader, p); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// decrypt lenSize
|
|
||||||
_, err := r.Open(p[:0], nonce, p, nil)
|
|
||||||
increment(nonce)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// get payload size
|
|
||||||
size := int(binary.BigEndian.Uint16(p[:lenSize]))
|
|
||||||
|
|
||||||
// read encrypted payload + overhead
|
|
||||||
p = p[:size+r.Overhead()]
|
|
||||||
if _, err := io.ReadFull(r.Reader, p); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// decrypt payload
|
|
||||||
_, err = r.Open(p[:0], nonce, p, nil)
|
|
||||||
increment(nonce)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return size, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *reader) Read(p []byte) (int, error) {
|
|
||||||
if r.buf == nil {
|
|
||||||
if len(p) >= maxPayload+r.Overhead() {
|
|
||||||
return r.read(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := pool.GetBuffer(bufSize)
|
|
||||||
n, err := r.read(buf)
|
|
||||||
if err != nil {
|
|
||||||
pool.PutBuffer(buf)
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.buf = buf[:n]
|
|
||||||
r.offset = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
n := copy(p, r.buf[r.offset:])
|
|
||||||
r.offset += n
|
|
||||||
if r.offset == len(r.buf) {
|
|
||||||
pool.PutBuffer(r.buf)
|
|
||||||
r.buf = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// increment little-endian encoded unsigned integer b. Wrap around on overflow.
|
|
||||||
func increment(b []byte) {
|
|
||||||
for i := range b {
|
|
||||||
b[i]++
|
|
||||||
if b[i] != 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,143 +0,0 @@
|
|||||||
package shadowstream
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/md5"
|
|
||||||
"crypto/rc4"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/aead/chacha20"
|
|
||||||
"github.com/aead/chacha20/chacha"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Cipher generates a pair of stream ciphers for encryption and decryption.
|
|
||||||
type Cipher interface {
|
|
||||||
IVSize() int
|
|
||||||
Encrypter(iv []byte) cipher.Stream
|
|
||||||
Decrypter(iv []byte) cipher.Stream
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeySizeError is an error about the key size.
|
|
||||||
type KeySizeError int
|
|
||||||
|
|
||||||
func (e KeySizeError) Error() string {
|
|
||||||
return "key size error: need " + strconv.Itoa(int(e)) + " bytes"
|
|
||||||
}
|
|
||||||
|
|
||||||
// CTR mode
|
|
||||||
type ctrStream struct{ cipher.Block }
|
|
||||||
|
|
||||||
func (b *ctrStream) IVSize() int { return b.BlockSize() }
|
|
||||||
func (b *ctrStream) Decrypter(iv []byte) cipher.Stream { return b.Encrypter(iv) }
|
|
||||||
func (b *ctrStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCTR(b, iv) }
|
|
||||||
|
|
||||||
// AESCTR returns an aesctr cipher.
|
|
||||||
func AESCTR(key []byte) (Cipher, error) {
|
|
||||||
blk, err := aes.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &ctrStream{blk}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CFB mode
|
|
||||||
type cfbStream struct{ cipher.Block }
|
|
||||||
|
|
||||||
func (b *cfbStream) IVSize() int { return b.BlockSize() }
|
|
||||||
func (b *cfbStream) Decrypter(iv []byte) cipher.Stream { return cipher.NewCFBDecrypter(b, iv) }
|
|
||||||
func (b *cfbStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCFBEncrypter(b, iv) }
|
|
||||||
|
|
||||||
// AESCFB returns an aescfb cipher.
|
|
||||||
func AESCFB(key []byte) (Cipher, error) {
|
|
||||||
blk, err := aes.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &cfbStream{blk}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IETF-variant of chacha20
|
|
||||||
type chacha20ietfkey []byte
|
|
||||||
|
|
||||||
func (k chacha20ietfkey) IVSize() int { return chacha.INonceSize }
|
|
||||||
func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
|
|
||||||
func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream {
|
|
||||||
ciph, err := chacha20.NewCipher(iv, k)
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // should never happen
|
|
||||||
}
|
|
||||||
return ciph
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chacha20IETF returns a Chacha20IETF cipher.
|
|
||||||
func Chacha20IETF(key []byte) (Cipher, error) {
|
|
||||||
if len(key) != chacha.KeySize {
|
|
||||||
return nil, KeySizeError(chacha.KeySize)
|
|
||||||
}
|
|
||||||
return chacha20ietfkey(key), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// xchacha20
|
|
||||||
type xchacha20key []byte
|
|
||||||
|
|
||||||
func (k xchacha20key) IVSize() int { return chacha.XNonceSize }
|
|
||||||
func (k xchacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
|
|
||||||
func (k xchacha20key) Encrypter(iv []byte) cipher.Stream {
|
|
||||||
ciph, err := chacha20.NewCipher(iv, k)
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // should never happen
|
|
||||||
}
|
|
||||||
return ciph
|
|
||||||
}
|
|
||||||
|
|
||||||
// Xchacha20 returns a Xchacha20 cipher.
|
|
||||||
func Xchacha20(key []byte) (Cipher, error) {
|
|
||||||
if len(key) != chacha.KeySize {
|
|
||||||
return nil, KeySizeError(chacha.KeySize)
|
|
||||||
}
|
|
||||||
return xchacha20key(key), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// chacah20
|
|
||||||
type chacha20key []byte
|
|
||||||
|
|
||||||
func (k chacha20key) IVSize() int { return chacha.NonceSize }
|
|
||||||
func (k chacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
|
|
||||||
func (k chacha20key) Encrypter(iv []byte) cipher.Stream {
|
|
||||||
ciph, err := chacha20.NewCipher(iv, k)
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // should never happen
|
|
||||||
}
|
|
||||||
return ciph
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChaCha20 returns a ChaCha20 cipher.
|
|
||||||
func ChaCha20(key []byte) (Cipher, error) {
|
|
||||||
if len(key) != chacha.KeySize {
|
|
||||||
return nil, KeySizeError(chacha.KeySize)
|
|
||||||
}
|
|
||||||
return chacha20key(key), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// rc4md5
|
|
||||||
type rc4Md5Key []byte
|
|
||||||
|
|
||||||
func (k rc4Md5Key) IVSize() int { return 16 }
|
|
||||||
func (k rc4Md5Key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
|
|
||||||
func (k rc4Md5Key) Encrypter(iv []byte) cipher.Stream {
|
|
||||||
h := md5.New()
|
|
||||||
h.Write([]byte(k))
|
|
||||||
h.Write(iv)
|
|
||||||
rc4key := h.Sum(nil)
|
|
||||||
ciph, err := rc4.NewCipher(rc4key)
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // should never happen
|
|
||||||
}
|
|
||||||
return ciph
|
|
||||||
}
|
|
||||||
|
|
||||||
// RC4MD5 returns a RC4MD5 cipher.
|
|
||||||
func RC4MD5(key []byte) (Cipher, error) {
|
|
||||||
return rc4Md5Key(key), nil
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
package shadowstream
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type conn struct {
|
|
||||||
net.Conn
|
|
||||||
Cipher
|
|
||||||
r *reader
|
|
||||||
w *writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConn wraps a stream-oriented net.Conn with stream cipher encryption/decryption.
|
|
||||||
func NewConn(c net.Conn, ciph Cipher) net.Conn {
|
|
||||||
return &conn{Conn: c, Cipher: ciph}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) initReader() error {
|
|
||||||
if c.r == nil {
|
|
||||||
iv := make([]byte, c.IVSize())
|
|
||||||
if _, err := io.ReadFull(c.Conn, iv); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.r = &reader{Reader: c.Conn, Stream: c.Decrypter(iv)}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) Read(b []byte) (int, error) {
|
|
||||||
if c.r == nil {
|
|
||||||
if err := c.initReader(); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c.r.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) initWriter() error {
|
|
||||||
if c.w == nil {
|
|
||||||
iv := make([]byte, c.IVSize())
|
|
||||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := c.Conn.Write(iv); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.w = &writer{Writer: c.Conn, Stream: c.Encrypter(iv)}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) Write(b []byte) (int, error) {
|
|
||||||
if c.w == nil {
|
|
||||||
if err := c.initWriter(); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c.w.Write(b)
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
package shadowstream
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrShortPacket means the packet is too short to be a valid encrypted packet.
|
|
||||||
var ErrShortPacket = errors.New("short packet")
|
|
||||||
|
|
||||||
// Pack encrypts plaintext using stream cipher s and a random IV.
|
|
||||||
// Returns a slice of dst containing random IV and ciphertext.
|
|
||||||
// Ensure len(dst) >= s.IVSize() + len(plaintext).
|
|
||||||
func Pack(dst, plaintext []byte, s Cipher) ([]byte, error) {
|
|
||||||
if len(dst) < s.IVSize()+len(plaintext) {
|
|
||||||
return nil, io.ErrShortBuffer
|
|
||||||
}
|
|
||||||
iv := dst[:s.IVSize()]
|
|
||||||
_, err := io.ReadFull(rand.Reader, iv)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Encrypter(iv).XORKeyStream(dst[len(iv):], plaintext)
|
|
||||||
return dst[:len(iv)+len(plaintext)], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unpack decrypts pkt using stream cipher s.
|
|
||||||
// Returns a slice of dst containing decrypted plaintext.
|
|
||||||
func Unpack(dst, pkt []byte, s Cipher) ([]byte, error) {
|
|
||||||
if len(pkt) < s.IVSize() {
|
|
||||||
return nil, ErrShortPacket
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(dst) < len(pkt)-s.IVSize() {
|
|
||||||
return nil, io.ErrShortBuffer
|
|
||||||
}
|
|
||||||
iv := pkt[:s.IVSize()]
|
|
||||||
s.Decrypter(iv).XORKeyStream(dst, pkt[len(iv):])
|
|
||||||
return dst[:len(pkt)-len(iv)], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type packetConn struct {
|
|
||||||
net.PacketConn
|
|
||||||
Cipher
|
|
||||||
buf []byte
|
|
||||||
sync.Mutex // write lock
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPacketConn wraps a net.PacketConn with stream cipher encryption/decryption.
|
|
||||||
func NewPacketConn(c net.PacketConn, ciph Cipher) net.PacketConn {
|
|
||||||
return &packetConn{PacketConn: c, Cipher: ciph, buf: make([]byte, 64*1024)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *packetConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
|
||||||
c.Lock()
|
|
||||||
defer c.Unlock()
|
|
||||||
buf, err := Pack(c.buf, b, c.Cipher)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
_, err = c.PacketConn.WriteTo(buf, addr)
|
|
||||||
return len(b), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *packetConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
|
||||||
n, addr, err := c.PacketConn.ReadFrom(b)
|
|
||||||
if err != nil {
|
|
||||||
return n, addr, err
|
|
||||||
}
|
|
||||||
bb, err := Unpack(b[c.IVSize():], b[:n], c.Cipher)
|
|
||||||
if err != nil {
|
|
||||||
return n, addr, err
|
|
||||||
}
|
|
||||||
copy(b, bb)
|
|
||||||
return len(bb), addr, err
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
package shadowstream
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/cipher"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
const bufSize = 32 * 1024
|
|
||||||
|
|
||||||
type writer struct {
|
|
||||||
io.Writer
|
|
||||||
cipher.Stream
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWriter wraps an io.Writer with stream cipher encryption.
|
|
||||||
func NewWriter(w io.Writer, s cipher.Stream) io.Writer {
|
|
||||||
return &writer{Writer: w, Stream: s}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *writer) Write(p []byte) (n int, err error) {
|
|
||||||
buf := pool.GetBuffer(bufSize)
|
|
||||||
defer pool.PutBuffer(buf)
|
|
||||||
|
|
||||||
for nw := 0; n < len(p) && err == nil; n += nw {
|
|
||||||
end := n + len(buf)
|
|
||||||
if end > len(p) {
|
|
||||||
end = len(p)
|
|
||||||
}
|
|
||||||
w.XORKeyStream(buf, p[n:end])
|
|
||||||
nw, err = w.Writer.Write(buf[:end-n])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type reader struct {
|
|
||||||
io.Reader
|
|
||||||
cipher.Stream
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReader wraps an io.Reader with stream cipher decryption.
|
|
||||||
func NewReader(r io.Reader, s cipher.Stream) io.Reader {
|
|
||||||
return &reader{Reader: r, Stream: s}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *reader) Read(p []byte) (int, error) {
|
|
||||||
n, err := r.Reader.Read(p)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
p = p[:n]
|
|
||||||
r.XORKeyStream(p, p)
|
|
||||||
return n, nil
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
package ss
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
|
||||||
"github.com/nadoo/glider/pkg/socks"
|
|
||||||
"github.com/nadoo/glider/proxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewSSDialer returns a ss proxy dialer.
|
|
||||||
func NewSSDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
|
||||||
return NewSS(s, d, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Addr returns forwarder's address.
|
|
||||||
func (s *SS) Addr() string {
|
|
||||||
if s.addr == "" {
|
|
||||||
return s.dialer.Addr()
|
|
||||||
}
|
|
||||||
return s.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial connects to the address addr on the network net via the proxy.
|
|
||||||
func (s *SS) Dial(network, addr string) (net.Conn, error) {
|
|
||||||
target := socks.ParseAddr(addr)
|
|
||||||
if target == nil {
|
|
||||||
return nil, errors.New("[ss] unable to parse address: " + addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := s.dialer.Dial("tcp", s.addr)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[ss] dial to %s error: %s", s.addr, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c = s.StreamConn(c)
|
|
||||||
if _, err = c.Write(target); err != nil {
|
|
||||||
c.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
|
||||||
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, 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
|
|
||||||
}
|
|
@ -1,84 +1,67 @@
|
|||||||
package ss
|
package ss
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
"github.com/nadoo/glider/common/socks"
|
||||||
"github.com/nadoo/glider/pkg/socks"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PktConn .
|
// PktConn .
|
||||||
type PktConn struct {
|
type PktConn struct {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
writeTo net.Addr
|
|
||||||
target socks.Addr // if target is not nil, it may be a tunnel
|
writeAddr net.Addr // write to and read from addr
|
||||||
|
|
||||||
|
tgtAddr socks.Addr
|
||||||
|
tgtHeader bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPktConn returns a PktConn, the writeAddr must be *net.UDPAddr or *net.UnixAddr.
|
// NewPktConn returns a PktConn
|
||||||
func NewPktConn(c net.PacketConn, writeAddr net.Addr, targetAddr socks.Addr) *PktConn {
|
func NewPktConn(c net.PacketConn, writeAddr net.Addr, tgtAddr socks.Addr, tgtHeader bool) *PktConn {
|
||||||
return &PktConn{PacketConn: c, writeTo: writeAddr, target: targetAddr}
|
pc := &PktConn{
|
||||||
|
PacketConn: c,
|
||||||
|
writeAddr: writeAddr,
|
||||||
|
tgtAddr: tgtAddr,
|
||||||
|
tgtHeader: tgtHeader}
|
||||||
|
return pc
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFrom overrides the original function from net.PacketConn.
|
// ReadFrom overrides the original function from net.PacketConn
|
||||||
func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
n, _, target, err := pc.readFrom(b)
|
if !pc.tgtHeader {
|
||||||
return n, target, err
|
return pc.PacketConn.ReadFrom(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pc *PktConn) readFrom(b []byte) (int, net.Addr, net.Addr, error) {
|
|
||||||
buf := pool.GetBuffer(len(b))
|
|
||||||
defer pool.PutBuffer(buf)
|
|
||||||
|
|
||||||
|
buf := make([]byte, len(b))
|
||||||
n, raddr, err := pc.PacketConn.ReadFrom(buf)
|
n, raddr, err := pc.PacketConn.ReadFrom(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, raddr, nil, err
|
return n, raddr, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tgtAddr := socks.SplitAddr(buf[:n])
|
tgtAddr := socks.SplitAddr(buf)
|
||||||
if tgtAddr == nil {
|
copy(b, buf[len(tgtAddr):])
|
||||||
return n, raddr, nil, errors.New("can not get target addr")
|
|
||||||
|
//test
|
||||||
|
if pc.writeAddr == nil {
|
||||||
|
pc.writeAddr = raddr
|
||||||
}
|
}
|
||||||
|
|
||||||
target, err := net.ResolveUDPAddr("udp", tgtAddr.String())
|
if pc.tgtAddr == nil {
|
||||||
if err != nil {
|
pc.tgtAddr = tgtAddr
|
||||||
return n, raddr, nil, errors.New("wrong target addr")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if pc.writeTo == nil {
|
return n - len(tgtAddr), raddr, err
|
||||||
pc.writeTo = raddr
|
|
||||||
}
|
|
||||||
|
|
||||||
if pc.target == nil {
|
|
||||||
pc.target = make([]byte, len(tgtAddr))
|
|
||||||
copy(pc.target, tgtAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
n = copy(b, buf[len(tgtAddr):n])
|
|
||||||
return n, raddr, target, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteTo overrides the original function from net.PacketConn
|
// WriteTo overrides the original function from net.PacketConn
|
||||||
func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||||
target := pc.target
|
if !pc.tgtHeader {
|
||||||
if addr != nil {
|
return pc.PacketConn.WriteTo(b, addr)
|
||||||
target = socks.ParseAddr(addr.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if target == nil {
|
buf := make([]byte, len(pc.tgtAddr)+len(b))
|
||||||
return 0, errors.New("invalid addr")
|
copy(buf, pc.tgtAddr)
|
||||||
}
|
copy(buf[len(pc.tgtAddr):], b)
|
||||||
|
|
||||||
buf := pool.GetBytesBuffer()
|
return pc.PacketConn.WriteTo(buf, pc.writeAddr)
|
||||||
defer pool.PutBytesBuffer(buf)
|
|
||||||
|
|
||||||
tgtLen, _ := buf.Write(target)
|
|
||||||
buf.Write(b)
|
|
||||||
|
|
||||||
n, err := pc.PacketConn.WriteTo(buf.Bytes(), pc.writeTo)
|
|
||||||
if n > tgtLen {
|
|
||||||
return n - tgtLen, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, err
|
|
||||||
}
|
}
|
||||||
|
@ -1,178 +0,0 @@
|
|||||||
package ss
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
|
||||||
"github.com/nadoo/glider/pkg/socks"
|
|
||||||
"github.com/nadoo/glider/proxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
var nm sync.Map
|
|
||||||
|
|
||||||
// NewSSServer returns a ss proxy server.
|
|
||||||
func NewSSServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
|
||||||
return NewSS(s, nil, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenAndServe serves ss requests.
|
|
||||||
func (s *SS) ListenAndServe() {
|
|
||||||
go s.ListenAndServeUDP()
|
|
||||||
s.ListenAndServeTCP()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenAndServeTCP serves tcp ss requests.
|
|
||||||
func (s *SS) ListenAndServeTCP() {
|
|
||||||
l, err := net.Listen("tcp", s.addr)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("[ss] failed to listen on %s: %v", s.addr, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.F("[ss] listening TCP on %s", s.addr)
|
|
||||||
|
|
||||||
for {
|
|
||||||
c, err := l.Accept()
|
|
||||||
if err != nil {
|
|
||||||
log.F("[ss] failed to accept: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
go s.Serve(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve serves a connection.
|
|
||||||
func (s *SS) Serve(c net.Conn) {
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
if c, ok := c.(*net.TCPConn); ok {
|
|
||||||
c.SetKeepAlive(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
sc := s.StreamConn(c)
|
|
||||||
|
|
||||||
tgt, err := socks.ReadAddr(sc)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[ss] %s <-> target error: %v", c.RemoteAddr(), err)
|
|
||||||
proxy.Copy(io.Discard, c) // https://github.com/nadoo/glider/issues/180
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dialer := s.proxy.NextDialer(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
|
|
||||||
}
|
|
||||||
defer rc.Close()
|
|
||||||
|
|
||||||
log.F("[ss] %s <-> %s via %s", c.RemoteAddr(), tgt, dialer.Addr())
|
|
||||||
|
|
||||||
if err = proxy.Relay(sc, rc); err != nil {
|
|
||||||
log.F("[ss] %s <-> %s via %s, relay error: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
|
||||||
// record remote conn failure only
|
|
||||||
if !strings.Contains(err.Error(), s.addr) {
|
|
||||||
s.proxy.Record(dialer, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenAndServeUDP serves udp requests.
|
|
||||||
func (s *SS) ListenAndServeUDP() {
|
|
||||||
lc, err := net.ListenPacket("udp", s.addr)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("[ss] failed to listen on UDP %s: %v", s.addr, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer lc.Close()
|
|
||||||
|
|
||||||
log.F("[ss] listening UDP on %s", s.addr)
|
|
||||||
|
|
||||||
s.ServePacket(lc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServePacket implements proxy.PacketServer.
|
|
||||||
func (s *SS) ServePacket(pc net.PacketConn) {
|
|
||||||
lc := s.PacketConn(pc)
|
|
||||||
for {
|
|
||||||
c := NewPktConn(lc, nil, nil)
|
|
||||||
buf := pool.GetBuffer(proxy.UDPBufSize)
|
|
||||||
|
|
||||||
n, srcAddr, dstAddr, err := c.readFrom(buf)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[ssu] remote read error: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var session *Session
|
|
||||||
sessionKey := srcAddr.String()
|
|
||||||
|
|
||||||
v, ok := nm.Load(sessionKey)
|
|
||||||
if !ok || v == nil {
|
|
||||||
session = newSession(sessionKey, srcAddr, dstAddr, c)
|
|
||||||
nm.Store(sessionKey, session)
|
|
||||||
go s.serveSession(session)
|
|
||||||
} else {
|
|
||||||
session = v.(*Session)
|
|
||||||
}
|
|
||||||
|
|
||||||
session.msgCh <- message{dstAddr, buf[:n]}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SS) serveSession(session *Session) {
|
|
||||||
dstPC, dialer, err := s.proxy.DialUDP("udp", session.dst.String())
|
|
||||||
if err != nil {
|
|
||||||
log.F("[ssu] remote dial error: %v", err)
|
|
||||||
nm.Delete(session.key)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer dstPC.Close()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
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.dst, dialer.Addr())
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case msg := <-session.msgCh:
|
|
||||||
_, err = dstPC.WriteTo(msg.msg, msg.dst)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[ssu] writeTo %s error: %v", msg.dst, err)
|
|
||||||
}
|
|
||||||
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 message
|
|
||||||
finCh chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSession(key string, src, dst net.Addr, srcPC *PktConn) *Session {
|
|
||||||
return &Session{key, src, dst, srcPC, make(chan message, 32), make(chan struct{})}
|
|
||||||
}
|
|
252
proxy/ss/ss.go
252
proxy/ss/ss.go
@ -1,11 +1,19 @@
|
|||||||
package ss
|
package ss
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
"github.com/nadoo/go-shadowsocks2/core"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/common/conn"
|
||||||
|
"github.com/nadoo/glider/common/log"
|
||||||
|
"github.com/nadoo/glider/common/socks"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
"github.com/nadoo/glider/proxy/ss/cipher"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SS is a base ss struct.
|
// SS is a base ss struct.
|
||||||
@ -14,7 +22,7 @@ type SS struct {
|
|||||||
proxy proxy.Proxy
|
proxy proxy.Proxy
|
||||||
addr string
|
addr string
|
||||||
|
|
||||||
cipher.Cipher
|
core.Cipher
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -26,7 +34,7 @@ func init() {
|
|||||||
func NewSS(s string, d proxy.Dialer, p proxy.Proxy) (*SS, error) {
|
func NewSS(s string, d proxy.Dialer, p proxy.Proxy) (*SS, error) {
|
||||||
u, err := url.Parse(s)
|
u, err := url.Parse(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[ss] parse err: %s", err)
|
log.F("parse err: %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +42,7 @@ func NewSS(s string, d proxy.Dialer, p proxy.Proxy) (*SS, error) {
|
|||||||
method := u.User.Username()
|
method := u.User.Username()
|
||||||
pass, _ := u.User.Password()
|
pass, _ := u.User.Password()
|
||||||
|
|
||||||
ciph, err := cipher.PickCipher(method, nil, pass)
|
ciph, err := core.PickCipher(method, nil, pass)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("[ss] PickCipher for '%s', error: %s", method, err)
|
log.Fatalf("[ss] PickCipher for '%s', error: %s", method, err)
|
||||||
}
|
}
|
||||||
@ -49,18 +57,224 @@ func NewSS(s string, d proxy.Dialer, p proxy.Proxy) (*SS, error) {
|
|||||||
return ss, nil
|
return ss, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
// NewSSDialer returns a ss proxy dialer.
|
||||||
proxy.AddUsage("ss", `
|
func NewSSDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||||
SS scheme:
|
return NewSS(s, d, nil)
|
||||||
ss://method:pass@host:port
|
}
|
||||||
|
|
||||||
Available methods for ss:
|
// NewSSServer returns a ss proxy server.
|
||||||
AEAD Ciphers:
|
func NewSSServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||||
AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AEAD_XCHACHA20_POLY1305
|
return NewSS(s, nil, p)
|
||||||
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:
|
// ListenAndServe serves ss requests.
|
||||||
chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305
|
func (s *SS) ListenAndServe() {
|
||||||
Plain: NONE
|
go s.ListenAndServeUDP()
|
||||||
`)
|
s.ListenAndServeTCP()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServeTCP serves tcp ss requests.
|
||||||
|
func (s *SS) ListenAndServeTCP() {
|
||||||
|
l, err := net.Listen("tcp", s.addr)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[ss] failed to listen on %s: %v", s.addr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.F("[ss] listening TCP on %s", s.addr)
|
||||||
|
|
||||||
|
for {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.F("[ss] failed to accept: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go s.Serve(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve serves a connection.
|
||||||
|
func (s *SS) Serve(c net.Conn) {
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
if c, ok := c.(*net.TCPConn); ok {
|
||||||
|
c.SetKeepAlive(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
c = s.StreamConn(c)
|
||||||
|
|
||||||
|
tgt, err := socks.ReadAddr(c)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[ss] failed to get target address: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer := s.proxy.NextDialer(tgt.String())
|
||||||
|
|
||||||
|
// udp over tcp?
|
||||||
|
uot := socks.UoT(tgt[0])
|
||||||
|
if uot && dialer.Addr() == "DIRECT" {
|
||||||
|
rc, err := net.ListenPacket("udp", "")
|
||||||
|
if err != nil {
|
||||||
|
log.F("[ss-uottun] UDP remote listen error: %v", err)
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
req := make([]byte, conn.UDPBufSize)
|
||||||
|
n, err := c.Read(req)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[ss-uottun] error in ioutil.ReadAll: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tgtAddr, _ := net.ResolveUDPAddr("udp", tgt.String())
|
||||||
|
rc.WriteTo(req[:n], tgtAddr)
|
||||||
|
|
||||||
|
buf := make([]byte, conn.UDPBufSize)
|
||||||
|
n, _, err = rc.ReadFrom(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[ss-uottun] read error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Write(buf[:n])
|
||||||
|
|
||||||
|
log.F("[ss] %s <-tcp-> %s - %s <-udp-> %s ", c.RemoteAddr(), c.LocalAddr(), rc.LocalAddr(), tgt)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
network := "tcp"
|
||||||
|
if uot {
|
||||||
|
network = "udp"
|
||||||
|
}
|
||||||
|
|
||||||
|
rc, err := dialer.Dial(network, tgt.String())
|
||||||
|
if err != nil {
|
||||||
|
log.F("[ss] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
log.F("[ss] %s <-> %s via %s", c.RemoteAddr(), tgt, dialer.Addr())
|
||||||
|
|
||||||
|
_, _, err = conn.Relay(c, rc)
|
||||||
|
if err != nil {
|
||||||
|
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||||
|
return // ignore i/o timeout
|
||||||
|
}
|
||||||
|
log.F("[ss] relay error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServeUDP serves udp ss requests.
|
||||||
|
func (s *SS) ListenAndServeUDP() {
|
||||||
|
lc, err := net.ListenPacket("udp", s.addr)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[ss-udp] failed to listen on %s: %v", s.addr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer lc.Close()
|
||||||
|
|
||||||
|
lc = s.PacketConn(lc)
|
||||||
|
|
||||||
|
log.F("[ss-udp] listening UDP on %s", s.addr)
|
||||||
|
|
||||||
|
var nm sync.Map
|
||||||
|
buf := make([]byte, conn.UDPBufSize)
|
||||||
|
|
||||||
|
for {
|
||||||
|
c := NewPktConn(lc, nil, nil, true)
|
||||||
|
|
||||||
|
n, raddr, err := c.ReadFrom(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[ss-udp] remote read error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var pc *PktConn
|
||||||
|
v, ok := nm.Load(raddr.String())
|
||||||
|
if !ok && v == nil {
|
||||||
|
lpc, nextHop, err := s.proxy.DialUDP("udp", c.tgtAddr.String())
|
||||||
|
if err != nil {
|
||||||
|
log.F("[ss-udp] remote dial error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pc = NewPktConn(lpc, nextHop, nil, false)
|
||||||
|
nm.Store(raddr.String(), pc)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
conn.RelayUDP(c, raddr, pc, 2*time.Minute)
|
||||||
|
pc.Close()
|
||||||
|
nm.Delete(raddr.String())
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.F("[ss-udp] %s <-> %s", raddr, c.tgtAddr)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
pc = v.(*PktConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = pc.WriteTo(buf[:n], pc.writeAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[ss-udp] remote write error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.F("[ss-udp] %s <-> %s", raddr, c.tgtAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCipher returns all the ciphers supported.
|
||||||
|
func ListCipher() string {
|
||||||
|
return strings.Join(core.ListCipher(), " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr returns forwarder's address.
|
||||||
|
func (s *SS) Addr() string {
|
||||||
|
if s.addr == "" {
|
||||||
|
return s.dialer.Addr()
|
||||||
|
}
|
||||||
|
return s.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the address addr on the network net via the proxy.
|
||||||
|
func (s *SS) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
target := socks.ParseAddr(addr)
|
||||||
|
if target == nil {
|
||||||
|
return nil, errors.New("[ss] unable to parse address: " + addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if network == "uot" {
|
||||||
|
target[0] = target[0] | 0x8
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := s.dialer.Dial("tcp", s.addr)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[ss] dial to %s error: %s", s.addr, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c = s.StreamConn(c)
|
||||||
|
if _, err = c.Write(target); err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialUDP connects to the given address via the proxy.
|
||||||
|
func (s *SS) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||||
|
pc, nextHop, err := s.dialer.DialUDP(network, s.addr)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[ss] dialudp to %s error: %s", s.addr, err)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pkc := NewPktConn(s.PacketConn(pc), nextHop, socks.ParseAddr(addr), true)
|
||||||
|
return pkc, nextHop, err
|
||||||
}
|
}
|
||||||
|
199
proxy/ssh/ssh.go
199
proxy/ssh/ssh.go
@ -1,199 +0,0 @@
|
|||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/log"
|
|
||||||
"github.com/nadoo/glider/proxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SSH is a base ssh struct.
|
|
||||||
type SSH struct {
|
|
||||||
dialer proxy.Dialer
|
|
||||||
proxy proxy.Proxy
|
|
||||||
addr string
|
|
||||||
|
|
||||||
conn net.Conn
|
|
||||||
client *ssh.Client
|
|
||||||
config *ssh.ClientConfig
|
|
||||||
|
|
||||||
once sync.Once
|
|
||||||
mutex sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
proxy.RegisterDialer("ssh", NewSSHDialer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSSH returns a ssh proxy.
|
|
||||||
func NewSSH(s string, d proxy.Dialer, p proxy.Proxy) (*SSH, error) {
|
|
||||||
u, err := url.Parse(s)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[ssh] parse err: %s", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
user := u.User.Username()
|
|
||||||
if user == "" {
|
|
||||||
user = "root"
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &ssh.ClientConfig{
|
|
||||||
User: user,
|
|
||||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if pass, _ := u.User.Password(); pass != "" {
|
|
||||||
config.Auth = []ssh.AuthMethod{ssh.Password(pass)}
|
|
||||||
}
|
|
||||||
|
|
||||||
query := u.Query()
|
|
||||||
if key := query.Get("key"); key != "" {
|
|
||||||
keyAuth, err := privateKeyAuth(key)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[ssh] read key file error: %s", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.Auth = append(config.Auth, keyAuth)
|
|
||||||
}
|
|
||||||
|
|
||||||
// timeout of ssh handshake and channel operation
|
|
||||||
qtimeout := query.Get("timeout")
|
|
||||||
if qtimeout == "" {
|
|
||||||
qtimeout = "5" // default timeout
|
|
||||||
}
|
|
||||||
timeout, err := strconv.ParseUint(qtimeout, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[ssh] parse timeout err: %s", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.Timeout = time.Second * time.Duration(timeout)
|
|
||||||
|
|
||||||
t := &SSH{
|
|
||||||
dialer: d,
|
|
||||||
proxy: p,
|
|
||||||
addr: u.Host,
|
|
||||||
config: config,
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, port, _ := net.SplitHostPort(t.addr); port == "" {
|
|
||||||
t.addr = net.JoinHostPort(t.addr, "22")
|
|
||||||
}
|
|
||||||
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSSHDialer returns a ssh proxy dialer.
|
|
||||||
func NewSSHDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
|
||||||
return NewSSH(s, d, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Addr returns forwarder's address.
|
|
||||||
func (s *SSH) Addr() string {
|
|
||||||
if s.addr == "" {
|
|
||||||
return s.dialer.Addr()
|
|
||||||
}
|
|
||||||
return s.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial connects to the address addr on the network net via the proxy.
|
|
||||||
func (s *SSH) Dial(network, addr string) (net.Conn, error) {
|
|
||||||
s.once.Do(func() { go s.keepConn(s.initConn() == nil) })
|
|
||||||
|
|
||||||
s.mutex.RLock()
|
|
||||||
defer s.mutex.RUnlock()
|
|
||||||
|
|
||||||
if s.client == nil {
|
|
||||||
return nil, errors.New("ssh client is nil")
|
|
||||||
}
|
|
||||||
return s.dial(network, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSH) dial(network, addr string) (net.Conn, error) {
|
|
||||||
s.conn.SetDeadline(time.Now().Add(s.config.Timeout))
|
|
||||||
c, err := s.client.Dial(network, addr)
|
|
||||||
s.conn.SetDeadline(time.Time{})
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSH) initConn() error {
|
|
||||||
s.mutex.Lock()
|
|
||||||
defer s.mutex.Unlock()
|
|
||||||
|
|
||||||
log.F("[ssh] connecting to %s", s.addr)
|
|
||||||
c, err := s.dialer.Dial("tcp", s.addr)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[ssh] dial connection to %s error: %s", s.addr, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.SetDeadline(time.Now().Add(s.config.Timeout))
|
|
||||||
conn, ch, req, err := ssh.NewClientConn(c, s.addr, s.config)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[ssh] initial connection to %s error: %s", s.addr, err)
|
|
||||||
c.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.SetDeadline(time.Time{})
|
|
||||||
|
|
||||||
s.conn = c
|
|
||||||
s.client = ssh.NewClient(conn, ch, req)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSH) keepConn(connected bool) {
|
|
||||||
if connected {
|
|
||||||
s.client.Conn.Wait()
|
|
||||||
s.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
sleep := time.Second
|
|
||||||
for {
|
|
||||||
if err := s.initConn(); err != nil {
|
|
||||||
sleep *= 2
|
|
||||||
if sleep > time.Second*60 {
|
|
||||||
sleep = time.Second * 60
|
|
||||||
}
|
|
||||||
time.Sleep(sleep)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
sleep = time.Second
|
|
||||||
s.client.Conn.Wait()
|
|
||||||
s.conn.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
|
||||||
func (s *SSH) DialUDP(network, addr string) (pc net.PacketConn, err error) {
|
|
||||||
return nil, proxy.ErrNotSupported
|
|
||||||
}
|
|
||||||
|
|
||||||
func privateKeyAuth(file string) (ssh.AuthMethod, error) {
|
|
||||||
buffer, err := os.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := ssh.ParsePrivateKey(buffer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
`)
|
|
||||||
}
|
|
@ -1,342 +0,0 @@
|
|||||||
package cipher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/des"
|
|
||||||
"crypto/md5"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rc4"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/aead/chacha20"
|
|
||||||
"github.com/dgryski/go-camellia"
|
|
||||||
"github.com/dgryski/go-idea"
|
|
||||||
"github.com/dgryski/go-rc2"
|
|
||||||
"golang.org/x/crypto/blowfish"
|
|
||||||
"golang.org/x/crypto/cast5"
|
|
||||||
"golang.org/x/crypto/salsa20/salsa"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
|
||||||
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errEmptyPassword = errors.New("empty key")
|
|
||||||
|
|
||||||
type DecOrEnc int
|
|
||||||
|
|
||||||
const (
|
|
||||||
Decrypt DecOrEnc = iota
|
|
||||||
Encrypt
|
|
||||||
)
|
|
||||||
|
|
||||||
func newCTRStream(block cipher.Block, err error, key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return cipher.NewCTR(block, iv), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAESCTRStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
|
||||||
block, err := aes.NewCipher(key)
|
|
||||||
return newCTRStream(block, err, key, iv, doe)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newOFBStream(block cipher.Block, err error, key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return cipher.NewCTR(block, iv), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAESOFBStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
|
||||||
block, err := aes.NewCipher(key)
|
|
||||||
return newOFBStream(block, err, key, iv, doe)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCFBStream(block cipher.Block, err error, key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if doe == Encrypt {
|
|
||||||
return cipher.NewCFBEncrypter(block, iv), nil
|
|
||||||
} else {
|
|
||||||
return cipher.NewCFBDecrypter(block, iv), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAESCFBStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
|
||||||
block, err := aes.NewCipher(key)
|
|
||||||
return newCFBStream(block, err, key, iv, doe)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDESStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
|
||||||
block, err := des.NewCipher(key)
|
|
||||||
return newCFBStream(block, err, key, iv, doe)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBlowFishStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
|
||||||
block, err := blowfish.NewCipher(key)
|
|
||||||
return newCFBStream(block, err, key, iv, doe)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCast5Stream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
|
||||||
block, err := cast5.NewCipher(key)
|
|
||||||
return newCFBStream(block, err, key, iv, doe)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRC4MD5Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
|
|
||||||
h := md5.New()
|
|
||||||
h.Write(key)
|
|
||||||
h.Write(iv)
|
|
||||||
rc4key := h.Sum(nil)
|
|
||||||
|
|
||||||
return rc4.NewCipher(rc4key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newChaCha20Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
|
|
||||||
return chacha20.NewCipher(iv, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newChacha20IETFStream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
|
|
||||||
return chacha20.NewCipher(iv, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
type salsaStreamCipher struct {
|
|
||||||
nonce [8]byte
|
|
||||||
key [32]byte
|
|
||||||
counter int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *salsaStreamCipher) XORKeyStream(dst, src []byte) {
|
|
||||||
var buf []byte
|
|
||||||
padLen := c.counter % 64
|
|
||||||
dataSize := len(src) + padLen
|
|
||||||
if cap(dst) >= dataSize {
|
|
||||||
buf = dst[:dataSize]
|
|
||||||
// nadoo: comment out codes here to use pool buffer
|
|
||||||
// modify start -->
|
|
||||||
// } else if leakybuf.GlobalLeakyBufSize >= dataSize {
|
|
||||||
// buf = leakybuf.GlobalLeakyBuf.Get()
|
|
||||||
// defer leakybuf.GlobalLeakyBuf.Put(buf)
|
|
||||||
// buf = buf[:dataSize]
|
|
||||||
// } else {
|
|
||||||
// buf = make([]byte, dataSize)
|
|
||||||
// }
|
|
||||||
} else {
|
|
||||||
buf = pool.GetBuffer(dataSize)
|
|
||||||
defer pool.PutBuffer(buf)
|
|
||||||
}
|
|
||||||
// --> modify end
|
|
||||||
|
|
||||||
var subNonce [16]byte
|
|
||||||
copy(subNonce[:], c.nonce[:])
|
|
||||||
binary.LittleEndian.PutUint64(subNonce[len(c.nonce):], uint64(c.counter/64))
|
|
||||||
|
|
||||||
// It's difficult to avoid data copy here. src or dst maybe slice from
|
|
||||||
// Conn.Read/Write, which can't have padding.
|
|
||||||
copy(buf[padLen:], src[:])
|
|
||||||
salsa.XORKeyStream(buf, buf, &subNonce, &c.key)
|
|
||||||
copy(dst, buf[padLen:])
|
|
||||||
|
|
||||||
c.counter += len(src)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSalsa20Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
|
|
||||||
var c salsaStreamCipher
|
|
||||||
copy(c.nonce[:], iv[:8])
|
|
||||||
copy(c.key[:], key[:32])
|
|
||||||
return &c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCamelliaStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
|
||||||
block, err := camellia.New(key)
|
|
||||||
return newCFBStream(block, err, key, iv, doe)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newIdeaStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
|
||||||
block, err := idea.NewCipher(key)
|
|
||||||
return newCFBStream(block, err, key, iv, doe)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRC2Stream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
|
||||||
block, err := rc2.New(key, 16)
|
|
||||||
return newCFBStream(block, err, key, iv, doe)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRC4Stream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
|
||||||
return rc4.NewCipher(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSeedStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
|
||||||
// TODO: SEED block cipher implementation is required
|
|
||||||
block, err := rc2.New(key, 16)
|
|
||||||
return newCFBStream(block, err, key, iv, doe)
|
|
||||||
}
|
|
||||||
|
|
||||||
type NoneStream struct {
|
|
||||||
cipher.Stream
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*NoneStream) XORKeyStream(dst, src []byte) {
|
|
||||||
copy(dst, src)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newNoneStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
|
||||||
return new(NoneStream), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type cipherInfo struct {
|
|
||||||
keyLen int
|
|
||||||
ivLen int
|
|
||||||
newStream func(key, iv []byte, doe DecOrEnc) (cipher.Stream, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
var streamCipherMethod = map[string]*cipherInfo{
|
|
||||||
"aes-128-cfb": {16, 16, newAESCFBStream},
|
|
||||||
"aes-192-cfb": {24, 16, newAESCFBStream},
|
|
||||||
"aes-256-cfb": {32, 16, newAESCFBStream},
|
|
||||||
"aes-128-ctr": {16, 16, newAESCTRStream},
|
|
||||||
"aes-192-ctr": {24, 16, newAESCTRStream},
|
|
||||||
"aes-256-ctr": {32, 16, newAESCTRStream},
|
|
||||||
"aes-128-ofb": {16, 16, newAESOFBStream},
|
|
||||||
"aes-192-ofb": {24, 16, newAESOFBStream},
|
|
||||||
"aes-256-ofb": {32, 16, newAESOFBStream},
|
|
||||||
"des-cfb": {8, 8, newDESStream},
|
|
||||||
"bf-cfb": {16, 8, newBlowFishStream},
|
|
||||||
"cast5-cfb": {16, 8, newCast5Stream},
|
|
||||||
"rc4-md5": {16, 16, newRC4MD5Stream},
|
|
||||||
"rc4-md5-6": {16, 6, newRC4MD5Stream},
|
|
||||||
"chacha20": {32, 8, newChaCha20Stream},
|
|
||||||
"chacha20-ietf": {32, 12, newChacha20IETFStream},
|
|
||||||
"salsa20": {32, 8, newSalsa20Stream},
|
|
||||||
"camellia-128-cfb": {16, 16, newCamelliaStream},
|
|
||||||
"camellia-192-cfb": {24, 16, newCamelliaStream},
|
|
||||||
"camellia-256-cfb": {32, 16, newCamelliaStream},
|
|
||||||
"idea-cfb": {16, 8, newIdeaStream},
|
|
||||||
"rc2-cfb": {16, 8, newRC2Stream},
|
|
||||||
"seed-cfb": {16, 8, newSeedStream},
|
|
||||||
"rc4": {16, 0, newRC4Stream},
|
|
||||||
"none": {16, 0, newNoneStream},
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckCipherMethod(method string) error {
|
|
||||||
if method == "" {
|
|
||||||
method = "rc4-md5"
|
|
||||||
}
|
|
||||||
_, ok := streamCipherMethod[method]
|
|
||||||
if !ok {
|
|
||||||
return errors.New("Unsupported encryption method: " + method)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type StreamCipher struct {
|
|
||||||
enc cipher.Stream
|
|
||||||
dec cipher.Stream
|
|
||||||
key []byte
|
|
||||||
info *cipherInfo
|
|
||||||
iv []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStreamCipher creates a cipher that can be used in Dial() etc.
|
|
||||||
// Use cipher.Copy() to create a new cipher with the same method and password
|
|
||||||
// to avoid the cost of repeated cipher initialization.
|
|
||||||
func NewStreamCipher(method, password string) (c *StreamCipher, err error) {
|
|
||||||
if password == "" {
|
|
||||||
return nil, errEmptyPassword
|
|
||||||
}
|
|
||||||
if method == "" {
|
|
||||||
method = "rc4-md5"
|
|
||||||
}
|
|
||||||
mi, ok := streamCipherMethod[method]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("Unsupported encryption method: " + method)
|
|
||||||
}
|
|
||||||
|
|
||||||
key := tools.EVPBytesToKey(password, mi.keyLen)
|
|
||||||
|
|
||||||
c = &StreamCipher{key: key, info: mi}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *StreamCipher) EncryptInited() bool {
|
|
||||||
return c.enc != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *StreamCipher) DecryptInited() bool {
|
|
||||||
return c.dec != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initializes the block cipher with CFB mode, returns IV.
|
|
||||||
func (c *StreamCipher) InitEncrypt() (iv []byte, err error) {
|
|
||||||
if c.iv == nil {
|
|
||||||
iv = make([]byte, c.info.ivLen)
|
|
||||||
rand.Read(iv)
|
|
||||||
c.iv = iv
|
|
||||||
} else {
|
|
||||||
iv = c.iv
|
|
||||||
}
|
|
||||||
c.enc, err = c.info.newStream(c.key, iv, Encrypt)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *StreamCipher) InitDecrypt(iv []byte) (err error) {
|
|
||||||
c.dec, err = c.info.newStream(c.key, iv, Decrypt)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *StreamCipher) Encrypt(dst, src []byte) {
|
|
||||||
c.enc.XORKeyStream(dst, src)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *StreamCipher) Decrypt(dst, src []byte) {
|
|
||||||
c.dec.XORKeyStream(dst, src)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy creates a new cipher at it's initial state.
|
|
||||||
func (c *StreamCipher) Copy() *StreamCipher {
|
|
||||||
// This optimization maybe not necessary. But without this function, we
|
|
||||||
// need to maintain a table cache for newTableCipher and use lock to
|
|
||||||
// protect concurrent access to that cache.
|
|
||||||
|
|
||||||
// AES and DES ciphers does not return specific types, so it's difficult
|
|
||||||
// to create copy. But their initialization time is less than 4000ns on my
|
|
||||||
// 2.26 GHz Intel Core 2 Duo processor. So no need to worry.
|
|
||||||
|
|
||||||
// Currently, blow-fish and cast5 initialization cost is an order of
|
|
||||||
// magnitude slower than other ciphers. (I'm not sure whether this is
|
|
||||||
// because the current implementation is not highly optimized, or this is
|
|
||||||
// the nature of the algorithm.)
|
|
||||||
|
|
||||||
nc := *c
|
|
||||||
nc.enc = nil
|
|
||||||
nc.dec = nil
|
|
||||||
return &nc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *StreamCipher) Key() []byte {
|
|
||||||
return c.key
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *StreamCipher) IV() []byte {
|
|
||||||
return c.iv
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *StreamCipher) SetIV(iv []byte) {
|
|
||||||
c.iv = iv
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *StreamCipher) SetKey(key []byte) {
|
|
||||||
c.key = key
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *StreamCipher) InfoIVLen() int {
|
|
||||||
return c.info.ivLen
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *StreamCipher) InfoKeyLen() int {
|
|
||||||
return c.info.keyLen
|
|
||||||
}
|
|
@ -1,228 +0,0 @@
|
|||||||
// source code from https://github.com/v2rayA/shadowsocksR
|
|
||||||
// Just copy here to use glider's builtin buffer pool.
|
|
||||||
// As this protocol hasn't been maintained since 2017, it doesn't deserve our research to rewrite it.
|
|
||||||
|
|
||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pkg/pool"
|
|
||||||
"github.com/nadoo/glider/proxy"
|
|
||||||
"github.com/nadoo/glider/proxy/ssr/internal/cipher"
|
|
||||||
"github.com/nadoo/glider/proxy/ssr/internal/obfs"
|
|
||||||
"github.com/nadoo/glider/proxy/ssr/internal/protocol"
|
|
||||||
)
|
|
||||||
|
|
||||||
var bufSize = proxy.TCPBufSize
|
|
||||||
|
|
||||||
// SSTCPConn the struct that override the net.Conn methods
|
|
||||||
type SSTCPConn struct {
|
|
||||||
net.Conn
|
|
||||||
*cipher.StreamCipher
|
|
||||||
IObfs obfs.IObfs
|
|
||||||
IProtocol protocol.IProtocol
|
|
||||||
readBuf []byte
|
|
||||||
underPostdecryptBuf *bytes.Buffer
|
|
||||||
readIndex uint64
|
|
||||||
decryptedBuf *bytes.Buffer
|
|
||||||
writeBuf []byte
|
|
||||||
lastReadError error
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSSTCPConn(c net.Conn, cipher *cipher.StreamCipher) *SSTCPConn {
|
|
||||||
return &SSTCPConn{
|
|
||||||
Conn: c,
|
|
||||||
StreamCipher: cipher,
|
|
||||||
readBuf: pool.GetBuffer(bufSize),
|
|
||||||
decryptedBuf: pool.GetBytesBuffer(),
|
|
||||||
underPostdecryptBuf: pool.GetBytesBuffer(),
|
|
||||||
writeBuf: pool.GetBuffer(bufSize),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SSTCPConn) Close() error {
|
|
||||||
pool.PutBuffer(c.readBuf)
|
|
||||||
pool.PutBytesBuffer(c.decryptedBuf)
|
|
||||||
pool.PutBytesBuffer(c.underPostdecryptBuf)
|
|
||||||
pool.PutBuffer(c.writeBuf)
|
|
||||||
return c.Conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SSTCPConn) GetIv() (iv []byte) {
|
|
||||||
iv = make([]byte, len(c.IV()))
|
|
||||||
copy(iv, c.IV())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SSTCPConn) GetKey() (key []byte) {
|
|
||||||
key = make([]byte, len(c.Key()))
|
|
||||||
copy(key, c.Key())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SSTCPConn) initEncryptor(b []byte) (iv []byte, err error) {
|
|
||||||
if !c.EncryptInited() {
|
|
||||||
iv, err = c.InitEncrypt()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
overhead := c.IObfs.GetOverhead() + c.IProtocol.GetOverhead()
|
|
||||||
// should initialize obfs/protocol now, because iv is ready now
|
|
||||||
obfsServerInfo := c.IObfs.GetServerInfo()
|
|
||||||
obfsServerInfo.SetHeadLen(b, 30)
|
|
||||||
obfsServerInfo.IV, obfsServerInfo.IVLen = c.IV(), c.InfoIVLen()
|
|
||||||
obfsServerInfo.Key, obfsServerInfo.KeyLen = c.Key(), c.InfoKeyLen()
|
|
||||||
obfsServerInfo.Overhead = overhead
|
|
||||||
c.IObfs.SetServerInfo(obfsServerInfo)
|
|
||||||
|
|
||||||
protocolServerInfo := c.IProtocol.GetServerInfo()
|
|
||||||
protocolServerInfo.SetHeadLen(b, 30)
|
|
||||||
protocolServerInfo.IV, protocolServerInfo.IVLen = c.IV(), c.InfoIVLen()
|
|
||||||
protocolServerInfo.Key, protocolServerInfo.KeyLen = c.Key(), c.InfoKeyLen()
|
|
||||||
protocolServerInfo.Overhead = overhead
|
|
||||||
c.IProtocol.SetServerInfo(protocolServerInfo)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SSTCPConn) Read(b []byte) (n int, err error) {
|
|
||||||
for {
|
|
||||||
n, err = c.doRead(b)
|
|
||||||
if b == nil || n != 0 || err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SSTCPConn) doRead(b []byte) (n int, err error) {
|
|
||||||
if c.decryptedBuf.Len() > 0 {
|
|
||||||
return c.decryptedBuf.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err = c.Conn.Read(c.readBuf)
|
|
||||||
if n == 0 || err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
decodedData, needSendBack, err := c.IObfs.Decode(c.readBuf[:n])
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//do send back
|
|
||||||
if needSendBack {
|
|
||||||
c.Write(nil)
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
decodedDataLen := len(decodedData)
|
|
||||||
if decodedDataLen == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.DecryptInited() {
|
|
||||||
if len(decodedData) < c.InfoIVLen() {
|
|
||||||
return 0, errors.New(fmt.Sprintf("invalid ivLen:%v, actual length:%v", c.InfoIVLen(), len(decodedData)))
|
|
||||||
}
|
|
||||||
|
|
||||||
iv := decodedData[0:c.InfoIVLen()]
|
|
||||||
if err = c.InitDecrypt(iv); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.IV()) == 0 {
|
|
||||||
c.SetIV(iv)
|
|
||||||
}
|
|
||||||
|
|
||||||
decodedDataLen -= c.InfoIVLen()
|
|
||||||
if decodedDataLen <= 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
decodedData = decodedData[c.InfoIVLen():]
|
|
||||||
}
|
|
||||||
|
|
||||||
buf1 := pool.GetBuffer(decodedDataLen)
|
|
||||||
defer pool.PutBuffer(buf1)
|
|
||||||
|
|
||||||
c.Decrypt(buf1, decodedData)
|
|
||||||
c.underPostdecryptBuf.Write(buf1)
|
|
||||||
buf := c.underPostdecryptBuf.Bytes()
|
|
||||||
|
|
||||||
postDecryptedData, length, err := c.IProtocol.PostDecrypt(buf)
|
|
||||||
if err != nil {
|
|
||||||
c.underPostdecryptBuf.Reset()
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if length == 0 {
|
|
||||||
// not enough to postDecrypt
|
|
||||||
return 0, nil
|
|
||||||
} else {
|
|
||||||
c.underPostdecryptBuf.Next(length)
|
|
||||||
}
|
|
||||||
|
|
||||||
postDecryptedLength := len(postDecryptedData)
|
|
||||||
blength := len(b)
|
|
||||||
|
|
||||||
if blength >= postDecryptedLength {
|
|
||||||
copy(b, postDecryptedData)
|
|
||||||
return postDecryptedLength, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(b, postDecryptedData[:blength])
|
|
||||||
c.decryptedBuf.Write(postDecryptedData[blength:])
|
|
||||||
|
|
||||||
return blength, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SSTCPConn) preWrite(b []byte) (outData []byte, err error) {
|
|
||||||
if b == nil {
|
|
||||||
b = make([]byte, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
var iv []byte
|
|
||||||
if iv, err = c.initEncryptor(b); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var preEncryptedData []byte
|
|
||||||
preEncryptedData, err = c.IProtocol.PreEncrypt(b)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
preEncryptedDataLen := len(preEncryptedData)
|
|
||||||
//! \attention here the expected output buffer length MUST be accurate, it is preEncryptedDataLen now!
|
|
||||||
|
|
||||||
cipherData := c.writeBuf
|
|
||||||
dataSize := preEncryptedDataLen + len(iv)
|
|
||||||
if dataSize > len(cipherData) {
|
|
||||||
cipherData = make([]byte, dataSize)
|
|
||||||
} else {
|
|
||||||
cipherData = cipherData[:dataSize]
|
|
||||||
}
|
|
||||||
|
|
||||||
if iv != nil {
|
|
||||||
// Put initialization vector in buffer before be encoded
|
|
||||||
copy(cipherData, iv)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Encrypt(cipherData[len(iv):], preEncryptedData)
|
|
||||||
return c.IObfs.Encode(cipherData)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SSTCPConn) Write(b []byte) (n int, err error) {
|
|
||||||
outData, err := c.preWrite(b)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
n, err = c.Conn.Write(outData)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package obfs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
|
||||||
)
|
|
||||||
|
|
||||||
type creator func() IObfs
|
|
||||||
|
|
||||||
var (
|
|
||||||
creatorMap = make(map[string]creator)
|
|
||||||
)
|
|
||||||
|
|
||||||
type IObfs interface {
|
|
||||||
SetServerInfo(s *ssr.ServerInfo)
|
|
||||||
GetServerInfo() (s *ssr.ServerInfo)
|
|
||||||
Encode(data []byte) (encodedData []byte, err error)
|
|
||||||
Decode(data []byte) (decodedData []byte, needSendBack bool, err error)
|
|
||||||
SetData(data any)
|
|
||||||
GetData() any
|
|
||||||
GetOverhead() int
|
|
||||||
}
|
|
||||||
|
|
||||||
func register(name string, c creator) {
|
|
||||||
creatorMap[name] = c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewObfs create an obfs object by name and return as an IObfs interface
|
|
||||||
func NewObfs(name string) IObfs {
|
|
||||||
c, ok := creatorMap[strings.ToLower(name)]
|
|
||||||
if ok {
|
|
||||||
return c()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package obfs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
register("http_post", newHttpPost)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newHttpPost create a http_post object
|
|
||||||
func newHttpPost() IObfs {
|
|
||||||
// newHttpSimple create a http_simple object
|
|
||||||
|
|
||||||
t := &httpSimplePost{
|
|
||||||
userAgentIndex: rand.IntN(len(requestUserAgent)),
|
|
||||||
methodGet: false,
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
@ -1,185 +0,0 @@
|
|||||||
package obfs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"math/rand/v2"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
requestPath = []string{
|
|
||||||
"", "",
|
|
||||||
"login.php?redir=", "",
|
|
||||||
"register.php?code=", "",
|
|
||||||
"?keyword=", "",
|
|
||||||
"search?src=typd&q=", "&lang=en",
|
|
||||||
"s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&ch=&bar=&wd=", "&rn=",
|
|
||||||
"post.php?id=", "&goto=view.php",
|
|
||||||
}
|
|
||||||
requestUserAgent = []string{
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/44.0",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.10 Chromium/27.0.1453.93 Chrome/27.0.1453.93 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:35.0) Gecko/20100101 Firefox/35.0",
|
|
||||||
"Mozilla/5.0 (compatible; WOW64; MSIE 10.0; Windows NT 6.2)",
|
|
||||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27",
|
|
||||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; Trident/7.0; .NET4.0E; .NET4.0C)",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
|
|
||||||
"Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/BuildID) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
|
|
||||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// HttpSimple http_simple obfs encapsulate
|
|
||||||
type httpSimplePost struct {
|
|
||||||
ssr.ServerInfo
|
|
||||||
rawTransSent bool
|
|
||||||
rawTransReceived bool
|
|
||||||
userAgentIndex int
|
|
||||||
methodGet bool // true for get, false for post
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
register("http_simple", newHttpSimple)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newHttpSimple create a http_simple object
|
|
||||||
func newHttpSimple() IObfs {
|
|
||||||
|
|
||||||
t := &httpSimplePost{
|
|
||||||
rawTransSent: false,
|
|
||||||
rawTransReceived: false,
|
|
||||||
userAgentIndex: rand.IntN(len(requestUserAgent)),
|
|
||||||
methodGet: true,
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *httpSimplePost) SetServerInfo(s *ssr.ServerInfo) {
|
|
||||||
t.ServerInfo = *s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *httpSimplePost) GetServerInfo() (s *ssr.ServerInfo) {
|
|
||||||
return &t.ServerInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *httpSimplePost) SetData(data any) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *httpSimplePost) GetData() any {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *httpSimplePost) boundary() (ret string) {
|
|
||||||
|
|
||||||
set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
|
||||||
for range 32 {
|
|
||||||
ret = fmt.Sprintf("%s%c", ret, set[rand.IntN(len(set))])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *httpSimplePost) data2URLEncode(data []byte) (ret string) {
|
|
||||||
for i := range data {
|
|
||||||
ret = fmt.Sprintf("%s%%%s", ret, hex.EncodeToString([]byte{data[i]}))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *httpSimplePost) Encode(data []byte) (encodedData []byte, err error) {
|
|
||||||
if t.rawTransSent {
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
dataLength := len(data)
|
|
||||||
var headData []byte
|
|
||||||
if headSize := t.IVLen + t.HeadLen; dataLength-headSize > 64 {
|
|
||||||
headData = make([]byte, headSize+rand.IntN(64))
|
|
||||||
} else {
|
|
||||||
headData = make([]byte, dataLength)
|
|
||||||
}
|
|
||||||
copy(headData, data[0:len(headData)])
|
|
||||||
requestPathIndex := rand.IntN(len(requestPath)/2) * 2
|
|
||||||
host := t.Host
|
|
||||||
var customHead string
|
|
||||||
|
|
||||||
if len(t.Param) > 0 {
|
|
||||||
customHeads := strings.Split(t.Param, "#")
|
|
||||||
if len(customHeads) > 2 {
|
|
||||||
customHeads = customHeads[0:2]
|
|
||||||
}
|
|
||||||
param := t.Param
|
|
||||||
if len(customHeads) > 1 {
|
|
||||||
customHead = customHeads[1]
|
|
||||||
param = customHeads[0]
|
|
||||||
}
|
|
||||||
hosts := strings.Split(param, ",")
|
|
||||||
if len(hosts) > 0 {
|
|
||||||
host = strings.TrimSpace(hosts[rand.IntN(len(hosts))])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
method := "GET /"
|
|
||||||
if !t.methodGet {
|
|
||||||
method = "POST /"
|
|
||||||
}
|
|
||||||
httpBuf := fmt.Sprintf("%s%s%s%s HTTP/1.1\r\nHost: %s:%d\r\n",
|
|
||||||
method,
|
|
||||||
requestPath[requestPathIndex],
|
|
||||||
t.data2URLEncode(headData),
|
|
||||||
requestPath[requestPathIndex+1],
|
|
||||||
host,
|
|
||||||
t.Port)
|
|
||||||
if len(customHead) > 0 {
|
|
||||||
httpBuf = httpBuf + strings.Replace(customHead, "\\n", "\r\n", -1) + "\r\n\r\n"
|
|
||||||
} else {
|
|
||||||
var contentType string
|
|
||||||
if !t.methodGet {
|
|
||||||
contentType = "Content-Type: multipart/form-data; boundary=" + t.boundary() + "\r\n"
|
|
||||||
}
|
|
||||||
httpBuf = httpBuf +
|
|
||||||
"User-Agent: " + requestUserAgent[t.userAgentIndex] + "\r\n" +
|
|
||||||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
|
|
||||||
"Accept-Language: en-US,en;q=0.8\r\n" +
|
|
||||||
"Accept-Encoding: gzip, deflate\r\n" +
|
|
||||||
contentType +
|
|
||||||
"DNT: 1\r\n" +
|
|
||||||
"Connection: keep-alive\r\n" +
|
|
||||||
"\r\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(headData) < dataLength {
|
|
||||||
encodedData = make([]byte, len(httpBuf)+(dataLength-len(headData)))
|
|
||||||
copy(encodedData, []byte(httpBuf))
|
|
||||||
copy(encodedData[len(httpBuf):], data[len(headData):])
|
|
||||||
} else {
|
|
||||||
encodedData = []byte(httpBuf)
|
|
||||||
}
|
|
||||||
t.rawTransSent = true
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *httpSimplePost) Decode(data []byte) (decodedData []byte, needSendBack bool, err error) {
|
|
||||||
if t.rawTransReceived {
|
|
||||||
return data, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pos := bytes.Index(data, []byte("\r\n\r\n"))
|
|
||||||
if pos > 0 {
|
|
||||||
decodedData = make([]byte, len(data)-pos-4)
|
|
||||||
copy(decodedData, data[pos+4:])
|
|
||||||
t.rawTransReceived = true
|
|
||||||
}
|
|
||||||
return decodedData, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *httpSimplePost) GetOverhead() int {
|
|
||||||
return 0
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user