Compare commits

...

27 Commits

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

View File

@ -7,7 +7,7 @@ RUN apk add --no-cache ca-certificates
ARG TARGETPLATFORM ARG TARGETPLATFORM
RUN case $TARGETPLATFORM in \ RUN case $TARGETPLATFORM in \
'linux/386') \ 'linux/386') \
export FOLDER='default_linux_386'; \ export FOLDER='default_linux_386_sse2'; \
;; \ ;; \
'linux/amd64') \ 'linux/amd64') \
export FOLDER='default_linux_amd64_v1'; \ export FOLDER='default_linux_amd64_v1'; \
@ -19,10 +19,10 @@ RUN case $TARGETPLATFORM in \
export FOLDER='default_linux_arm_7'; \ export FOLDER='default_linux_arm_7'; \
;; \ ;; \
'linux/arm64') \ 'linux/arm64') \
export FOLDER='default_linux_arm64'; \ export FOLDER='default_linux_arm64_v8.0'; \
;; \ ;; \
'linux/riscv64') \ 'linux/riscv64') \
export FOLDER='default_linux_riscv64'; \ export FOLDER='default_linux_riscv64_rva20u64'; \
;; \ ;; \
*) echo >&2 "error: unsupported architecture '$TARGETPLATFORM'"; exit 1 ;; \ *) echo >&2 "error: unsupported architecture '$TARGETPLATFORM'"; exit 1 ;; \
esac \ esac \

View File

@ -2,9 +2,9 @@ name: Build
on: on:
push: push:
branches: branches:
- 'dev' - "dev"
tags: tags:
- '*' - "*"
pull_request: pull_request:
env: env:
@ -19,96 +19,63 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set Vars - name: Set Vars
run: | run: |
echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "GO_MOD_VERSION=$(grep -P "go \d+\." go.mod | cut -d " " -f2)" >> $GITHUB_ENV
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v3 uses: actions/setup-go@v5
with: with:
check-latest: true check-latest: true
go-version: ${{ env.GO_MOD_VERSION}} go-version-file: "go.mod"
cache: true
- name: Set up Cache
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Test - name: Test
run: go test -v . run: go test -v ./...
- name: Build - name: Build
uses: goreleaser/goreleaser-action@v2 uses: goreleaser/goreleaser-action@v6
if: "!startsWith(github.ref, 'refs/tags/')" if: "!startsWith(github.ref, 'refs/tags/')"
with: with:
version: latest args: build --snapshot --clean
args: build --snapshot --rm-dist
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Artifact - Linux amd64 - name: Upload Artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
if: "!startsWith(github.ref, 'refs/tags/')" if: "!startsWith(github.ref, 'refs/tags/')"
with: with:
name: ${{ env.APP_NAME }}-dev-${{ env.SHA_SHORT }}-linux-amd64 name: ${{ env.APP_NAME }}-dev-${{ env.SHA_SHORT }}
path: | path: |
./dist/default_linux_amd64_v1/${{ env.APP_NAME }} ./dist/default_linux_amd64_v1/${{ env.APP_NAME }}
- name: Upload Artifact - Linux arm64
uses: actions/upload-artifact@v3
if: "!startsWith(github.ref, 'refs/tags/')"
with:
name: ${{ env.APP_NAME }}-dev-${{ env.SHA_SHORT }}-linux-arm64
path: |
./dist/default_linux_arm64/${{ env.APP_NAME }} ./dist/default_linux_arm64/${{ env.APP_NAME }}
- name: Upload Artifact - macOS arm64
uses: actions/upload-artifact@v3
if: "!startsWith(github.ref, 'refs/tags/')"
with:
name: ${{ env.APP_NAME }}-dev-${{ env.SHA_SHORT }}-macos-arm64
path: |
./dist/default_darwin_arm64/${{ env.APP_NAME }} ./dist/default_darwin_arm64/${{ env.APP_NAME }}
- name: Upload Artifact - Windows amd64
uses: actions/upload-artifact@v3
if: "!startsWith(github.ref, 'refs/tags/')"
with:
name: ${{ env.APP_NAME }}-dev-${{ env.SHA_SHORT }}-windows-amd64
path: |
./dist/default_windows_amd64_v1/${{ env.APP_NAME }}.exe ./dist/default_windows_amd64_v1/${{ env.APP_NAME }}.exe
- name: Release - name: Release
uses: goreleaser/goreleaser-action@v2 uses: goreleaser/goreleaser-action@v6
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
with: with:
version: latest args: release --clean
args: release --rm-dist
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Docker - Set up Buildx - name: Docker - Set up Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- name: Docker - Login to DockerHub - name: Docker - Login to DockerHub
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker - Login to GHCR - name: Docker - Login to GHCR
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@ -116,7 +83,7 @@ jobs:
- name: Docker - Docker meta - name: Docker - Docker meta
id: meta id: meta
uses: docker/metadata-action@v4 uses: docker/metadata-action@v5
with: with:
images: | images: |
${{ env.DOCKERHUB_REPO }} ${{ env.DOCKERHUB_REPO }}
@ -127,11 +94,10 @@ jobs:
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
- name: Docker - Build and push - name: Docker - Build and push
uses: docker/build-push-action@v3 uses: docker/build-push-action@v6
with: with:
context: . context: .
file: .Dockerfile file: .Dockerfile
platforms: ${{ env.PLATFORMS }} platforms: ${{ env.PLATFORMS }}
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}

View File

@ -7,7 +7,7 @@ jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v5 - uses: actions/stale@v9
with: 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.' 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-stale: 90

2
.gitignore vendored
View File

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

View File

@ -1,3 +1,5 @@
version: 2
before: before:
hooks: hooks:
- go mod tidy - go mod tidy
@ -35,13 +37,11 @@ archives:
- id: default - id: default
builds: builds:
- default - default
replacements:
darwin: macos
wrap_in_directory: true wrap_in_directory: true
format: tar.gz formats: tar.gz
format_overrides: format_overrides:
- goos: windows - goos: windows
format: zip formats: zip
files: files:
- LICENSE - LICENSE
- README.md - README.md
@ -49,7 +49,7 @@ archives:
- systemd/* - systemd/*
snapshot: snapshot:
name_template: '{{ incpatch .Version }}-dev-{{.ShortCommit}}' version_template: '{{ incpatch .Version }}-dev-{{.ShortCommit}}'
checksum: checksum:
name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt" name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
@ -83,7 +83,7 @@ nfpms:
dst: /etc/systemd/system/glider@.service dst: /etc/systemd/system/glider@.service
- src: config/glider.conf.example - src: config/glider.conf.example
dst: /etc/glider/glider.conf dst: /etc/glider/glider.conf.example
scripts: scripts:
postinstall: "systemd/postinstall.sh" postinstall: "systemd/postinstall.sh"

View File

@ -1,5 +1,5 @@
# Build Stage # Build Stage
FROM golang:1.18-alpine AS build-env FROM golang:1.24-alpine AS build-env
ADD . /src ADD . /src
RUN apk --no-cache add git \ RUN apk --no-cache add git \
&& cd /src && go build -v -ldflags "-s -w" && cd /src && go build -v -ldflags "-s -w"

View File

@ -3,7 +3,7 @@
[![Go Version](https://img.shields.io/github/go-mod/go-version/nadoo/glider?style=flat-square)](https://go.dev/dl/) [![Go Version](https://img.shields.io/github/go-mod/go-version/nadoo/glider?style=flat-square)](https://go.dev/dl/)
[![Go Report Card](https://goreportcard.com/badge/github.com/nadoo/glider?style=flat-square)](https://goreportcard.com/report/github.com/nadoo/glider) [![Go Report Card](https://goreportcard.com/badge/github.com/nadoo/glider?style=flat-square)](https://goreportcard.com/report/github.com/nadoo/glider)
[![GitHub release](https://img.shields.io/github/v/release/nadoo/glider.svg?style=flat-square&include_prereleases)](https://github.com/nadoo/glider/releases) [![GitHub release](https://img.shields.io/github/v/release/nadoo/glider.svg?style=flat-square&include_prereleases)](https://github.com/nadoo/glider/releases)
[![Actions Status](https://img.shields.io/github/workflow/status/nadoo/glider/Build?style=flat-square)](https://github.com/nadoo/glider/actions) [![Actions Status](https://img.shields.io/github/actions/workflow/status/nadoo/glider/build.yml?branch=dev&style=flat-square)](https://github.com/nadoo/glider/actions)
[![DockerHub](https://img.shields.io/docker/image-size/nadoo/glider?color=blue&label=docker&style=flat-square)](https://hub.docker.com/r/nadoo/glider) [![DockerHub](https://img.shields.io/docker/image-size/nadoo/glider?color=blue&label=docker&style=flat-square)](https://hub.docker.com/r/nadoo/glider)
glider is a forward proxy with multiple protocols support, and also a dns/dhcp server with ipset management features(like dnsmasq). glider is a forward proxy with multiple protocols support, and also a dns/dhcp server with ipset management features(like dnsmasq).
@ -82,7 +82,10 @@ we can set up local listeners as proxy servers, and forward requests to internet
- Binary: [https://github.com/nadoo/glider/releases](https://github.com/nadoo/glider/releases) - Binary: [https://github.com/nadoo/glider/releases](https://github.com/nadoo/glider/releases)
- Docker: `docker pull nadoo/glider` - Docker: `docker pull nadoo/glider`
- Manjaro: `pamac install glider`
- ArchLinux: `sudo pacman -S glider` - ArchLinux: `sudo pacman -S glider`
- Homebrew: `brew install glider`
- MacPorts: `sudo port install glider`
- Source: `go install github.com/nadoo/glider@latest` - Source: `go install github.com/nadoo/glider@latest`
## Usage ## Usage
@ -221,7 +224,7 @@ Help:
see README.md and glider.conf.example for more details. see README.md and glider.conf.example for more details.
-- --
glider 0.16.1, https://github.com/nadoo/glider (glider.proxy@gmail.com) glider 0.16.4, https://github.com/nadoo/glider (glider.proxy@gmail.com)
``` ```
</details> </details>
@ -235,7 +238,10 @@ glider 0.16.1, https://github.com/nadoo/glider (glider.proxy@gmail.com)
Direct scheme: Direct scheme:
direct:// direct://
Only needed when you want to load balance multiple interfaces directly: 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 glider -verbose -listen :8443 -forward direct://#interface=eth0 -forward direct://#interface=eth1 -strategy rr
Or you can use the high availability mode: Or you can use the high availability mode:
@ -407,7 +413,7 @@ Examples:
glider -listen udp://:53 -forward socks5://serverA:1080,udp://8.8.8.8:53 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. -udp tunnel: listen on :53 and forward all udp requests to 8.8.8.8:53 via remote socks5 server.
glider -verbose -listen -dns=:53 -dnsserver=8.8.8.8:53 -forward socks5://serverA:1080 -dnsrecord=abc.com/1.2.3.4 glider -verbose -dns=:53 -dnsserver=8.8.8.8:53 -forward socks5://serverA:1080 -dnsrecord=abc.com/1.2.3.4
-dns over proxy: listen on :53 as dns server, forward to 8.8.8.8:53 via socks5 server. -dns over proxy: listen on :53 as dns server, forward to 8.8.8.8:53 via socks5 server.
``` ```
@ -438,7 +444,7 @@ glider -config CONFIG_PATH
## Linux Daemon ## Linux Daemon
- systemd: [https://github.com/nadoo/glider/blob/master/systemd/](https://github.com/nadoo/glider/blob/master/systemd/) - systemd: [https://github.com/nadoo/glider/tree/main/systemd](https://github.com/nadoo/glider/tree/main/systemd)
- <details> <summary>docker: click to see details</summary> - <details> <summary>docker: click to see details</summary>
@ -480,7 +486,7 @@ glider -config CONFIG_PATH
// _ "github.com/nadoo/glider/proxy/kcp" // _ "github.com/nadoo/glider/proxy/kcp"
``` ```
3. Build it(requires **Go 1.18+** ) 3. Build it:
```bash ```bash
go build -v -ldflags "-s -w" go build -v -ldflags "-s -w"
``` ```
@ -532,5 +538,5 @@ glider -config CONFIG_PATH
- [ipset](https://github.com/nadoo/ipset): netlink ipset package for Go. - [ipset](https://github.com/nadoo/ipset): netlink ipset package for Go.
- [conflag](https://github.com/nadoo/conflag): a drop-in replacement for Go's standard flag package with config file support. - [conflag](https://github.com/nadoo/conflag): a drop-in replacement for Go's standard flag package with config file support.
- [ArchLinux](https://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` - [urlencode](https://www.w3schools.com/tags/ref_urlencode.asp): you should encode special characters in scheme url. e.g., `@`->`%40`

View File

@ -99,12 +99,12 @@ check=disable: disable health check`)
} }
if *scheme != "" { if *scheme != "" {
fmt.Fprintf(flag.Output(), proxy.Usage(*scheme)) fmt.Fprint(flag.Output(), proxy.Usage(*scheme))
os.Exit(0) os.Exit(0)
} }
if *example { if *example {
fmt.Fprintf(flag.Output(), examples) fmt.Fprint(flag.Output(), examples)
os.Exit(0) os.Exit(0)
} }
@ -249,6 +249,6 @@ Examples:
glider -listen udp://:53 -forward socks5://serverA:1080,udp://8.8.8.8:53 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. -udp tunnel: listen on :53 and forward all udp requests to 8.8.8.8:53 via remote socks5 server.
glider -verbose -listen -dns=:53 -dnsserver=8.8.8.8:53 -forward socks5://serverA:1080 -dnsrecord=abc.com/1.2.3.4 glider -verbose -dns=:53 -dnsserver=8.8.8.8:53 -forward socks5://serverA:1080 -dnsrecord=abc.com/1.2.3.4
-dns over proxy: listen on :53 as dns server, forward to 8.8.8.8:53 via socks5 server. -dns over proxy: listen on :53 as dns server, forward to 8.8.8.8:53 via socks5 server.
` `

View File

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

View File

@ -82,7 +82,8 @@ 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 DNS settings #### Client 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:
@ -91,10 +92,10 @@ DNS Resolving:
2. upstream dns server choice: glider will lookup it's rule config and find out the dns server to use for this domain(matched "example1.com" in office.rule, so 208.67.222.222:53 will be chosen) 2. upstream dns server choice: glider will lookup it's rule config and find out the dns server to use for this domain(matched "example1.com" in office.rule, so 208.67.222.222:53 will be chosen)
3. glider uses the forwarder in office.rule to ask 208.67.222.222:53 for the resolve answers(dns over proxy). 3. glider uses the forwarder in office.rule to ask 208.67.222.222:53 for the resolve answers(dns over proxy).
4. glider updates it's office rule config, adds the resolved ip address to it. 4. glider updates it's office rule config, adds the resolved ip address to it.
5. glider adds the resolved ip into ipset "glider", and return the dns answer to client. 5. glider adds the resolved ip into ipset "glider", and returns 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. iptabes matches the ip in ipset "glider" and redirect this request to :1081(glider) 3. iptables matches the ip in ipset "glider" and redirect this request to :1081(glider)
4. glider finds the ip in office rule, and then choose a forwarder in office.rule to complete the request. 4. glider finds the ip in office rule, and then choose a forwarder in office.rule to complete the request.

View File

@ -32,16 +32,17 @@ verbose=True
# different protocols. # different protocols.
# listen on 8443, serve as http/socks5 proxy on the same port. # listen on 8443, serve as http/socks5 proxy on the same port.
listen=:8443 # listen=:8443
listen=127.0.0.1:8443
# listen on 8448 as a ss server. # listen on 8448 as a ss server.
# listen=ss://AEAD_CHACHA20_POLY1305:pass@:8448 # listen=ss://AEAD_CHACHA20_POLY1305:pass@:8448
# listen on 8080 as a http proxy server. # listen on 8080 as a http proxy server.
listen=http://:8080 # listen=http://:8080
# listen on 1080 as a socks5 proxy server. # listen on 1080 as a socks5 proxy server.
listen=socks5://:1080 # listen=socks5://:1080
# listen on 1234 as vless proxy server. # listen on 1234 as vless proxy server.
# listen=vless://uuid@:1234 # listen=vless://uuid@:1234
@ -79,10 +80,10 @@ listen=socks5://:1080
# listen=ws://:1234/path?host=domain.com,vless://707f20ea-d4b8-4d1d-8e2e-2c86cb2ed97a@?fallback=127.0.0.1:80 # listen=ws://:1234/path?host=domain.com,vless://707f20ea-d4b8-4d1d-8e2e-2c86cb2ed97a@?fallback=127.0.0.1:80
# trojan server # trojan server
# listen=trojan://PASSWORD:1234?cert=/path/to/cert&key=/path/to/key&fallback=127.0.0.1 # listen=trojan://PASSWORD@:1234?cert=/path/to/cert&key=/path/to/key&fallback=127.0.0.1
# trojanc server (trojan without tls) # trojanc server (trojan without tls)
# listen=trojanc://PASSWORD:1234?fallback=127.0.0.1 # listen=trojanc://PASSWORD@:1234?fallback=127.0.0.1
# FORWARDERS # FORWARDERS
# ---------- # ----------
@ -139,7 +140,7 @@ listen=socks5://:1080
# forward=tls://server.com:443,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98 # forward=tls://server.com:443,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98
# vmess over websocket # vmess over websocket
# forward=ws://1.1.1.1:80/path?host=server.com,vmess://chacha20-poly1305:5a146038-0b56-4e95-b1dc-5c6f5a32cd98 # forward=ws://1.1.1.1:80/path?host=server.com,vmess://chacha20-poly1305:5a146038-0b56-4e95-b1dc-5c6f5a32cd98@
# vmess over ws over tls # vmess over ws over tls
# forward=tls://server.com:443,ws://,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98 # forward=tls://server.com:443,ws://,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98
@ -222,7 +223,7 @@ checkdisabledonly=false
# we can specify different upstream dns server in rule file for different destinations. # we can specify different upstream dns server in rule file for different destinations.
# Setup a dns forwarding server # Setup a dns forwarding server
dns=:53 # dns=:53
# global remote dns server (you can specify different dns server in rule file) # global remote dns server (you can specify different dns server in rule file)
dnsserver=8.8.8.8:53 dnsserver=8.8.8.8:53
@ -279,7 +280,7 @@ dnsrecord=www.example.com/2606:2800:220:1:248:1893:25c8:1946
# 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

View File

@ -179,7 +179,7 @@ func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) (
ups := c.UpStream(qname) ups := c.UpStream(qname)
server = ups.Server() server = ups.Server()
for i := 0; i < ups.Len(); i++ { 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 {

View File

@ -5,17 +5,14 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"io" "io"
"math/rand" "math/rand/v2"
"net/netip" "net/netip"
"strings" "strings"
) )
// UDPMaxLen is the max size of udp dns request. // UDPMaxLen is the max size of udp dns request.
// https://www.rfc-editor.org/rfc/rfc1035#section-4.2.1 // https://www.dnsflagday.net/2020/
// Messages carried by UDP are restricted to 512 bytes (not counting the IP const UDPMaxLen = 1232
// 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
@ -44,16 +41,16 @@ const ClassINET uint16 = 1
// 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:
// //
// +---------------------+ // +---------------------+
// | Header | // | Header |
// +---------------------+ // +---------------------+
// | Question | the question for the name server // | Question | the question for the name server
// +---------------------+ // +---------------------+
// | Answer | RRs answering the question // | Answer | RRs answering the question
// +---------------------+ // +---------------------+
// | Authority | RRs pointing toward an authority // | Authority | RRs pointing toward an authority
// +---------------------+ // +---------------------+
// | Additional | RRs holding additional information // | Additional | RRs holding additional information
type Message struct { type Message struct {
Header Header
// most dns implementation only support 1 question // most dns implementation only support 1 question
@ -149,7 +146,7 @@ func UnmarshalMessage(b []byte) (*Message, error) {
// resp answers // resp answers
rrIdx := HeaderLen + qLen rrIdx := HeaderLen + qLen
for i := 0; i < int(m.Header.ANCOUNT); i++ { for range int(m.Header.ANCOUNT) {
rr := &RR{} rr := &RR{}
rrLen, err := m.UnmarshalRR(rrIdx, rr) rrLen, err := m.UnmarshalRR(rrIdx, rr)
if err != nil { if err != nil {
@ -169,22 +166,21 @@ func UnmarshalMessage(b []byte) (*Message, error) {
// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.1 // https://www.rfc-editor.org/rfc/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
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | ID | // | ID |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// |QR| Opcode |AA|TC|RD|RA| Z | RCODE | // |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | QDCOUNT | // | QDCOUNT |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | ANCOUNT | // | ANCOUNT |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | NSCOUNT | // | NSCOUNT |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | ARCOUNT | // | ARCOUNT |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
type Header struct { type Header struct {
ID uint16 ID uint16
Bits uint16 Bits uint16
@ -214,10 +210,11 @@ func (h *Header) SetAncount(ancount int) {
h.ANCOUNT = uint16(ancount) h.ANCOUNT = uint16(ancount)
} }
func (h *Header) setFlag(QR uint16, Opcode uint16, AA uint16, // Not used now, but keep it for future use.
TC uint16, RD uint16, RA uint16, RCODE uint16) { // func (h *Header) setFlag(QR uint16, Opcode uint16, AA uint16,
h.Bits = QR<<15 + Opcode<<11 + AA<<10 + TC<<9 + RD<<8 + RA<<7 + RCODE // TC uint16, RD uint16, RA uint16, RCODE uint16) {
} // h.Bits = QR<<15 + Opcode<<11 + AA<<10 + TC<<9 + RD<<8 + RA<<7 + RCODE
// }
// MarshalTo marshals header struct to []byte and write to w. // MarshalTo marshals header struct to []byte and write to w.
func (h *Header) MarshalTo(w io.Writer) (int, error) { func (h *Header) MarshalTo(w io.Writer) (int, error) {
@ -250,17 +247,17 @@ func UnmarshalHeader(b []byte, h *Header) error {
// 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:
// //
// 1 1 1 1 1 1 // 1 1 1 1 1 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | | // | |
// / QNAME / // / QNAME /
// / / // / /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | QTYPE | // | QTYPE |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | QCLASS | // | QCLASS |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
type Question struct { type Question struct {
QNAME string QNAME string
QTYPE uint16 QTYPE uint16
@ -328,26 +325,26 @@ func (m *Message) UnmarshalQuestion(b []byte, q *Question) (n int, err error) {
// records is specified in the corresponding count field in the header. // records is specified in the corresponding count field in the header.
// Each resource record has the following format: // Each resource record has the following format:
// //
// 1 1 1 1 1 1 // 1 1 1 1 1 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | | // | |
// / / // / /
// / NAME / // / NAME /
// | | // | |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | TYPE | // | TYPE |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | CLASS | // | CLASS |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | TTL | // | TTL |
// | | // | |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | RDLENGTH | // | RDLENGTH |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
// / RDATA / // / RDATA /
// / / // / /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
type RR struct { type RR struct {
NAME string NAME string
TYPE uint16 TYPE uint16

31
go.mod
View File

@ -1,36 +1,29 @@
module github.com/nadoo/glider module github.com/nadoo/glider
go 1.18 go 1.24
require ( require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
github.com/nadoo/conflag v0.3.1 github.com/nadoo/conflag v0.3.1
github.com/nadoo/ipset v0.5.0 github.com/nadoo/ipset v0.5.0
github.com/xtaci/kcp-go/v5 v5.6.1 github.com/xtaci/kcp-go/v5 v5.6.18
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 golang.org/x/crypto v0.35.0
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 golang.org/x/sys v0.30.0
) )
require ( require (
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 // indirect github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 // indirect
github.com/klauspost/cpuid/v2 v2.0.12 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/klauspost/reedsolomon v1.9.16 // indirect github.com/klauspost/reedsolomon v1.12.4 // indirect
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/mdlayher/raw v0.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/templexxx/cpu v0.0.10-0.20211111114238-98168dcec14a // indirect github.com/templexxx/cpu v0.1.1 // indirect
github.com/templexxx/xorsimd v0.4.1 // indirect github.com/templexxx/xorsimd v0.4.3 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect golang.org/x/net v0.35.0 // indirect
) )
// Replace dependency modules with local developing copy
// use `go list -m all` to confirm the final module used
// replace (
// github.com/nadoo/conflag => ../conflag
// )

143
go.sum
View File

@ -18,7 +18,6 @@ github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:ucvhdsUCE3TH0Lo
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -34,154 +33,82 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f h1:l1QCwn715k8nYkj4Ql50rzEog3WnMdrd4YYMMwemxEo= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= github.com/klauspost/reedsolomon v1.12.4 h1:5aDr3ZGoJbgu/8+j45KtUJxzYm8k08JGtB9Wx1VQ4OA=
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= github.com/klauspost/reedsolomon v1.12.4/go.mod h1:d3CzOMOt0JXGIFZm1StgkyF14EYr3xneR2rNWo7NcMU=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/reedsolomon v1.9.9/go.mod h1:O7yFFHiQwDR6b2t63KPUpccPtNdp5ADgh1gg4fd12wo=
github.com/klauspost/reedsolomon v1.9.16 h1:mR0AwphBwqFv/I3B9AHtNKvzuowI1vrj8/3UX4XRmHA=
github.com/klauspost/reedsolomon v1.9.16/go.mod h1:eqPAcE7xar5CIzcdfwydOEdcmchAKAP/qs14y4GCBOk=
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 h1:2oDp6OOhLxQ9JBoUuysVz9UZ9uI6oLUbvAZu0x8o+vE=
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118/go.mod h1:ZFUnHIVchZ9lJoWoEGUg8Q3M4U8aNNWA3CVSUTkW4og=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/packet v1.0.0 h1:InhZJbdShQYt6XV2GPj5XHxChzOfhJJOMbvnGAmOfQ8=
github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU=
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.1.0 h1:K4PFMVy+AFsp0Zdlrts7yNhxc/uXoPVHi9RzRvtZF2Y=
github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5s9Sg=
github.com/mdlayher/socket v0.2.1 h1:F2aaOwb53VsBE+ebRS9bLd7yPOfYUMC8lOODdCBDY6w=
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104/go.mod h1:wqKykBG2QzQDJEzvRkcS8x6MiSJkF52hXZsXcjaB3ls=
github.com/nadoo/conflag v0.3.1 h1:4pHkLIz8PUsfg6ajNYRRSY3bt6m2LPsu6KOzn5uIXQw= 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/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 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc=
github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ= 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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= github.com/templexxx/cpu v0.1.1 h1:isxHaxBXpYFWnk2DReuKkigaZyrjs2+9ypIdGP4h+HI=
github.com/templexxx/cpu v0.0.7/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= github.com/templexxx/cpu v0.1.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/cpu v0.0.10-0.20211111114238-98168dcec14a h1:f0GQM8LuKYnXdNLcAg+di6PULSlR5iQtZT3bDwDRiA0= github.com/templexxx/xorsimd v0.4.3 h1:9AQTFHd7Bhk3dIT7Al2XeBX5DWOvsUPZCuhyAtNbHjU=
github.com/templexxx/cpu v0.0.10-0.20211111114238-98168dcec14a/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= github.com/templexxx/xorsimd v0.4.3/go.mod h1:oZQcD6RFDisW2Am58dSAGwwL6rHjbzrlu25VDqfWkQg=
github.com/templexxx/xorsimd v0.4.1 h1:iUZcywbOYDRAZUasAs2eSCUW8eobuZDy0I9FJiORkVg=
github.com/templexxx/xorsimd v0.4.1/go.mod h1:W+ffZz8jJMH2SXwuKu9WhygqBMbFnp14G2fqEr8qaNo=
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 h1:hl6sK6aFgTLISijk6xIzeqnPzQcsLqqvL6vEfTPinME= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= github.com/xtaci/kcp-go/v5 v5.6.18 h1:7oV4mc272pcnn39/13BB11Bx7hJM4ogMIEokJYVWn4g=
github.com/xtaci/kcp-go/v5 v5.6.1 h1:Pwn0aoeNSPF9dTS7IgiPXn0HEtaIlVb6y5UKWPsx8bI= github.com/xtaci/kcp-go/v5 v5.6.18/go.mod h1:75S1AKYYzNUSXIv30h+jPKJYZUwqpfvLshu63nCNSOM=
github.com/xtaci/kcp-go/v5 v5.6.1/go.mod h1:W3kVPyNYwZ06p79dNwFWQOVFrdcBpDBsdyvK8moQrYo=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/arch v0.0.0-20190909030613-46d78d1859ac/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 h1:Tgea0cVUD0ivh5ADBX4WwuI12DUd2to3nCYe2eayMIw= golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-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-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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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-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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-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-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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 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-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@ -196,9 +123,7 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.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.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,25 @@
// Package smux is a multiplexing library for Golang. // MIT License
// //
// It relies on an underlying connection to provide reliability and ordering, such as TCP or KCP, // Copyright (c) 2016-2017 xtaci
// and provides stream-oriented multiplexing over a single channel. //
// 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 package smux
import ( import (

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,32 +0,0 @@
package smux
import (
"container/heap"
"testing"
)
func TestShaper(t *testing.T) {
w1 := writeRequest{prio: 10}
w2 := writeRequest{prio: 10}
w3 := writeRequest{prio: 20}
w4 := writeRequest{prio: 100}
w5 := writeRequest{prio: (1 << 32) - 1}
var reqs shaperHeap
heap.Push(&reqs, w5)
heap.Push(&reqs, w4)
heap.Push(&reqs, w3)
heap.Push(&reqs, w2)
heap.Push(&reqs, w1)
var lastPrio = reqs[0].prio
for len(reqs) > 0 {
w := heap.Pop(&reqs).(writeRequest)
if int32(w.prio-lastPrio) < 0 {
t.Fatal("incorrect shaper priority")
}
t.Log("prio:", w.prio)
lastPrio = w.prio
}
}

View File

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

View File

@ -139,7 +139,10 @@ func init() {
Direct scheme: Direct scheme:
direct:// direct://
Only needed when you want to load balance multiple interfaces directly: 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 glider -verbose -listen :8443 -forward direct://#interface=eth0 -forward direct://#interface=eth1 -strategy rr
Or you can use the high availability mode: Or you can use the high availability mode:

View File

@ -33,6 +33,8 @@ func (s *HTTP) Dial(network, addr string) (net.Conn, error) {
} }
buf := pool.GetBytesBuffer() buf := pool.GetBytesBuffer()
defer pool.PutBytesBuffer(buf)
buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n") buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n")
buf.WriteString("Host: " + addr + "\r\n") buf.WriteString("Host: " + addr + "\r\n")
buf.WriteString("Proxy-Connection: Keep-Alive\r\n") buf.WriteString("Proxy-Connection: Keep-Alive\r\n")
@ -45,7 +47,6 @@ func (s *HTTP) Dial(network, addr string) (net.Conn, error) {
// header ended // header ended
buf.WriteString("\r\n") buf.WriteString("\r\n")
_, err = rc.Write(buf.Bytes()) _, err = rc.Write(buf.Bytes())
pool.PutBytesBuffer(buf)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -64,10 +64,7 @@ func (c *TLSObfsConn) Write(b []byte) (int, error) {
n := len(b) n := len(b)
for i := 0; i < n; i += chunkSize { for i := 0; i < n; i += chunkSize {
buf.Reset() buf.Reset()
end := i + chunkSize end := min(i+chunkSize, n)
if end > n {
end = n
}
buf.Write([]byte{0x17, 0x03, 0x03}) buf.Write([]byte{0x17, 0x03, 0x03})
binary.Write(buf, binary.BigEndian, uint16(len(b[i:end]))) binary.Write(buf, binary.BigEndian, uint16(len(b[i:end])))

View File

@ -60,7 +60,7 @@ func (s *RedirProxy) ListenAndServe() {
return return
} }
log.F("[redir] listening TCP on " + s.addr) log.F("[redir] listening TCP on %s", s.addr)
for { for {
c, err := l.Accept() c, err := l.Accept()

View File

@ -59,7 +59,7 @@ func (s *SS) Serve(c net.Conn) {
tgt, err := socks.ReadAddr(sc) tgt, err := socks.ReadAddr(sc)
if err != nil { if err != nil {
log.F("[ss] failed to get target address: %v", err) log.F("[ss] %s <-> target error: %v", c.RemoteAddr(), err)
proxy.Copy(io.Discard, c) // https://github.com/nadoo/glider/issues/180 proxy.Copy(io.Discard, c) // https://github.com/nadoo/glider/issues/180
return return
} }

View File

@ -5,10 +5,10 @@ import (
"crypto/cipher" "crypto/cipher"
"crypto/des" "crypto/des"
"crypto/md5" "crypto/md5"
"crypto/rand"
"crypto/rc4" "crypto/rc4"
"encoding/binary" "encoding/binary"
"errors" "errors"
"math/rand"
"github.com/aead/chacha20" "github.com/aead/chacha20"
"github.com/dgryski/go-camellia" "github.com/dgryski/go-camellia"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,9 +6,9 @@ import (
"bytes" "bytes"
"crypto/aes" "crypto/aes"
stdCipher "crypto/cipher" stdCipher "crypto/cipher"
"crypto/rand"
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
"math/rand"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -194,7 +194,7 @@ func (a *authChainA) packAuthData(data []byte) (outData []byte) {
copy(a.userKey, a.Key) copy(a.userKey, a.Key)
} }
} }
for i := 0; i < 4; i++ { for i := range 4 {
uid[i] = a.uid[i] ^ a.lastClientHash[8+i] uid[i] = a.uid[i] ^ a.lastClientHash[8+i]
} }
base64UserKey = base64.StdEncoding.EncodeToString(a.userKey) base64UserKey = base64.StdEncoding.EncodeToString(a.userKey)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,13 +5,14 @@ import (
"crypto/cipher" "crypto/cipher"
"crypto/hmac" "crypto/hmac"
"crypto/md5" "crypto/md5"
crand "crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"hash/fnv" "hash/fnv"
"io" "io"
"math/rand" "math/rand/v2"
"net" "net"
"runtime" "runtime"
"strings" "strings"
@ -116,15 +117,12 @@ func NewClient(uuidStr, security string, alterID int, aead bool) (*Client, error
return nil, errors.New("unknown security type: " + security) return nil, errors.New("unknown security type: " + security)
} }
// NOTE: give rand a new seed to avoid the same sequence of values
rand.Seed(time.Now().UnixNano())
return c, nil return c, nil
} }
// NewConn returns a new vmess conn. // NewConn returns a new vmess conn.
func (c *Client) NewConn(rc net.Conn, target string, cmd CmdType) (*Conn, error) { func (c *Client) NewConn(rc net.Conn, target string, cmd CmdType) (*Conn, error) {
r := rand.Intn(c.count) r := rand.IntN(c.count)
conn := &Conn{user: c.users[r], opt: c.opt, aead: c.aead, security: c.security, Conn: rc} conn := &Conn{user: c.users[r], opt: c.opt, aead: c.aead, security: c.security, Conn: rc}
var err error var err error
@ -134,12 +132,12 @@ func (c *Client) NewConn(rc net.Conn, target string, cmd CmdType) (*Conn, error)
} }
randBytes := pool.GetBuffer(32) randBytes := pool.GetBuffer(32)
rand.Read(randBytes) crand.Read(randBytes)
copy(conn.reqBodyIV[:], randBytes[:16]) copy(conn.reqBodyIV[:], randBytes[:16])
copy(conn.reqBodyKey[:], randBytes[16:32]) copy(conn.reqBodyKey[:], randBytes[16:32])
pool.PutBuffer(randBytes) pool.PutBuffer(randBytes)
conn.reqRespV = byte(rand.Intn(1 << 8)) conn.reqRespV = byte(rand.IntN(1 << 8))
if conn.aead { if conn.aead {
bodyIV := sha256.Sum256(conn.reqBodyIV[:]) bodyIV := sha256.Sum256(conn.reqBodyIV[:])
@ -195,7 +193,7 @@ func (c *Conn) Request(cmd CmdType) error {
buf.WriteByte(c.opt) // Opt buf.WriteByte(c.opt) // Opt
// pLen and Sec // pLen and Sec
paddingLen := rand.Intn(16) paddingLen := rand.IntN(16)
pSec := byte(paddingLen<<4) | c.security // P(4bit) and Sec(4bit) pSec := byte(paddingLen<<4) | c.security // P(4bit) and Sec(4bit)
buf.WriteByte(pSec) buf.WriteByte(pSec)
@ -210,7 +208,7 @@ func (c *Conn) Request(cmd CmdType) error {
// padding // padding
if paddingLen > 0 { if paddingLen > 0 {
padding := pool.GetBuffer(paddingLen) padding := pool.GetBuffer(paddingLen)
rand.Read(padding) crand.Read(padding)
buf.Write(padding) buf.Write(padding)
pool.PutBuffer(padding) pool.PutBuffer(padding)
} }

View File

@ -43,7 +43,7 @@ func nextID(oldID [16]byte) (newID [16]byte) {
func (u *User) GenAlterIDUsers(alterID int) []*User { func (u *User) GenAlterIDUsers(alterID int) []*User {
users := make([]*User, alterID) users := make([]*User, alterID)
preID := u.UUID preID := u.UUID
for i := 0; i < alterID; i++ { for i := range alterID {
newID := nextID(preID) newID := nextID(preID)
// NOTE: alterID user is a user which have a different uuid but a same cmdkey with the primary user. // NOTE: alterID user is a user which have a different uuid but a same cmdkey with the primary user.
users[i] = &User{UUID: newID, CmdKey: u.CmdKey} users[i] = &User{UUID: newID, CmdKey: u.CmdKey}

View File

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

View File

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

View File

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

View File

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

View File

@ -25,7 +25,7 @@ package ws
import ( import (
"encoding/binary" "encoding/binary"
"io" "io"
"math/rand" "math/rand/v2"
"net" "net"
"github.com/nadoo/glider/pkg/pool" "github.com/nadoo/glider/pkg/pool"
@ -93,7 +93,7 @@ func (w *frameWriter) Write(b []byte) (int, error) {
defer pool.PutBuffer(payload) defer pool.PutBuffer(payload)
// payload with mask // payload with mask
for i := 0; i < nPayload; i++ { for i := range nPayload {
payload[i] = b[i] ^ w.maskKey[i%4] payload[i] = b[i] ^ w.maskKey[i%4]
} }

View File

@ -225,7 +225,7 @@ func (p *FwdrGroup) Check() {
log.F("[group] %s: using check config: %s", p.name, p.config.Check) log.F("[group] %s: using check config: %s", p.name, p.config.Check)
for i := 0; i < len(p.fwdrs); i++ { for i := range p.fwdrs {
go p.check(p.fwdrs[i], checker) go p.check(p.fwdrs[i], checker)
} }
} }

View File

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

View File

@ -1,3 +1,5 @@
//go:build linux
package dhcpd package dhcpd
import ( import (
@ -126,58 +128,69 @@ func (d *dhcpd) handleDHCP(serverIP net.IP, mask net.IPMask, pool *Pool) server4
return return
} }
var reqIP netip.Addr
var reqType, replyType dhcpv4.MessageType var reqType, replyType dhcpv4.MessageType
switch reqType = m.MessageType(); reqType {
reqType = m.MessageType()
log.F("[dpcpd] %s: %s from %v(%v)", d.name, reqType, m.ClientHWAddr, m.ClientIPAddr)
switch reqType {
case dhcpv4.MessageTypeDiscover: case dhcpv4.MessageTypeDiscover:
replyType = dhcpv4.MessageTypeOffer replyType = dhcpv4.MessageTypeOffer
case dhcpv4.MessageTypeRequest, dhcpv4.MessageTypeInform: case dhcpv4.MessageTypeInform:
replyType = dhcpv4.MessageTypeAck replyType = dhcpv4.MessageTypeAck
case dhcpv4.MessageTypeRelease: case dhcpv4.MessageTypeRequest:
replyType = dhcpv4.MessageTypeAck
if m.Options.Has(dhcpv4.OptionRequestedIPAddress) {
reqIP, _ = netip.AddrFromSlice(m.Options.Get(dhcpv4.OptionRequestedIPAddress))
} else {
// client uses Unicast to renew ip address lease, just take client ip
reqIP = netip.AddrFrom4([4]byte(m.ClientIPAddr.To4()))
}
case dhcpv4.MessageTypeRelease, dhcpv4.MessageTypeDecline:
pool.ReleaseIP(m.ClientHWAddr) pool.ReleaseIP(m.ClientHWAddr)
log.F("[dpcpd] %s:%v released ip %v", d.name, m.ClientHWAddr, m.ClientIPAddr)
return
case dhcpv4.MessageTypeDecline:
pool.ReleaseIP(m.ClientHWAddr)
log.F("[dpcpd] %s: received decline message from %v", d.name, m.ClientHWAddr)
return return
default: default:
log.F("[dpcpd] %s: can't handle type %v", d.name, reqType) log.F("[dpcpd] %s: can't handle type %v from %v", d.name, reqType, m.ClientHWAddr)
return return
} }
replyIP, err := pool.LeaseIP(m.ClientHWAddr) replyIP, err := pool.LeaseIP(m.ClientHWAddr, reqIP)
if err != nil { if err != nil {
log.F("[dpcpd] %s: can not assign IP, error %s", d.name, err) log.F("[dpcpd] %s: can not assign IP for %v, error: %s", d.name, m.ClientHWAddr, err)
return return
} }
reply, err := dhcpv4.NewReplyFromRequest(m, if reqType == dhcpv4.MessageTypeRequest && !reqIP.IsUnspecified() && reqIP != replyIP {
replyType = dhcpv4.MessageTypeNak
}
resp, err := dhcpv4.NewReplyFromRequest(m,
dhcpv4.WithMessageType(replyType), dhcpv4.WithMessageType(replyType),
dhcpv4.WithServerIP(serverIP),
dhcpv4.WithNetmask(mask), dhcpv4.WithNetmask(mask),
dhcpv4.WithYourIP(replyIP.AsSlice()), dhcpv4.WithYourIP(replyIP.AsSlice()),
dhcpv4.WithRouter(serverIP), dhcpv4.WithRouter(serverIP),
dhcpv4.WithDNS(serverIP), dhcpv4.WithDNS(serverIP),
// RFC 2131, Section 4.3.1. Server Identifier: MUST dhcpv4.WithServerIP(serverIP), //
dhcpv4.WithOption(dhcpv4.OptServerIdentifier(serverIP)),
// RFC 2131, Section 4.3.1. IP lease time: MUST // RFC 2131, Section 4.3.1. IP lease time: MUST
dhcpv4.WithOption(dhcpv4.OptIPAddressLeaseTime(d.lease)), dhcpv4.WithOption(dhcpv4.OptIPAddressLeaseTime(d.lease)),
// RFC 2131, Section 4.3.1. Server Identifier: MUST
dhcpv4.WithOption(dhcpv4.OptServerIdentifier(serverIP)),
) )
if err != nil { if err != nil {
log.F("[dpcpd] %s: can not create reply message, error %s", d.name, err) log.F("[dpcpd] %s: can not create reply message, error: %s", d.name, err)
return return
} }
if val := m.Options.Get(dhcpv4.OptionClientIdentifier); len(val) > 0 { if err := reply(d.iface, resp); err != nil {
reply.UpdateOption(dhcpv4.OptGeneric(dhcpv4.OptionClientIdentifier, val)) log.F("[dpcpd] %s: could not write to %v(%v): %s",
} d.name, resp.ClientHWAddr, peer, err)
if _, err := conn.WriteTo(reply.ToBytes(), peer); err != nil {
log.F("[dpcpd] %s: could not write to client %s(%s): %s", d.name, peer, reply.ClientHWAddr, err)
return return
} }
log.F("[dpcpd] %s: lease %v to client %v", d.name, replyIP, reply.ClientHWAddr) log.F("[dpcpd] %s: %s to %v for %v",
d.name, replyType, resp.ClientHWAddr, replyIP)
} }
} }

View File

@ -1,9 +1,11 @@
//go:build linux
package dhcpd package dhcpd
import ( import (
"bytes" "bytes"
"errors" "errors"
"math/rand" "math/rand/v2"
"net" "net"
"net/netip" "net/netip"
"sync" "sync"
@ -38,13 +40,12 @@ func NewPool(lease time.Duration, start, end netip.Addr) (*Pool, error) {
for n := s; n <= e; n++ { for n := s; n <= e; n++ {
items = append(items, &item{ip: numToIPv4(n)}) items = append(items, &item{ip: numToIPv4(n)})
} }
rand.Seed(time.Now().Unix())
p := &Pool{items: items, lease: lease} p := &Pool{items: items, lease: lease}
go func() { go func() {
for now := range time.Tick(time.Second) { for now := range time.Tick(time.Second) {
p.mutex.Lock() p.mutex.Lock()
for i := 0; i < len(items); i++ { for i := range len(items) {
if !items[i].expire.IsZero() && now.After(items[i].expire) { if !items[i].expire.IsZero() && now.After(items[i].expire) {
items[i].mac = nil items[i].mac = nil
items[i].expire = time.Time{} items[i].expire = time.Time{}
@ -58,17 +59,31 @@ func NewPool(lease time.Duration, start, end netip.Addr) (*Pool, error) {
} }
// LeaseIP leases an ip to mac from dhcp pool. // LeaseIP leases an ip to mac from dhcp pool.
func (p *Pool) LeaseIP(mac net.HardwareAddr) (netip.Addr, error) { func (p *Pool) LeaseIP(mac net.HardwareAddr, ip netip.Addr) (netip.Addr, error) {
p.mutex.Lock() p.mutex.Lock()
defer p.mutex.Unlock() defer p.mutex.Unlock()
// static ip and leased ip
for _, item := range p.items { for _, item := range p.items {
if bytes.Equal(mac, item.mac) { if bytes.Equal(mac, item.mac) {
if !item.expire.IsZero() {
item.expire = time.Now().Add(p.lease)
}
return item.ip, nil return item.ip, nil
} }
} }
idx := rand.Intn(len(p.items)) // requested ip
for _, item := range p.items {
if item.ip == ip && item.mac == nil {
item.mac = mac
item.expire = time.Now().Add(p.lease)
return item.ip, nil
}
}
// lease new ip
idx := rand.IntN(len(p.items))
for _, item := range p.items[idx:] { for _, item := range p.items[idx:] {
if item.mac == nil { if item.mac == nil {
item.mac = mac item.mac = mac
@ -96,7 +111,7 @@ func (p *Pool) LeaseStaticIP(mac net.HardwareAddr, ip netip.Addr) {
for _, item := range p.items { for _, item := range p.items {
if item.ip == ip { if item.ip == ip {
item.mac = mac item.mac = mac
item.expire = time.Now().Add(time.Hour * 24 * 365 * 50) // 50 years item.expire = time.Time{}
} }
} }
} }
@ -107,7 +122,8 @@ func (p *Pool) ReleaseIP(mac net.HardwareAddr) {
defer p.mutex.Unlock() defer p.mutex.Unlock()
for _, item := range p.items { for _, item := range p.items {
if bytes.Equal(mac, item.mac) { // not static ip
if !item.expire.IsZero() && bytes.Equal(mac, item.mac) {
item.mac = nil item.mac = nil
item.expire = time.Time{} item.expire = time.Time{}
} }

86
service/dhcpd/reply.go Normal file
View File

@ -0,0 +1,86 @@
//go:build linux
package dhcpd
import (
"encoding/binary"
"fmt"
"net"
"syscall"
"github.com/insomniacslk/dhcp/dhcpv4"
"github.com/nadoo/glider/pkg/log"
)
func reply(iface *net.Interface, resp *dhcpv4.DHCPv4) error {
p := [590]byte{12: 0x08, //ethernet layer: 14 bytes
14: 0x45, 16: 0x02, 17: 0x40, 22: 0x40, 23: 0x11, //ip layer: 20 bytes
35: 67, 37: 68, 38: 0x02, 39: 0x2c, //udp layer: 8 bytes
}
copy(p[0:], resp.ClientHWAddr[0:6])
copy(p[6:], iface.HardwareAddr[0:6])
copy(p[26:], resp.ServerIPAddr[0:4])
copy(p[30:], resp.YourIPAddr[0:4])
// ip layer checksum
checksum := checksum(p[14:34])
binary.BigEndian.PutUint16(p[24:], checksum)
// dhcp payload
copy(p[42:], resp.ToBytes())
// udp layer checksum, set to zero
// https://datatracker.ietf.org/doc/html/rfc768
// An all zero transmitted checksum value means that the transmitter generated no
// checksum (for debugging or for higher level protocols that don't care).
fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, 0)
if err != nil {
return fmt.Errorf("cannot open socket: %v", err)
}
defer func() {
err = syscall.Close(fd)
if err != nil {
log.F("dhcpd: cannot close socket: %v", err)
}
}()
err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
if err != nil {
log.F("dhcpd: cannot set option for socket: %v", err)
}
var hwAddr [8]byte
copy(hwAddr[0:6], resp.ClientHWAddr[0:6])
ethAddr := syscall.SockaddrLinklayer{
Protocol: 0,
Ifindex: iface.Index,
Halen: 6,
Addr: hwAddr,
}
err = syscall.Sendto(fd, p[:], 0, &ethAddr)
if err != nil {
return fmt.Errorf("cannot send frame via socket: %v", err)
}
return nil
}
func checksum(bytes []byte) uint16 {
var csum uint32
for i := 0; i < len(bytes); i += 2 {
csum += uint32(bytes[i]) << 8
csum += uint32(bytes[i+1])
}
for {
// Break when sum is less or equals to 0xFFFF
if csum <= 65535 {
break
}
// Add carry to the sum
csum = (csum >> 16) + uint32(uint16(csum))
}
// Flip all the bits
return ^uint16(csum)
}

View File

@ -2,6 +2,10 @@
set -e set -e
if test ! -f "/etc/glider/glider.conf"; then
cp /etc/glider/glider.conf.example /etc/glider/glider.conf
fi
/bin/systemctl daemon-reload /bin/systemctl daemon-reload
if /bin/systemctl is-active --quiet glider@glider; then if /bin/systemctl is-active --quiet glider@glider; then