mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
142 Commits
v1.9.0-alp
...
v1.10.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82269a4937 | ||
|
|
1b74c3c245 | ||
|
|
3d30ea02ae | ||
|
|
1abe60755b | ||
|
|
826ebd56a6 | ||
|
|
6635876db8 | ||
|
|
45c0c46479 | ||
|
|
9e727a440b | ||
|
|
0ee9ed78bb | ||
|
|
49e8e39ec1 | ||
|
|
8d8aa9d8e3 | ||
|
|
142ff1b455 | ||
|
|
74d662f7a3 | ||
|
|
085f603377 | ||
|
|
460fae83dc | ||
|
|
bb9bd9bff6 | ||
|
|
c2354ebf25 | ||
|
|
c1f4755c4e | ||
|
|
0ca5909b06 | ||
|
|
e77a8114c5 | ||
|
|
f1393235ff | ||
|
|
bdba2365de | ||
|
|
ce0da5b557 | ||
|
|
3853201412 | ||
|
|
7003ef40a3 | ||
|
|
59ec92228c | ||
|
|
0eeb2da323 | ||
|
|
977b0fac02 | ||
|
|
51964801ff | ||
|
|
e08c052fc9 | ||
|
|
53927d8bbd | ||
|
|
968b9bc217 | ||
|
|
69dc87aa6d | ||
|
|
4193df375f | ||
|
|
5ff7006326 | ||
|
|
a89107ea9d | ||
|
|
9ffdbba2ed | ||
|
|
65c71049ea | ||
|
|
7d4e6a7f4e | ||
|
|
d612620c5d | ||
|
|
8a9a77a438 | ||
|
|
a2098c18e1 | ||
|
|
cf2181dd3a | ||
|
|
5899e95ff1 | ||
|
|
d7160c19cf | ||
|
|
da9e22b4e6 | ||
|
|
0e120f8a44 | ||
|
|
d918863ac5 | ||
|
|
2ae192305c | ||
|
|
71d1879bd6 | ||
|
|
917514e09f | ||
|
|
5327aeaea4 | ||
|
|
93ae3f7a1e | ||
|
|
f24a2aed7d | ||
|
|
0517ceef76 | ||
|
|
830ea46932 | ||
|
|
cd0fcd5ddc | ||
|
|
003176f069 | ||
|
|
71d92518c1 | ||
|
|
b5dcd6bf59 | ||
|
|
11c7b4a866 | ||
|
|
ee14135298 | ||
|
|
cbcf005f37 | ||
|
|
daee0b154e | ||
|
|
d530c724c0 | ||
|
|
7f698c1104 | ||
|
|
7a4a44c6d2 | ||
|
|
44277e5dd2 | ||
|
|
1f470c69c4 | ||
|
|
742adacce7 | ||
|
|
32e1d5a5e2 | ||
|
|
cb9f4ce597 | ||
|
|
4b1a6185ba | ||
|
|
8d85c92356 | ||
|
|
c6164c9eca | ||
|
|
3c85b8bc48 | ||
|
|
8b8fb4344c | ||
|
|
e85a38e059 | ||
|
|
f3ac91673a | ||
|
|
0f1e58b917 | ||
|
|
c4cfe24aef | ||
|
|
3d73b159ba | ||
|
|
0ae1afef44 | ||
|
|
a5e2a4073b | ||
|
|
b6cb3948a3 | ||
|
|
7b0f5061dc | ||
|
|
76f20482f7 | ||
|
|
e735a5bdc8 | ||
|
|
70381e93c8 | ||
|
|
07a40716e8 | ||
|
|
5fea5956db | ||
|
|
d20a389043 | ||
|
|
4a4180bde5 | ||
|
|
7ecb6daabb | ||
|
|
712bdd9ae5 | ||
|
|
a3b74591a7 | ||
|
|
2f4abc6523 | ||
|
|
965ab075d9 | ||
|
|
ed2f8b9637 | ||
|
|
0f71ce5120 | ||
|
|
f8085ab111 | ||
|
|
f61b272cbf | ||
|
|
59d437b9d2 | ||
|
|
a7338fdc2b | ||
|
|
d88860928e | ||
|
|
20a2e38f47 | ||
|
|
acd438be23 | ||
|
|
e27fb51b54 | ||
|
|
adc38b26eb | ||
|
|
7e943e743a | ||
|
|
ceffcc0ad2 | ||
|
|
fdc451f7c6 | ||
|
|
b48c471e6a | ||
|
|
4b1fabd007 | ||
|
|
2b5eb1c59e | ||
|
|
e2d3862e64 | ||
|
|
4f5e7b974d | ||
|
|
21dedddd93 | ||
|
|
e02502bec0 | ||
|
|
ba67633ee8 | ||
|
|
7fd9abe802 | ||
|
|
78a5f59202 | ||
|
|
8d0da685d2 | ||
|
|
e6644f784e | ||
|
|
2b93b74d38 | ||
|
|
dd52c26ae1 | ||
|
|
f288e3898b | ||
|
|
1bc893a73a | ||
|
|
7359fdf195 | ||
|
|
02b7041de6 | ||
|
|
96ac931b11 | ||
|
|
3077a82650 | ||
|
|
de998c5119 | ||
|
|
d32c30c4b7 | ||
|
|
4823023806 | ||
|
|
bb355d17b2 | ||
|
|
aaf30bf92b | ||
|
|
f8c400cffc | ||
|
|
3c24411e14 | ||
|
|
4a44aa3c21 | ||
|
|
8db2ae0c83 | ||
|
|
80d1aebcb7 |
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -65,6 +65,12 @@ body:
|
||||
For Apple platform clients, please check `Settings - View Service Log` for crash logs.
|
||||
For the Android client, please check the `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` file for crash logs.
|
||||
render: shell
|
||||
- type: checkboxes
|
||||
id: supporter
|
||||
attributes:
|
||||
label: Supporter
|
||||
options:
|
||||
- label: I am a [sponsor](https://github.com/sponsors/nekohasekai/)
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Integrity requirements
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
6
.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
@@ -65,6 +65,12 @@ body:
|
||||
对于 Apple 平台图形客户端程序,请检查 `Settings - View Service Log` 以导出崩溃日志。
|
||||
对于 Android 图形客户端程序,请检查 `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` 文件以导出崩溃日志。
|
||||
render: shell
|
||||
- type: checkboxes
|
||||
id: supporter
|
||||
attributes:
|
||||
label: 支持我们
|
||||
options:
|
||||
- label: 我已经 [赞助](https://github.com/sponsors/nekohasekai/)
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 完整性要求
|
||||
|
||||
28
.github/workflows/debug.yml
vendored
28
.github/workflows/debug.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
@@ -33,32 +33,12 @@ jobs:
|
||||
- name: Run Test
|
||||
run: |
|
||||
go test -v ./...
|
||||
build_go118:
|
||||
name: Debug build (Go 1.18)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ~1.18
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
key: go118-${{ hashFiles('**/go.sum') }}
|
||||
- name: Run Test
|
||||
run: make ci_build_go118
|
||||
build_go120:
|
||||
name: Debug build (Go 1.20)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
@@ -78,7 +58,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
@@ -208,7 +188,7 @@ jobs:
|
||||
TAGS: with_clash_api,with_quic
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
|
||||
33
.github/workflows/docker.yml
vendored
33
.github/workflows/docker.yml
vendored
@@ -3,14 +3,36 @@ name: Build Docker Images
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
- released
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "The tag version you want to build"
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get commit to build
|
||||
id: ref
|
||||
run: |-
|
||||
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
|
||||
ref="${{ github.ref_name }}"
|
||||
else
|
||||
ref="${{ github.event.inputs.tag }}"
|
||||
fi
|
||||
echo "ref=$ref"
|
||||
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||
if [[ $ref == *"-"* ]]; then
|
||||
latest=latest-beta
|
||||
else
|
||||
latest=latest
|
||||
fi
|
||||
echo "latest=$latest"
|
||||
echo "latest=$latest" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
with:
|
||||
ref: ${{ steps.ref.outputs.ref }}
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Setup QEMU for Docker Buildx
|
||||
@@ -30,10 +52,11 @@ jobs:
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
|
||||
context: .
|
||||
target: dist
|
||||
build-args: |
|
||||
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
||||
tags: |
|
||||
ghcr.io/sagernet/sing-box:latest
|
||||
ghcr.io/sagernet/sing-box:${{ github.ref_name }}
|
||||
ghcr.io/sagernet/sing-box:${{ steps.ref.outputs.latest }}
|
||||
ghcr.io/sagernet/sing-box:${{ steps.ref.outputs.ref }}
|
||||
push: true
|
||||
|
||||
8
.github/workflows/lint.yml
vendored
8
.github/workflows/lint.yml
vendored
@@ -22,19 +22,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.22
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ steps.version.outputs.go_version }}
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout=30m
|
||||
|
||||
39
.github/workflows/linux.yml
vendored
Normal file
39
.github/workflows/linux.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Release to Linux repository
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.22
|
||||
- name: Extract signing key
|
||||
run: |-
|
||||
mkdir -p $HOME/.gnupg
|
||||
cat > $HOME/.gnupg/sagernet.key <<EOF
|
||||
${{ secrets.GPG_KEY }}
|
||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||
EOF
|
||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||
- name: Publish release
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
args: release -f .goreleaser.fury.yaml --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
|
||||
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
||||
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -14,3 +14,5 @@
|
||||
/*.xcframework/
|
||||
.DS_Store
|
||||
/config.d/
|
||||
/venv/
|
||||
|
||||
|
||||
86
.goreleaser.fury.yaml
Normal file
86
.goreleaser.fury.yaml
Normal file
@@ -0,0 +1,86 @@
|
||||
project_name: sing-box
|
||||
builds:
|
||||
- id: main
|
||||
main: ./cmd/sing-box
|
||||
flags:
|
||||
- -v
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
|
||||
tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
- with_dhcp
|
||||
- with_wireguard
|
||||
- with_ech
|
||||
- with_utls
|
||||
- with_reality_server
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
targets:
|
||||
- linux_386
|
||||
- linux_amd64_v1
|
||||
- linux_arm64
|
||||
- linux_arm_7
|
||||
- linux_s390x
|
||||
- linux_riscv64
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
snapshot:
|
||||
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
||||
nfpms:
|
||||
- &template
|
||||
id: package
|
||||
package_name: sing-box
|
||||
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||
builds:
|
||||
- main
|
||||
homepage: https://sing-box.sagernet.org/
|
||||
maintainer: nekohasekai <contact-git@sekai.icu>
|
||||
description: The universal proxy platform.
|
||||
license: GPLv3 or later
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
priority: extra
|
||||
contents:
|
||||
- src: release/config/config.json
|
||||
dst: /etc/sing-box/config.json
|
||||
type: config
|
||||
- src: release/config/sing-box.service
|
||||
dst: /usr/lib/systemd/system/sing-box.service
|
||||
- src: release/config/sing-box@.service
|
||||
dst: /usr/lib/systemd/system/sing-box@.service
|
||||
- src: LICENSE
|
||||
dst: /usr/share/licenses/sing-box/LICENSE
|
||||
deb:
|
||||
signature:
|
||||
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
||||
fields:
|
||||
Bugs: https://github.com/SagerNet/sing-box/issues
|
||||
rpm:
|
||||
signature:
|
||||
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
||||
conflicts:
|
||||
- sing-box-beta
|
||||
- id: package_beta
|
||||
<<: *template
|
||||
package_name: sing-box-beta
|
||||
file_name_template: '{{ .ProjectName }}-beta_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
conflicts:
|
||||
- sing-box
|
||||
release:
|
||||
disable: true
|
||||
furies:
|
||||
- account: sagernet
|
||||
ids:
|
||||
- package
|
||||
disable: "{{ not (not .Prerelease) }}"
|
||||
- account: sagernet
|
||||
ids:
|
||||
- package_beta
|
||||
disable: "{{ not .Prerelease }}"
|
||||
@@ -1,14 +1,11 @@
|
||||
project_name: sing-box
|
||||
builds:
|
||||
- id: main
|
||||
- &template
|
||||
id: main
|
||||
main: ./cmd/sing-box
|
||||
flags:
|
||||
- -v
|
||||
- -trimpath
|
||||
asmflags:
|
||||
- all=-trimpath={{.Env.GOPATH}}
|
||||
gcflags:
|
||||
- all=-trimpath={{.Env.GOPATH}}
|
||||
ldflags:
|
||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
|
||||
tags:
|
||||
@@ -30,25 +27,16 @@ builds:
|
||||
- linux_arm64
|
||||
- linux_arm_7
|
||||
- linux_s390x
|
||||
- linux_riscv64
|
||||
- windows_amd64_v1
|
||||
- windows_amd64_v3
|
||||
- windows_386
|
||||
- windows_arm64
|
||||
- darwin_amd64_v1
|
||||
- darwin_amd64_v3
|
||||
- darwin_arm64
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
- id: legacy
|
||||
main: ./cmd/sing-box
|
||||
flags:
|
||||
- -v
|
||||
- -trimpath
|
||||
asmflags:
|
||||
- all=-trimpath={{.Env.GOPATH}}
|
||||
gcflags:
|
||||
- all=-trimpath={{.Env.GOPATH}}
|
||||
ldflags:
|
||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
|
||||
<<: *template
|
||||
tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
@@ -60,34 +48,14 @@ builds:
|
||||
- with_clash_api
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
- GOROOT=/nix/store/kg6i737jjqs923jcijnm003h68c1dghj-go-1.20.11/share/go
|
||||
gobinary: /nix/store/kg6i737jjqs923jcijnm003h68c1dghj-go-1.20.11/bin/go
|
||||
- GOROOT={{ .Env.GOPATH }}/go1.20.14
|
||||
gobinary: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
|
||||
targets:
|
||||
- windows_amd64_v1
|
||||
- windows_386
|
||||
- darwin_amd64_v1
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
- id: android
|
||||
main: ./cmd/sing-box
|
||||
flags:
|
||||
- -v
|
||||
- -trimpath
|
||||
asmflags:
|
||||
- all=-trimpath={{.Env.GOPATH}}
|
||||
gcflags:
|
||||
- all=-trimpath={{.Env.GOPATH}}
|
||||
ldflags:
|
||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
|
||||
tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
- with_dhcp
|
||||
- with_wireguard
|
||||
- with_ech
|
||||
- with_utls
|
||||
- with_reality_server
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
<<: *template
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
overrides:
|
||||
@@ -95,8 +63,8 @@ builds:
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
env:
|
||||
- CC=armv7a-linux-androideabi19-clang
|
||||
- CXX=armv7a-linux-androideabi19-clang++
|
||||
- CC=armv7a-linux-androideabi21-clang
|
||||
- CXX=armv7a-linux-androideabi21-clang++
|
||||
- goos: android
|
||||
goarch: arm64
|
||||
env:
|
||||
@@ -105,8 +73,8 @@ builds:
|
||||
- goos: android
|
||||
goarch: 386
|
||||
env:
|
||||
- CC=i686-linux-android19-clang
|
||||
- CXX=i686-linux-android19-clang++
|
||||
- CC=i686-linux-android21-clang
|
||||
- CXX=i686-linux-android21-clang++
|
||||
- goos: android
|
||||
goarch: amd64
|
||||
goamd64: v1
|
||||
@@ -118,11 +86,11 @@ builds:
|
||||
- android_arm64
|
||||
- android_386
|
||||
- android_amd64
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
snapshot:
|
||||
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
||||
archives:
|
||||
- id: archive
|
||||
- &template
|
||||
id: archive
|
||||
builds:
|
||||
- main
|
||||
- android
|
||||
@@ -135,21 +103,16 @@ archives:
|
||||
- LICENSE
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||
- id: archive-legacy
|
||||
<<: *template
|
||||
builds:
|
||||
- legacy
|
||||
format: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
wrap_in_directory: true
|
||||
files:
|
||||
- LICENSE
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
|
||||
nfpms:
|
||||
- id: package
|
||||
package_name: sing-box
|
||||
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||
vendor: sagernet
|
||||
builds:
|
||||
- main
|
||||
homepage: https://sing-box.sagernet.org/
|
||||
maintainer: nekohasekai <contact-git@sekai.icu>
|
||||
description: The universal proxy platform.
|
||||
@@ -164,11 +127,27 @@ nfpms:
|
||||
dst: /etc/sing-box/config.json
|
||||
type: config
|
||||
- src: release/config/sing-box.service
|
||||
dst: /etc/systemd/system/sing-box.service
|
||||
dst: /usr/lib/systemd/system/sing-box.service
|
||||
- src: release/config/sing-box@.service
|
||||
dst: /etc/systemd/system/sing-box@.service
|
||||
dst: /usr/lib/systemd/system/sing-box@.service
|
||||
- src: LICENSE
|
||||
dst: /usr/share/licenses/sing-box/LICENSE
|
||||
deb:
|
||||
signature:
|
||||
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
||||
fields:
|
||||
Bugs: https://github.com/SagerNet/sing-box/issues
|
||||
rpm:
|
||||
signature:
|
||||
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
||||
overrides:
|
||||
deb:
|
||||
conflicts:
|
||||
- sing-box-beta
|
||||
rpm:
|
||||
conflicts:
|
||||
- sing-box-beta
|
||||
|
||||
source:
|
||||
enabled: false
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
||||
@@ -182,6 +161,10 @@ release:
|
||||
github:
|
||||
owner: SagerNet
|
||||
name: sing-box
|
||||
name_template: '{{ if .IsSnapshot }}{{ nightly }}{{ else }}{{ .Version }}{{ end }}'
|
||||
draft: true
|
||||
mode: replace
|
||||
prerelease: auto
|
||||
mode: replace
|
||||
ids:
|
||||
- archive
|
||||
- package
|
||||
skip_upload: true
|
||||
40
Makefile
40
Makefile
@@ -1,7 +1,6 @@
|
||||
NAME = sing-box
|
||||
COMMIT = $(shell git rev-parse --short HEAD)
|
||||
TAGS_GO118 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api
|
||||
TAGS_GO120 = with_quic,with_utls
|
||||
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls
|
||||
TAGS_GO121 = with_ech
|
||||
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121)
|
||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
|
||||
@@ -15,18 +14,14 @@ MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
|
||||
MAIN = ./cmd/sing-box
|
||||
PREFIX ?= $(shell go env GOPATH)
|
||||
|
||||
.PHONY: test release docs
|
||||
.PHONY: test release docs build
|
||||
|
||||
build:
|
||||
go build $(MAIN_PARAMS) $(MAIN)
|
||||
|
||||
ci_build_go118:
|
||||
go build $(PARAMS) $(MAIN)
|
||||
go build $(PARAMS) -tags "$(TAGS_GO118)" $(MAIN)
|
||||
|
||||
ci_build_go120:
|
||||
go build $(PARAMS) $(MAIN)
|
||||
go build $(PARAMS) -tags "$(TAGS_GO118),$(TAGS_GO120)" $(MAIN)
|
||||
go build $(PARAMS) -tags "$(TAGS_GO120)" $(MAIN)
|
||||
|
||||
ci_build:
|
||||
go build $(PARAMS) $(MAIN)
|
||||
@@ -64,21 +59,30 @@ proto_install:
|
||||
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||
|
||||
release:
|
||||
go run ./cmd/internal/build goreleaser release --clean --skip-publish || exit 1
|
||||
go run ./cmd/internal/build goreleaser release --clean --skip publish
|
||||
mkdir dist/release
|
||||
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/*.pkg.tar.zst dist/release
|
||||
mv dist/*.tar.gz \
|
||||
dist/*.zip \
|
||||
dist/*.deb \
|
||||
dist/*.rpm \
|
||||
dist/*_amd64.pkg.tar.zst \
|
||||
dist/*_amd64v3.pkg.tar.zst \
|
||||
dist/*_arm64.pkg.tar.zst \
|
||||
dist/release
|
||||
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release
|
||||
rm -r dist/release
|
||||
|
||||
release_repo:
|
||||
go run ./cmd/internal/build goreleaser release -f .goreleaser.fury.yaml --clean
|
||||
|
||||
release_install:
|
||||
go install -v github.com/goreleaser/goreleaser@latest
|
||||
go install -v github.com/tcnksm/ghr@latest
|
||||
|
||||
update_android_version:
|
||||
go run ./cmd/internal/update_android_version
|
||||
|
||||
build_android:
|
||||
cd ../sing-box-for-android && ./gradlew :app:assemblePlayRelease && ./gradlew :app:assembleOtherRelease && ./gradlew --stop
|
||||
cd ../sing-box-for-android && ./gradlew :app:clean :app:assemblePlayRelease :app:assembleOtherRelease && ./gradlew --stop
|
||||
|
||||
upload_android:
|
||||
mkdir -p dist/release_android
|
||||
@@ -184,17 +188,19 @@ lib:
|
||||
go run ./cmd/internal/build_libbox -target ios
|
||||
|
||||
lib_install:
|
||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.1
|
||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.1
|
||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.3
|
||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.3
|
||||
|
||||
docs:
|
||||
mkdocs serve
|
||||
venv/bin/mkdocs serve
|
||||
|
||||
publish_docs:
|
||||
mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
|
||||
venv/bin/mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
|
||||
|
||||
docs_install:
|
||||
pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
|
||||
python -m venv venv
|
||||
source ./venv/bin/active && pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
|
||||
|
||||
clean:
|
||||
rm -rf bin dist sing-box
|
||||
rm -f $(shell go env GOPATH)/sing-box
|
||||
|
||||
@@ -66,6 +66,7 @@ func (c *InboundContext) ResetRuleCache() {
|
||||
c.SourcePortMatch = false
|
||||
c.DestinationAddressMatch = false
|
||||
c.DestinationPortMatch = false
|
||||
c.DidMatch = false
|
||||
}
|
||||
|
||||
type inboundContextKey struct{}
|
||||
@@ -98,3 +99,12 @@ func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
||||
}
|
||||
return WithContext(ctx, &newMetadata), &newMetadata
|
||||
}
|
||||
|
||||
func OverrideContext(ctx context.Context) context.Context {
|
||||
if metadata := ContextFrom(ctx); metadata != nil {
|
||||
var newMetadata InboundContext
|
||||
newMetadata = *metadata
|
||||
return WithContext(ctx, &newMetadata)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -10,15 +10,18 @@ import (
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
mdns "github.com/miekg/dns"
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
type Router interface {
|
||||
Service
|
||||
PreStarter
|
||||
PostStarter
|
||||
Cleanup() error
|
||||
|
||||
Outbounds() []Outbound
|
||||
Outbound(tag string) (Outbound, bool)
|
||||
@@ -45,7 +48,9 @@ type Router interface {
|
||||
DefaultInterface() string
|
||||
AutoDetectInterface() bool
|
||||
AutoDetectInterfaceFunc() control.Func
|
||||
DefaultMark() int
|
||||
DefaultMark() uint32
|
||||
RegisterAutoRedirectOutputMark(mark uint32) error
|
||||
AutoRedirectOutputMark() uint32
|
||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||
PackageManager() tun.PackageManager
|
||||
@@ -71,6 +76,7 @@ func RouterFromContext(ctx context.Context) Router {
|
||||
|
||||
type HeadlessRule interface {
|
||||
Match(metadata *InboundContext) bool
|
||||
String() string
|
||||
}
|
||||
|
||||
type Rule interface {
|
||||
@@ -79,26 +85,34 @@ type Rule interface {
|
||||
Type() string
|
||||
UpdateGeosite() error
|
||||
Outbound() string
|
||||
String() string
|
||||
}
|
||||
|
||||
type DNSRule interface {
|
||||
Rule
|
||||
DisableCache() bool
|
||||
RewriteTTL() *uint32
|
||||
ClientSubnet() *netip.Addr
|
||||
ClientSubnet() *netip.Prefix
|
||||
WithAddressLimit() bool
|
||||
MatchAddressLimit(metadata *InboundContext) bool
|
||||
}
|
||||
|
||||
type RuleSet interface {
|
||||
Name() string
|
||||
StartContext(ctx context.Context, startContext RuleSetStartContext) error
|
||||
PostStart() error
|
||||
Metadata() RuleSetMetadata
|
||||
ExtractIPSet() []*netipx.IPSet
|
||||
IncRef()
|
||||
DecRef()
|
||||
Cleanup()
|
||||
RegisterCallback(callback RuleSetUpdateCallback) *list.Element[RuleSetUpdateCallback]
|
||||
UnregisterCallback(element *list.Element[RuleSetUpdateCallback])
|
||||
Close() error
|
||||
HeadlessRule
|
||||
}
|
||||
|
||||
type RuleSetUpdateCallback func(it RuleSet)
|
||||
|
||||
type RuleSetMetadata struct {
|
||||
ContainsProcessRule bool
|
||||
ContainsWIFIRule bool
|
||||
|
||||
33
box.go
33
box.go
@@ -111,6 +111,7 @@ func New(options Options) (*Box, error) {
|
||||
ctx,
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||
tag,
|
||||
inboundOptions,
|
||||
options.PlatformInterface,
|
||||
)
|
||||
@@ -235,7 +236,7 @@ func (s *Box) Start() error {
|
||||
}
|
||||
|
||||
func (s *Box) preStart() error {
|
||||
monitor := taskmonitor.New(s.logger, C.DefaultStartTimeout)
|
||||
monitor := taskmonitor.New(s.logger, C.StartTimeout)
|
||||
monitor.Start("start logger")
|
||||
err := s.logFactory.Start()
|
||||
monitor.Finish()
|
||||
@@ -302,7 +303,11 @@ func (s *Box) start() error {
|
||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
||||
}
|
||||
}
|
||||
return s.postStart()
|
||||
err = s.postStart()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.router.Cleanup()
|
||||
}
|
||||
|
||||
func (s *Box) postStart() error {
|
||||
@@ -312,16 +317,28 @@ func (s *Box) postStart() error {
|
||||
return E.Cause(err, "start ", serviceName)
|
||||
}
|
||||
}
|
||||
for _, outbound := range s.outbounds {
|
||||
if lateOutbound, isLateOutbound := outbound.(adapter.PostStarter); isLateOutbound {
|
||||
// TODO: reorganize ALL start order
|
||||
for _, out := range s.outbounds {
|
||||
if lateOutbound, isLateOutbound := out.(adapter.PostStarter); isLateOutbound {
|
||||
err := lateOutbound.PostStart()
|
||||
if err != nil {
|
||||
return E.Cause(err, "post-start outbound/", outbound.Tag())
|
||||
return E.Cause(err, "post-start outbound/", out.Tag())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s.router.PostStart()
|
||||
err := s.router.PostStart()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, in := range s.inbounds {
|
||||
if lateInbound, isLateInbound := in.(adapter.PostStarter); isLateInbound {
|
||||
err = lateInbound.PostStart()
|
||||
if err != nil {
|
||||
return E.Cause(err, "post-start inbound/", in.Tag())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Box) Close() error {
|
||||
@@ -331,7 +348,7 @@ func (s *Box) Close() error {
|
||||
default:
|
||||
close(s.done)
|
||||
}
|
||||
monitor := taskmonitor.New(s.logger, C.DefaultStopTimeout)
|
||||
monitor := taskmonitor.New(s.logger, C.StopTimeout)
|
||||
var errors error
|
||||
for serviceName, service := range s.postServices {
|
||||
monitor.Start("close ", serviceName)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func (s *Box) startOutbounds() error {
|
||||
monitor := taskmonitor.New(s.logger, C.DefaultStartTimeout)
|
||||
monitor := taskmonitor.New(s.logger, C.StartTimeout)
|
||||
outboundTags := make(map[adapter.Outbound]string)
|
||||
outbounds := make(map[string]adapter.Outbound)
|
||||
for i, outboundToStart := range s.outbounds {
|
||||
|
||||
Submodule clients/android updated: 4dfff0ace9...00e3a80875
Submodule clients/apple updated: 4008b59681...0cbe335cbb
@@ -46,13 +46,13 @@ var (
|
||||
|
||||
func init() {
|
||||
sharedFlags = append(sharedFlags, "-trimpath")
|
||||
sharedFlags = append(sharedFlags, "-ldflags")
|
||||
sharedFlags = append(sharedFlags, "-buildvcs=false")
|
||||
currentTag, err := build_shared.ReadTag()
|
||||
if err != nil {
|
||||
currentTag = "unknown"
|
||||
}
|
||||
sharedFlags = append(sharedFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
||||
debugFlags = append(debugFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||
|
||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_ech", "with_utls", "with_clash_api")
|
||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
||||
|
||||
@@ -30,7 +30,7 @@ func FindSDK() {
|
||||
}
|
||||
for _, path := range searchPath {
|
||||
path = os.ExpandEnv(path)
|
||||
if rw.FileExists(path + "/licenses/android-sdk-license") {
|
||||
if rw.FileExists(filepath.Join(path, "licenses", "android-sdk-license")) {
|
||||
androidSDKPath = path
|
||||
break
|
||||
}
|
||||
@@ -58,11 +58,13 @@ func FindSDK() {
|
||||
}
|
||||
|
||||
func findNDK() bool {
|
||||
if rw.FileExists(androidSDKPath + "/ndk/25.1.8937393") {
|
||||
androidNDKPath = androidSDKPath + "/ndk/25.1.8937393"
|
||||
const fixedVersion = "26.2.11394342"
|
||||
const versionFile = "source.properties"
|
||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.FileExists(filepath.Join(fixedPath, versionFile)) {
|
||||
androidNDKPath = fixedPath
|
||||
return true
|
||||
}
|
||||
ndkVersions, err := os.ReadDir(androidSDKPath + "/ndk")
|
||||
ndkVersions, err := os.ReadDir(filepath.Join(androidSDKPath, "ndk"))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -83,8 +85,10 @@ func findNDK() bool {
|
||||
return true
|
||||
})
|
||||
for _, versionName := range versionNames {
|
||||
if rw.FileExists(androidSDKPath + "/ndk/" + versionName) {
|
||||
androidNDKPath = androidSDKPath + "/ndk/" + versionName
|
||||
currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName)
|
||||
if rw.FileExists(filepath.Join(androidSDKPath, versionFile)) {
|
||||
androidNDKPath = currentNDKPath
|
||||
log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -95,13 +99,12 @@ var GoBinPath string
|
||||
|
||||
func FindMobile() {
|
||||
goBin := filepath.Join(build.Default.GOPATH, "bin")
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
if !rw.FileExists(goBin + "/" + "gobind.exe") {
|
||||
log.Fatal("missing gomobile.exe installation")
|
||||
if !rw.FileExists(filepath.Join(goBin, "gobind.exe")) {
|
||||
log.Fatal("missing gomobile installation")
|
||||
}
|
||||
} else {
|
||||
if !rw.FileExists(goBin + "/" + "gobind") {
|
||||
if !rw.FileExists(filepath.Join(goBin, "gobind")) {
|
||||
log.Fatal("missing gomobile installation")
|
||||
}
|
||||
}
|
||||
|
||||
86
cmd/sing-box/cmd_rule_set_match.go
Normal file
86
cmd/sing-box/cmd_rule_set_match.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/srs"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/route"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var flagRuleSetMatchFormat string
|
||||
|
||||
var commandRuleSetMatch = &cobra.Command{
|
||||
Use: "match <rule-set path> <domain>",
|
||||
Short: "Check if a domain matches the rule set",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := ruleSetMatch(args[0], args[1])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandRuleSetMatch.Flags().StringVarP(&flagRuleSetMatchFormat, "format", "f", "source", "rule-set format")
|
||||
commandRuleSet.AddCommand(commandRuleSetMatch)
|
||||
}
|
||||
|
||||
func ruleSetMatch(sourcePath string, domain string) error {
|
||||
var (
|
||||
reader io.Reader
|
||||
err error
|
||||
)
|
||||
if sourcePath == "stdin" {
|
||||
reader = os.Stdin
|
||||
} else {
|
||||
reader, err = os.Open(sourcePath)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read rule-set")
|
||||
}
|
||||
}
|
||||
content, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read rule-set")
|
||||
}
|
||||
var plainRuleSet option.PlainRuleSet
|
||||
switch flagRuleSetMatchFormat {
|
||||
case C.RuleSetFormatSource:
|
||||
var compat option.PlainRuleSetCompat
|
||||
compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
plainRuleSet = compat.Upgrade()
|
||||
case C.RuleSetFormatBinary:
|
||||
plainRuleSet, err = srs.Read(bytes.NewReader(content), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return E.New("unknown rule set format: ", flagRuleSetMatchFormat)
|
||||
}
|
||||
for i, ruleOptions := range plainRuleSet.Rules {
|
||||
var currentRule adapter.HeadlessRule
|
||||
currentRule, err = route.NewHeadlessRule(nil, ruleOptions)
|
||||
if err != nil {
|
||||
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
||||
}
|
||||
if currentRule.Match(&adapter.InboundContext{
|
||||
Domain: domain,
|
||||
}) {
|
||||
println("match rules.[", i, "]: "+currentRule.String())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -199,7 +199,7 @@ func run() error {
|
||||
}
|
||||
|
||||
func closeMonitor(ctx context.Context) {
|
||||
time.Sleep(C.DefaultStopFatalTimeout)
|
||||
time.Sleep(C.FatalStopTimeout)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
@@ -9,8 +9,10 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -32,7 +34,10 @@ func init() {
|
||||
commandTools.AddCommand(commandFetch)
|
||||
}
|
||||
|
||||
var httpClient *http.Client
|
||||
var (
|
||||
httpClient *http.Client
|
||||
http3Client *http.Client
|
||||
)
|
||||
|
||||
func fetch(args []string) error {
|
||||
instance, err := createPreStartedClient()
|
||||
@@ -53,8 +58,16 @@ func fetch(args []string) error {
|
||||
},
|
||||
}
|
||||
defer httpClient.CloseIdleConnections()
|
||||
if C.WithQUIC {
|
||||
err = initializeHTTP3Client(instance)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer http3Client.CloseIdleConnections()
|
||||
}
|
||||
for _, urlString := range args {
|
||||
parsedURL, err := url.Parse(urlString)
|
||||
var parsedURL *url.URL
|
||||
parsedURL, err = url.Parse(urlString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -63,16 +76,27 @@ func fetch(args []string) error {
|
||||
parsedURL.Scheme = "http"
|
||||
fallthrough
|
||||
case "http", "https":
|
||||
err = fetchHTTP(parsedURL)
|
||||
err = fetchHTTP(httpClient, parsedURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "http3":
|
||||
if !C.WithQUIC {
|
||||
return C.ErrQUICNotIncluded
|
||||
}
|
||||
parsedURL.Scheme = "https"
|
||||
err = fetchHTTP(http3Client, parsedURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return E.New("unsupported scheme: ", parsedURL.Scheme)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchHTTP(parsedURL *url.URL) error {
|
||||
func fetchHTTP(httpClient *http.Client, parsedURL *url.URL) error {
|
||||
request, err := http.NewRequest("GET", parsedURL.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
36
cmd/sing-box/cmd_tools_fetch_http3.go
Normal file
36
cmd/sing-box/cmd_tools_fetch_http3.go
Normal file
@@ -0,0 +1,36 @@
|
||||
//go:build with_quic
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
|
||||
"github.com/sagernet/quic-go"
|
||||
"github.com/sagernet/quic-go/http3"
|
||||
box "github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
func initializeHTTP3Client(instance *box.Box) error {
|
||||
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
http3Client = &http.Client{
|
||||
Transport: &http3.RoundTripper{
|
||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
destination := M.ParseSocksaddr(addr)
|
||||
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
|
||||
if dErr != nil {
|
||||
return nil, dErr
|
||||
}
|
||||
return quic.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), tlsCfg, cfg)
|
||||
},
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
18
cmd/sing-box/cmd_tools_fetch_http3_stub.go
Normal file
18
cmd/sing-box/cmd_tools_fetch_http3_stub.go
Normal file
@@ -0,0 +1,18 @@
|
||||
//go:build !with_quic
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
box "github.com/sagernet/sing-box"
|
||||
)
|
||||
|
||||
func initializeHTTP3Client(instance *box.Box) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func fetchHTTP3(parsedURL *url.URL) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
@@ -32,24 +32,44 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
||||
var dialer net.Dialer
|
||||
var listener net.ListenConfig
|
||||
if options.BindInterface != "" {
|
||||
bindFunc := control.BindToInterface(router.InterfaceFinder(), options.BindInterface, -1)
|
||||
var interfaceFinder control.InterfaceFinder
|
||||
if router != nil {
|
||||
interfaceFinder = router.InterfaceFinder()
|
||||
} else {
|
||||
interfaceFinder = control.NewDefaultInterfaceFinder()
|
||||
}
|
||||
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if router.AutoDetectInterface() {
|
||||
} else if router != nil && router.AutoDetectInterface() {
|
||||
bindFunc := router.AutoDetectInterfaceFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if router.DefaultInterface() != "" {
|
||||
} else if router != nil && router.DefaultInterface() != "" {
|
||||
bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1)
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
}
|
||||
if options.RoutingMark != 0 {
|
||||
var autoRedirectOutputMark uint32
|
||||
if router != nil {
|
||||
autoRedirectOutputMark = router.AutoRedirectOutputMark()
|
||||
}
|
||||
if autoRedirectOutputMark > 0 {
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||
}
|
||||
if options.RoutingMark > 0 {
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
|
||||
} else if router.DefaultMark() != 0 {
|
||||
if autoRedirectOutputMark > 0 {
|
||||
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `routing_mark`")
|
||||
}
|
||||
} else if router != nil && router.DefaultMark() > 0 {
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(router.DefaultMark()))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
|
||||
if autoRedirectOutputMark > 0 {
|
||||
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `default_mark`")
|
||||
}
|
||||
}
|
||||
if options.ReuseAddr {
|
||||
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
||||
@@ -63,6 +83,9 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
||||
} else {
|
||||
dialer.Timeout = C.TCPTimeout
|
||||
}
|
||||
// TODO: Add an option to customize the keep alive period
|
||||
dialer.KeepAlive = C.TCPKeepAliveInitial
|
||||
dialer.Control = control.Append(dialer.Control, control.SetKeepAlivePeriod(C.TCPKeepAliveInitial, C.TCPKeepAliveInterval))
|
||||
var udpFragment bool
|
||||
if options.UDPFragment != nil {
|
||||
udpFragment = *options.UDPFragment
|
||||
|
||||
@@ -13,6 +13,9 @@ func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error)
|
||||
if options.IsWireGuardListener {
|
||||
return NewDefault(router, options)
|
||||
}
|
||||
if router == nil {
|
||||
return NewDefault(nil, options)
|
||||
}
|
||||
var (
|
||||
dialer N.Dialer
|
||||
err error
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package mux
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-mux"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
@@ -30,7 +35,7 @@ func NewClientWithOptions(dialer N.Dialer, logger logger.Logger, options option.
|
||||
}
|
||||
}
|
||||
return mux.NewClient(mux.Options{
|
||||
Dialer: dialer,
|
||||
Dialer: &clientDialer{dialer},
|
||||
Logger: logger,
|
||||
Protocol: options.Protocol,
|
||||
MaxConnections: options.MaxConnections,
|
||||
@@ -40,3 +45,15 @@ func NewClientWithOptions(dialer N.Dialer, logger logger.Logger, options option.
|
||||
Brutal: brutalOptions,
|
||||
})
|
||||
}
|
||||
|
||||
type clientDialer struct {
|
||||
N.Dialer
|
||||
}
|
||||
|
||||
func (d *clientDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
return d.Dialer.DialContext(adapter.OverrideContext(ctx), network, destination)
|
||||
}
|
||||
|
||||
func (d *clientDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
return d.Dialer.ListenPacket(adapter.OverrideContext(ctx), destination)
|
||||
}
|
||||
|
||||
@@ -16,30 +16,40 @@ import (
|
||||
)
|
||||
|
||||
type LinuxSystemProxy struct {
|
||||
hasGSettings bool
|
||||
hasKWriteConfig5 bool
|
||||
sudoUser string
|
||||
serverAddr M.Socksaddr
|
||||
supportSOCKS bool
|
||||
isEnabled bool
|
||||
hasGSettings bool
|
||||
kWriteConfigCmd string
|
||||
sudoUser string
|
||||
serverAddr M.Socksaddr
|
||||
supportSOCKS bool
|
||||
isEnabled bool
|
||||
}
|
||||
|
||||
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*LinuxSystemProxy, error) {
|
||||
hasGSettings := common.Error(exec.LookPath("gsettings")) == nil
|
||||
hasKWriteConfig5 := common.Error(exec.LookPath("kwriteconfig5")) == nil
|
||||
kWriteConfigCmds := []string{
|
||||
"kwriteconfig5",
|
||||
"kwriteconfig6",
|
||||
}
|
||||
var kWriteConfigCmd string
|
||||
for _, cmd := range kWriteConfigCmds {
|
||||
if common.Error(exec.LookPath(cmd)) == nil {
|
||||
kWriteConfigCmd = cmd
|
||||
break
|
||||
}
|
||||
}
|
||||
var sudoUser string
|
||||
if os.Getuid() == 0 {
|
||||
sudoUser = os.Getenv("SUDO_USER")
|
||||
}
|
||||
if !hasGSettings && !hasKWriteConfig5 {
|
||||
if !hasGSettings && kWriteConfigCmd == "" {
|
||||
return nil, E.New("unsupported desktop environment")
|
||||
}
|
||||
return &LinuxSystemProxy{
|
||||
hasGSettings: hasGSettings,
|
||||
hasKWriteConfig5: hasKWriteConfig5,
|
||||
sudoUser: sudoUser,
|
||||
serverAddr: serverAddr,
|
||||
supportSOCKS: supportSOCKS,
|
||||
hasGSettings: hasGSettings,
|
||||
kWriteConfigCmd: kWriteConfigCmd,
|
||||
sudoUser: sudoUser,
|
||||
serverAddr: serverAddr,
|
||||
supportSOCKS: supportSOCKS,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -70,8 +80,8 @@ func (p *LinuxSystemProxy) Enable() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if p.hasKWriteConfig5 {
|
||||
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "1")
|
||||
if p.kWriteConfigCmd != "" {
|
||||
err := p.runAsUser(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "1")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -83,7 +93,7 @@ func (p *LinuxSystemProxy) Enable() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "Authmode", "0")
|
||||
err = p.runAsUser(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "Authmode", "0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -103,8 +113,8 @@ func (p *LinuxSystemProxy) Disable() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if p.hasKWriteConfig5 {
|
||||
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "0")
|
||||
if p.kWriteConfigCmd != "" {
|
||||
err := p.runAsUser(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -150,7 +160,7 @@ func (p *LinuxSystemProxy) setKDEProxy(proxyTypes ...string) error {
|
||||
proxyUrl = "http://" + p.serverAddr.String()
|
||||
}
|
||||
err := p.runAsUser(
|
||||
"kwriteconfig5",
|
||||
p.kWriteConfigCmd,
|
||||
"--file",
|
||||
"kioslaverc",
|
||||
"--group",
|
||||
|
||||
113
common/sniff/bittorrent.go
Normal file
113
common/sniff/bittorrent.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package sniff
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
)
|
||||
|
||||
const (
|
||||
trackerConnectFlag = iota
|
||||
trackerAnnounceFlag
|
||||
trackerScrapeFlag
|
||||
|
||||
trackerProtocolID = 0x41727101980
|
||||
|
||||
trackerConnectMinSize = 16
|
||||
trackerAnnounceMinSize = 20
|
||||
trackerScrapeMinSize = 8
|
||||
)
|
||||
|
||||
// BitTorrent detects if the stream is a BitTorrent connection.
|
||||
// For the BitTorrent protocol specification, see https://www.bittorrent.org/beps/bep_0003.html
|
||||
func BitTorrent(_ context.Context, reader io.Reader) (*adapter.InboundContext, error) {
|
||||
var first byte
|
||||
err := binary.Read(reader, binary.BigEndian, &first)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if first != 19 {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
var protocol [19]byte
|
||||
_, err = reader.Read(protocol[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if string(protocol[:]) != "BitTorrent protocol" {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
return &adapter.InboundContext{
|
||||
Protocol: C.ProtocolBitTorrent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UTP detects if the packet is a uTP connection packet.
|
||||
// For the uTP protocol specification, see
|
||||
// 1. https://www.bittorrent.org/beps/bep_0029.html
|
||||
// 2. https://github.com/bittorrent/libutp/blob/2b364cbb0650bdab64a5de2abb4518f9f228ec44/utp_internal.cpp#L112
|
||||
func UTP(_ context.Context, packet []byte) (*adapter.InboundContext, error) {
|
||||
// A valid uTP packet must be at least 20 bytes long.
|
||||
if len(packet) < 20 {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
version := packet[0] & 0x0F
|
||||
ty := packet[0] >> 4
|
||||
if version != 1 || ty > 4 {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
// Validate the extensions
|
||||
extension := packet[1]
|
||||
reader := bytes.NewReader(packet[20:])
|
||||
for extension != 0 {
|
||||
err := binary.Read(reader, binary.BigEndian, &extension)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var length byte
|
||||
err = binary.Read(reader, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = reader.Seek(int64(length), io.SeekCurrent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &adapter.InboundContext{
|
||||
Protocol: C.ProtocolBitTorrent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UDPTracker detects if the packet is a UDP Tracker Protocol packet.
|
||||
// For the UDP Tracker Protocol specification, see https://www.bittorrent.org/beps/bep_0015.html
|
||||
func UDPTracker(_ context.Context, packet []byte) (*adapter.InboundContext, error) {
|
||||
switch {
|
||||
case len(packet) >= trackerConnectMinSize &&
|
||||
binary.BigEndian.Uint64(packet[:8]) == trackerProtocolID &&
|
||||
binary.BigEndian.Uint32(packet[8:12]) == trackerConnectFlag:
|
||||
fallthrough
|
||||
case len(packet) >= trackerAnnounceMinSize &&
|
||||
binary.BigEndian.Uint32(packet[8:12]) == trackerAnnounceFlag:
|
||||
fallthrough
|
||||
case len(packet) >= trackerScrapeMinSize &&
|
||||
binary.BigEndian.Uint32(packet[8:12]) == trackerScrapeFlag:
|
||||
return &adapter.InboundContext{
|
||||
Protocol: C.ProtocolBitTorrent,
|
||||
}, nil
|
||||
default:
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
}
|
||||
81
common/sniff/bittorrent_test.go
Normal file
81
common/sniff/bittorrent_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package sniff_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/sagernet/sing-box/common/sniff"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSniffBittorrent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
packets := []string{
|
||||
"13426974546f7272656e742070726f746f636f6c0000000000100000e21ea9569b69bab33c97851d0298bdfa89bc90922d5554313631302dea812fcd6a3563e3be40c1d1",
|
||||
"13426974546f7272656e742070726f746f636f6c00000000001000052aa4f5a7e209e54b32803d43670971c4c8caaa052d5452333030302d653369733079647675763638",
|
||||
"13426974546f7272656e742070726f746f636f6c00000000001000052aa4f5a7e209e54b32803d43670971c4c8caaa052d5452343035302d6f7a316c6e79377931716130",
|
||||
}
|
||||
|
||||
for _, pkt := range packets {
|
||||
pkt, err := hex.DecodeString(pkt)
|
||||
require.NoError(t, err)
|
||||
metadata, err := sniff.BitTorrent(context.TODO(), bytes.NewReader(pkt))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSniffUTP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
packets := []string{
|
||||
"010041a282d7ee7b583afb160004000006d8318da776968f92d666f7963f32dae23ba0d2c810d8b8209cc4939f54fde9eeaa521c2c20c9ba7f43f4fb0375f28de06643b5e3ca4685ab7ac76adca99783be72ef05ed59ef4234f5712b75b4c7c0d7bee8fe2ca20ad626ba5bb0ffcc16bf06790896f888048cf72716419a07db1a3dca4550fbcea75b53e97235168a221cf3e553dfbb723961bd719fab038d86e0ecb74747f5a2cd669de1c4b9ad375f3a492d09d98cdfad745435625401315bbba98d35d32086299801377b93495a63a9efddb8d05f5b37a5c5b1c0a25e917f12007bb5e05013ada8aff544fab8cadf61d80ddb0b60f12741e44515a109d144fd53ef845acb4b5ccf0d6fc302d7003d76df3fc3423bb0237301c9e88f900c2d392a8e0fdb36d143cf7527a93fd0a2638b746e72f6699fffcd4fd15348fce780d4caa04382fd9faf1ca0ae377ca805da7536662b84f5ee18dd3ae38fcb095a7543e55f9069ae92c8cf54ae44e97b558d35e2545c66601ed2149cbc32bd6df199a2be7cf0da8b2ff137e0d23e776bc87248425013876d3a3cc31a83b424b752bd0346437f24b532978005d8f5b1b0be1a37a2489c32a18a9ad3118e3f9d30eb299bffae18e1f0677c2a5c185e62519093fe6bc2b7339299ea50a587989f726ca6443a75dd5bb936f6367c6355d80fae53ff529d740b2e5576e3eefdf1fdbfc69c3c8d8ac750512635de63e054bee1d3b689bc1b2bc3d2601e42a00b5c89066d173d4ae7ffedfd2274e5cf6d868fbe640aedb69b8246142f00b32d459974287537ddd5373460dcbc92f5cfdd7a3ed6020822ae922d947893752ca1983d0d32977374c384ac8f5ab566859019b7351526b9f13e932037a55bb052d9deb3b3c23317e0784fdc51a64f2159bfea3b069cf5caf02ee2c3c1a6b6b427bb16165713e8802d95b5c8ed77953690e994bd38c9ae113fedaf6ee7fc2b96c032ceafc2a530ad0422e84546b9c6ad8ef6ea02fa508abddd1805c38a7b42e9b7c971b1b636865ebec06ed754bb404cd6b4e6cc8cb77bd4a0c43410d5cd5ef8fe853a66d49b3b9e06cb141236cdbfdd5761601dc54d1250b86c660e0f898fe62526fdd9acf0eab60a3bbbb2151970461f28f10b31689594bea646c4b03ee197d63bdef4e5a7c22716b3bb9494a83b78ecd81b338b80ac6c09c43485b1b09ba41c74343832c78f0520c1d659ac9eb1502094141e82fb9e5e620970ebc0655514c43c294a7714cbf9a499d277daf089f556398a01589a77494bec8bfb60a108f3813b55368672b88c1af40f6b3c8b513f7c70c3e0efce85228b8b9ec67ba0393f9f7305024d8e2da6a26cf85613d14f249170ce1000089df4c9c260df7f8292aa2ecb5d5bac97656d59aa248caedea2d198e51ce87baece338716d114b458de02d65c9ff808ca5b5b73723b4d1e962d9ac2d98176544dc9984cf8554d07820ef3dd0861cfe57b478328046380de589adad94ee44743ffac73bb7361feca5d56f07cf8ce75080e261282ae30350d7882679b15cab9e7e53ddf93310b33f7390ae5d318bb53f387e6af5d0ef4f947fc9cb8e7e38b52c7f8d772ece6156b38d88796ea19df02c53723b44df7c76315a0de9462f27287e682d2b4cda1a68fe00d7e48c51ee981be44e1ca940fb5190c12655edb4a83c3a4f33e48a015692df4f0b3d61656e362aca657b5ae8c12db5a0db3db1e45135ee918b66918f40e53c4f83e9da0cddfe63f736ae751ab3837a30ae3220d8e8e311487093a7b90c7e7e40dd54ca750e19452f9193aa892aa6a6229ab493dadae988b1724f7898ee69c36d3eb7364c4adbeca811cfe2065873e78c2b6dfdf1595f7a7831c07e03cda82e4f86f76438dfb2b07c13638ce7b509cfa71b88b5102b39a203b423202088e1c2103319cb32c13c1e546ff8612fa194c95a7808ab767c265a1bd5fa0efed5c8ec1701876a00ec8",
|
||||
"01001ecb68176f215d04326300100000dbcf30292d14b54e9ee2d115ee5b8ebc7fad3e882d4fcdd0c14c6b917c11cb4c6a9f410b52a33ae97c2ac77c7a2b122b8955e09af3c5c595f1b2e79ca57cfe44c44e069610773b9bc9ba223d7f6b383e3adddd03fb88a8476028e30979c2ef321ffc97c5c132bcf9ac5b410bbb5ec6cefca3c7209202a14c5ae922b6b157b0a80249d13ffe5b996af0bc8e54ba576d148372494303e7ead0602b05b9c8fc97d48508a028a04d63a1fd28b0edfcd5c51715f63188b53eefede98a76912dca98518551a8856567307a56a702cbfcc115ea0c755b418bc2c7b57721239b82f09fb24328a4b0ce0f109bcb2a64e04b8aadb1f8487585425acdf8fc4ec8ea93cfcec5ac098bb29d42ddef6e46b03f34a5de28316726699b7cb5195c33e5c48abe87d591d63f9991c84c30819d186d6e0e95fd83c8dff07aa669c4430989bcaccfeacb9bcadbdb4d8f1964dbeb9687745656edd30b21c66cc0a1d742a78717d134a19a7f02d285a4973b1a198c00cfdff4676608dc4f3e817e3463c3b4e2c80d3e8d4fbac541a58a2fb7ad6939f607f8144eff6c8b0adc28ee5609ea158987519892fb",
|
||||
"21001ecb6817f2805d044fd700100000dbd03029",
|
||||
"410277ef0b1fb1f60000000000040000c233000000080000000000000000",
|
||||
}
|
||||
|
||||
for _, pkt := range packets {
|
||||
pkt, err := hex.DecodeString(pkt)
|
||||
require.NoError(t, err)
|
||||
|
||||
metadata, err := sniff.UTP(context.TODO(), pkt)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSniffUDPTracker(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
connectPackets := []string{
|
||||
// connect packets
|
||||
"00000417271019800000000078e90560",
|
||||
"00000417271019800000000022c5d64d",
|
||||
"000004172710198000000000b3863541",
|
||||
|
||||
// announce packets
|
||||
"3d7592ead4b8c9e300000001b871a3820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002092f616e6e6f756e6365",
|
||||
"3d7592ead4b8c9e30000000188deed1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002092f616e6e6f756e6365",
|
||||
"3d7592ead4b8c9e300000001ceb948ad0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a3362cdb7020ff920e5aa642c3d4066950dd1f01f4d00000000000000000000000000000000000000000000000000000000000000000000000000000000000002092f616e6e6f756e6365",
|
||||
|
||||
// scrape packets
|
||||
"3d7592ead4b8c9e300000002d2f4bba5a94a8fe5ccb19ba61c4c0873d391e987982fbbd3",
|
||||
"3d7592ead4b8c9e300000002441243292aae6c35c94fcfb415dbe95f408b9ce91ee846ed",
|
||||
"3d7592ead4b8c9e300000002b2aa461b1ad1fa9661cf3fe45fb2504ad52ec6c67758e294",
|
||||
}
|
||||
|
||||
for _, pkt := range connectPackets {
|
||||
pkt, err := hex.DecodeString(pkt)
|
||||
require.NoError(t, err)
|
||||
|
||||
metadata, err := sniff.UDPTracker(context.TODO(), pkt)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
||||
}
|
||||
}
|
||||
@@ -27,11 +27,10 @@ func (c *echClientConfig) DialEarly(ctx context.Context, conn net.PacketConn, ad
|
||||
return quic.DialEarly(ctx, conn, addr, c.config, config)
|
||||
}
|
||||
|
||||
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config, enableDatagrams bool) http.RoundTripper {
|
||||
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config) http.RoundTripper {
|
||||
return &http3.RoundTripper{
|
||||
TLSClientConfig: c.config,
|
||||
QuicConfig: quicConfig,
|
||||
EnableDatagrams: enableDatagrams,
|
||||
QUICConfig: quicConfig,
|
||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
quicConn, err := quic.DialEarly(ctx, conn, serverAddr.UDPAddr(), tlsCfg, cfg)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
ProtocolTLS = "tls"
|
||||
ProtocolHTTP = "http"
|
||||
ProtocolQUIC = "quic"
|
||||
ProtocolDNS = "dns"
|
||||
ProtocolSTUN = "stun"
|
||||
ProtocolTLS = "tls"
|
||||
ProtocolHTTP = "http"
|
||||
ProtocolQUIC = "quic"
|
||||
ProtocolDNS = "dns"
|
||||
ProtocolSTUN = "stun"
|
||||
ProtocolBitTorrent = "bittorrent"
|
||||
)
|
||||
|
||||
@@ -32,6 +32,12 @@ const (
|
||||
|
||||
func ProxyDisplayName(proxyType string) string {
|
||||
switch proxyType {
|
||||
case TypeTun:
|
||||
return "TUN"
|
||||
case TypeRedirect:
|
||||
return "Redirect"
|
||||
case TypeTProxy:
|
||||
return "TProxy"
|
||||
case TypeDirect:
|
||||
return "Direct"
|
||||
case TypeBlock:
|
||||
@@ -42,6 +48,8 @@ func ProxyDisplayName(proxyType string) string {
|
||||
return "SOCKS"
|
||||
case TypeHTTP:
|
||||
return "HTTP"
|
||||
case TypeMixed:
|
||||
return "Mixed"
|
||||
case TypeShadowsocks:
|
||||
return "Shadowsocks"
|
||||
case TypeVMess:
|
||||
|
||||
@@ -3,15 +3,18 @@ package constant
|
||||
import "time"
|
||||
|
||||
const (
|
||||
TCPTimeout = 5 * time.Second
|
||||
ReadPayloadTimeout = 300 * time.Millisecond
|
||||
DNSTimeout = 10 * time.Second
|
||||
QUICTimeout = 30 * time.Second
|
||||
STUNTimeout = 15 * time.Second
|
||||
UDPTimeout = 5 * time.Minute
|
||||
DefaultURLTestInterval = 3 * time.Minute
|
||||
DefaultURLTestIdleTimeout = 30 * time.Minute
|
||||
DefaultStartTimeout = 10 * time.Second
|
||||
DefaultStopTimeout = 5 * time.Second
|
||||
DefaultStopFatalTimeout = 10 * time.Second
|
||||
TCPKeepAliveInitial = 10 * time.Minute
|
||||
TCPKeepAliveInterval = 75 * time.Second
|
||||
TCPTimeout = 5 * time.Second
|
||||
ReadPayloadTimeout = 300 * time.Millisecond
|
||||
DNSTimeout = 10 * time.Second
|
||||
QUICTimeout = 30 * time.Second
|
||||
STUNTimeout = 15 * time.Second
|
||||
UDPTimeout = 5 * time.Minute
|
||||
DefaultURLTestInterval = 3 * time.Minute
|
||||
DefaultURLTestIdleTimeout = 30 * time.Minute
|
||||
StartTimeout = 10 * time.Second
|
||||
StopTimeout = 5 * time.Second
|
||||
FatalStopTimeout = 10 * time.Second
|
||||
FakeIPMetadataSaveInterval = 10 * time.Second
|
||||
)
|
||||
|
||||
@@ -2,6 +2,324 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.10.0-alpha.15
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.10.0-alpha.13
|
||||
|
||||
* TUN address fields are merged **1**
|
||||
* Add route address set support for auto-redirect **2**
|
||||
|
||||
**1**:
|
||||
|
||||
See [Migration](/migration/#tun-address-fields-are-merged).
|
||||
|
||||
**2**:
|
||||
|
||||
The new feature will allow you to configure the destination IP CIDR rules
|
||||
in the specified rule-sets to the firewall automatically.
|
||||
|
||||
Specified or unspecified destinations will bypass the sing-box routes to get better performance
|
||||
(for example, keep hardware offloading of direct traffics on the router).
|
||||
|
||||
See [route_address_set](/configuration/inbound/tun/#route_address_set)
|
||||
and [route_exclude_address_set](/configuration/inbound/tun/#route_exclude_address_set).
|
||||
|
||||
#### 1.10.0-alpha.12
|
||||
|
||||
* Fix auto-redirect not configuring nftables forward chain correctly
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.9.3
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.10.0-alpha.10
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.9.2
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.10.0-alpha.8
|
||||
|
||||
* Drop support for go1.18 and go1.19 **1**
|
||||
* Update quic-go to v0.45.0
|
||||
* Update Hysteria2 BBR congestion control
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.
|
||||
|
||||
### 1.9.1
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.10.0-alpha.7
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.10.0-alpha.5
|
||||
|
||||
* Improve auto-redirect **1**
|
||||
|
||||
**1**:
|
||||
|
||||
nftables support and DNS hijacking has been added.
|
||||
|
||||
Tun inbounds with `auto_route` and `auto_redirect` now works as expected on routers **without intervention**.
|
||||
|
||||
#### 1.10.0-alpha.4
|
||||
|
||||
* Fix auto-redirect **1**
|
||||
* Improve auto-route on linux **2**
|
||||
|
||||
**1**:
|
||||
|
||||
Tun inbounds with `auto_route` and `auto_redirect` now works as expected on routers.
|
||||
|
||||
**2**:
|
||||
|
||||
Tun inbounds with `auto_route` and `strict_route` now works as expected on routers and servers,
|
||||
but the usages of [exclude_interface](/configuration/inbound/tun/#exclude_interface) need to be updated.
|
||||
|
||||
#### 1.10.0-alpha.2
|
||||
|
||||
* Move auto-redirect to Tun **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Linux support are added.
|
||||
|
||||
See [Tun](/configuration/inbound/tun/#auto_redirect).
|
||||
|
||||
#### 1.10.0-alpha.1
|
||||
|
||||
* Add tailing comma support in JSON configuration
|
||||
* Add simple auto-redirect for Android **1**
|
||||
* Add BitTorrent sniffer **2**
|
||||
|
||||
**1**:
|
||||
|
||||
It allows you to use redirect inbound in the sing-box Android client
|
||||
and automatically configures IPv4 TCP redirection via su.
|
||||
|
||||
This may alleviate the symptoms of some OCD patients who think that
|
||||
redirect can effectively save power compared to the system HTTP Proxy.
|
||||
|
||||
See [Redirect](/configuration/inbound/redirect/).
|
||||
|
||||
**2**:
|
||||
|
||||
See [Protocol Sniff](/configuration/route/sniff/).
|
||||
|
||||
### 1.9.0
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
Important changes since 1.8:
|
||||
|
||||
* `domain_suffix` behavior update **1**
|
||||
* `process_path` format update on Windows **2**
|
||||
* Add address filter DNS rule items **3**
|
||||
* Add support for `client-subnet` DNS options **4**
|
||||
* Add rejected DNS response cache support **5**
|
||||
* Add `bypass_domain` and `search_domain` platform HTTP proxy options **6**
|
||||
* Fix missing `rule_set_ipcidr_match_source` item in DNS rules **7**
|
||||
* Handle Windows power events
|
||||
* Always disable cache for fake-ip DNS transport if `dns.independent_cache` disabled
|
||||
* Improve DNS truncate behavior
|
||||
* Update Hysteria protocol
|
||||
* Update quic-go to v0.43.1
|
||||
* Update gVisor to 20240422.0
|
||||
* Mitigating TunnelVision attacks **8**
|
||||
|
||||
**1**:
|
||||
|
||||
See [Migration](/migration/#domain_suffix-behavior-update).
|
||||
|
||||
**2**:
|
||||
|
||||
See [Migration](/migration/#process_path-format-update-on-windows).
|
||||
|
||||
**3**:
|
||||
|
||||
The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS
|
||||
if using this method.
|
||||
|
||||
See [Address Filter Fields](/configuration/dns/rule#address-filter-fields).
|
||||
|
||||
[Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated.
|
||||
|
||||
**4**:
|
||||
|
||||
See [DNS](/configuration/dns), [DNS Server](/configuration/dns/server) and [DNS Rules](/configuration/dns/rule).
|
||||
|
||||
Since this feature makes the scenario mentioned in `alpha.1` no longer leak DNS requests,
|
||||
the [Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) has been updated.
|
||||
|
||||
**5**:
|
||||
|
||||
The new feature allows you to cache the check results of
|
||||
[Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields) until expiration.
|
||||
|
||||
**6**:
|
||||
|
||||
See [TUN](/configuration/inbound/tun) inbound.
|
||||
|
||||
**7**:
|
||||
|
||||
See [DNS Rule](/configuration/dns/rule/).
|
||||
|
||||
**8**:
|
||||
|
||||
See [TunnelVision](/manual/misc/tunnelvision).
|
||||
|
||||
#### 1.9.0-rc.22
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-rc.20
|
||||
|
||||
* Prioritize `*_route_address` in linux auto-route
|
||||
* Fix `*_route_address` in darwin auto-route
|
||||
|
||||
#### 1.8.14
|
||||
|
||||
* Fix hysteria2 panic
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-rc.18
|
||||
|
||||
* Add custom prefix support in EDNS0 client subnet options
|
||||
* Fix hysteria2 crash
|
||||
* Fix `store_rdrc` corrupted
|
||||
* Update quic-go to v0.43.1
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-rc.16
|
||||
|
||||
* Mitigating TunnelVision attacks **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [TunnelVision](/manual/misc/tunnelvision).
|
||||
|
||||
#### 1.9.0-rc.15
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.8.13
|
||||
|
||||
* Fix fake-ip mapping
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-rc.14
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-rc.13
|
||||
|
||||
* Update Hysteria protocol
|
||||
* Update quic-go to v0.43.0
|
||||
* Update gVisor to 20240422.0
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.8.12
|
||||
|
||||
* Now we have official APT and DNF repositories **1**
|
||||
* Fix packet MTU for QUIC protocols
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Including stable and beta versions, see https://sing-box.sagernet.org/installation/package-manager/
|
||||
|
||||
#### 1.9.0-rc.11
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.8.11
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.8.10
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-beta.17
|
||||
|
||||
* Update `quic-go` to v0.42.0
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-beta.16
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_Our Testflight distribution has been temporarily blocked by Apple (possibly due to too many beta versions)
|
||||
and you cannot join the test, install or update the sing-box beta app right now.
|
||||
Please wait patiently for processing._
|
||||
|
||||
#### 1.9.0-beta.14
|
||||
|
||||
* Update gVisor to 20240212.0-65-g71212d503
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.8.9
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.8.8
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-beta.7
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-beta.6
|
||||
|
||||
* Fix address filter DNS rule items **1**
|
||||
* Fix DNS outbound responding with wrong data
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Fixed an issue where address filter DNS rule was incorrectly rejected under certain circumstances.
|
||||
If you have enabled `store_rdrc` to save results, consider clearing the cache file.
|
||||
|
||||
#### 1.8.7
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-alpha.15
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-alpha.14
|
||||
|
||||
* Improve DNS truncate behavior
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-alpha.13
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.8.6
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-alpha.12
|
||||
|
||||
* Handle Windows power events
|
||||
* Always disable cache for fake-ip DNS transport if `dns.independent_cache` disabled
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-alpha.11
|
||||
|
||||
* Fix missing `rule_set_ipcidr_match_source` item in DNS rules **1**
|
||||
@@ -95,7 +413,7 @@ See [Address Filter Fields](/configuration/dns/rule#address-filter-fields).
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.8.0
|
||||
### 1.8.0
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@@ -386,7 +704,7 @@ New commands manage GeoIP, Geosite and rule set resources, and help you migrate
|
||||
|
||||
Logical rules in route rules, DNS rules, and the new headless rule now allow nesting of logical rules.
|
||||
|
||||
#### 1.7.0
|
||||
### 1.7.0
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@@ -546,7 +864,7 @@ Introduced in V2Ray 5.10.0.
|
||||
|
||||
The new HTTPUpgrade transport has better performance than WebSocket and is better suited for CDN abuse.
|
||||
|
||||
#### 1.6.0
|
||||
### 1.6.0
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@@ -725,7 +1043,7 @@ introduce new issues.
|
||||
None of the existing Golang BBR congestion control implementations have been reviewed or unit tested.
|
||||
This update is intended to address the multi-send defects of the old implementation and may introduce new issues.
|
||||
|
||||
#### 1.5.0
|
||||
### 1.5.0
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@@ -919,7 +1237,7 @@ All inbounds and outbounds are supported, including `Naiveproxy`, `Hysteria`, `T
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.4.0
|
||||
### 1.4.0
|
||||
|
||||
* Fix bugs and update dependencies
|
||||
|
||||
@@ -1061,7 +1379,7 @@ The old testflight link and app are no longer valid.
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.3.0
|
||||
### 1.3.0
|
||||
|
||||
* Fix bugs and update dependencies
|
||||
|
||||
@@ -1253,7 +1571,7 @@ to `domain` rule.
|
||||
* Flush DNS cache for macOS when tun start/close
|
||||
* Fix tun's DNS hijacking compatibility with systemd-resolved
|
||||
|
||||
#### 1.2.0
|
||||
### 1.2.0
|
||||
|
||||
* Fix bugs and update dependencies
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ platform-specific function implementation, such as TUN transparent proxy impleme
|
||||
* [Play Store](https://play.google.com/store/apps/details?id=io.nekohasekai.sfa)
|
||||
* [Play Store (Beta)](https://play.google.com/apps/testing/io.nekohasekai.sfa)
|
||||
* [GitHub Releases](https://github.com/SagerNet/sing-box/releases)
|
||||
* [F-Droid](https://f-droid.org/packages/io.nekohasekai.sfa/) (Unified signature via reproducible builds)
|
||||
|
||||
## :material-source-repository: Source code
|
||||
|
||||
|
||||
@@ -15,7 +15,12 @@ platform-specific function implementation, such as TUN transparent proxy impleme
|
||||
## :material-download: Download
|
||||
|
||||
* [App Store](https://apps.apple.com/us/app/sing-box/id6451272673)
|
||||
* [TestFlight (Beta)](https://testflight.apple.com/join/AcqO44FH)
|
||||
* ~~TestFlight (Beta)~~
|
||||
|
||||
TestFlight quota is only available to [sponsors](https://github.com/sponsors/nekohasekai)
|
||||
(one-time sponsorships are accepted).
|
||||
Once you donate, you can get an invitation by sending us your Apple ID [via email](mailto:contact@sagernet.org),
|
||||
or join our Telegram group for sponsors from [@yet_another_sponsor_bot](https://t.me/yet_another_sponsor_bot).
|
||||
|
||||
## :material-file-download: Download (macOS standalone version)
|
||||
|
||||
|
||||
@@ -6,3 +6,9 @@ icon: material/security
|
||||
|
||||
sing-box and official graphics clients do not collect or share personal data,
|
||||
and the data generated by the software is always on your device.
|
||||
|
||||
## Android
|
||||
|
||||
If your configuration contains `wifi_ssid` or `wifi_bssid` routing rules,
|
||||
sing-box uses the location permission in the background
|
||||
to get information about the connected Wi-Fi network to make them work.
|
||||
|
||||
@@ -73,6 +73,8 @@ problematic in environments such as macOS, where DNS is proxied and cached by th
|
||||
|
||||
!!! question "Since sing-box 1.9.0"
|
||||
|
||||
Append a `edns0-subnet` OPT extra record with the specified IP address to every query by default.
|
||||
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
|
||||
|
||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||
|
||||
Can be overrides by `servers.[].client_subnet` or `rules.[].client_subnet`.
|
||||
|
||||
@@ -71,8 +71,10 @@ icon: material/new-box
|
||||
|
||||
!!! question "自 sing-box 1.9.0 起"
|
||||
|
||||
默认情况下,将带有指定 IP 地址的 `edns0-subnet` OPT 附加记录附加到每个查询。
|
||||
|
||||
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
|
||||
|
||||
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
|
||||
|
||||
可以被 `servers.[].client_subnet` 或 `rules.[].client_subnet` 覆盖。
|
||||
|
||||
#### fakeip
|
||||
|
||||
@@ -125,7 +125,7 @@ icon: material/new-box
|
||||
"server": "local",
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": 100,
|
||||
"client_subnet": "127.0.0.1"
|
||||
"client_subnet": "127.0.0.1/24"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
@@ -134,7 +134,7 @@ icon: material/new-box
|
||||
"server": "local",
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": 100,
|
||||
"client_subnet": "127.0.0.1"
|
||||
"client_subnet": "127.0.0.1/24"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -339,7 +339,9 @@ Rewrite TTL in DNS responses.
|
||||
|
||||
!!! question "Since sing-box 1.9.0"
|
||||
|
||||
Append a `edns0-subnet` OPT extra record with the specified IP address to every query by default.
|
||||
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
|
||||
|
||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||
|
||||
Will overrides `dns.client_subnet` and `servers.[].client_subnet`.
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ icon: material/new-box
|
||||
],
|
||||
"server": "local",
|
||||
"disable_cache": false,
|
||||
"client_subnet": "127.0.0.1"
|
||||
"client_subnet": "127.0.0.1/24"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
@@ -132,7 +132,7 @@ icon: material/new-box
|
||||
"rules": [],
|
||||
"server": "local",
|
||||
"disable_cache": false,
|
||||
"client_subnet": "127.0.0.1"
|
||||
"client_subnet": "127.0.0.1/24"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -337,7 +337,9 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
!!! question "自 sing-box 1.9.0 起"
|
||||
|
||||
默认情况下,将带有指定 IP 地址的 `edns0-subnet` OPT 附加记录附加到每个查询。
|
||||
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
|
||||
|
||||
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
|
||||
|
||||
将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。
|
||||
|
||||
@@ -355,19 +357,19 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
#### geoip
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
!!! question "自 sing-box 1.9.0 起"
|
||||
|
||||
与查询响应匹配 GeoIP。
|
||||
|
||||
#### ip_cidr
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
!!! question "自 sing-box 1.9.0 起"
|
||||
|
||||
与查询相应匹配 IP CIDR。
|
||||
|
||||
#### ip_is_private
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
!!! question "自 sing-box 1.9.0 起"
|
||||
|
||||
与查询响应匹配非公开 IP。
|
||||
|
||||
@@ -383,4 +385,4 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
#### rules
|
||||
|
||||
包括的规则。
|
||||
包括的规则。
|
||||
|
||||
@@ -100,7 +100,9 @@ Default outbound will be used if empty.
|
||||
|
||||
!!! question "Since sing-box 1.9.0"
|
||||
|
||||
Append a `edns0-subnet` OPT extra record with the specified IP address to every query by default.
|
||||
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
|
||||
|
||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||
|
||||
Can be overrides by `rules.[].client_subnet`.
|
||||
|
||||
|
||||
@@ -100,7 +100,9 @@ DNS 服务器的地址。
|
||||
|
||||
!!! question "自 sing-box 1.9.0 起"
|
||||
|
||||
默认情况下,将带有指定 IP 地址的 `edns0-subnet` OPT 附加记录附加到每个查询。
|
||||
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
|
||||
|
||||
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
|
||||
|
||||
可以被 `rules.[].client_subnet` 覆盖。
|
||||
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
|
||||
:material-plus: [address](#address)
|
||||
:material-delete-clock: [inet4_address](#inet4_address)
|
||||
:material-delete-clock: [inet6_address](#inet6_address)
|
||||
:material-plus: [route_address](#route_address)
|
||||
:material-delete-clock: [inet4_route_address](#inet4_route_address)
|
||||
:material-delete-clock: [inet6_route_address](#inet6_route_address)
|
||||
:material-plus: [route_exclude_address](#route_address)
|
||||
:material-delete-clock: [inet4_route_exclude_address](#inet4_route_exclude_address)
|
||||
:material-delete-clock: [inet6_route_exclude_address](#inet6_route_exclude_address)
|
||||
:material-plus: [auto_redirect](#auto_redirect)
|
||||
:material-plus: [route_address_set](#route_address_set)
|
||||
:material-plus: [route_exclude_address_set](#route_address_set)
|
||||
|
||||
!!! quote "Changes in sing-box 1.9.0"
|
||||
|
||||
:material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain)
|
||||
@@ -23,26 +38,57 @@ icon: material/new-box
|
||||
"type": "tun",
|
||||
"tag": "tun-in",
|
||||
"interface_name": "tun0",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||
"address": [
|
||||
"172.18.0.1/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
// deprecated
|
||||
"inet4_address": [
|
||||
"172.19.0.1/30"
|
||||
],
|
||||
// deprecated
|
||||
"inet6_address": [
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"mtu": 9000,
|
||||
"gso": false,
|
||||
"auto_route": true,
|
||||
"strict_route": true,
|
||||
"auto_redirect": false,
|
||||
"route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1",
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
// deprecated
|
||||
"inet4_route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1"
|
||||
],
|
||||
// deprecated
|
||||
"inet6_route_address": [
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"route_exclude_address": [
|
||||
"192.168.0.0/16",
|
||||
"fc00::/7"
|
||||
],
|
||||
// deprecated
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
// deprecated
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
],
|
||||
"route_address_set": [
|
||||
"geoip-cloudflare"
|
||||
],
|
||||
"route_exclude_address_set": [
|
||||
"geoip-cn"
|
||||
],
|
||||
"endpoint_independent_nat": false,
|
||||
"udp_timeout": "5m",
|
||||
"stack": "system",
|
||||
@@ -102,14 +148,26 @@ icon: material/new-box
|
||||
|
||||
Virtual device name, automatically selected if empty.
|
||||
|
||||
#### address
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
IPv4 and IPv6 prefix for the tun interface.
|
||||
|
||||
#### inet4_address
|
||||
|
||||
==Required==
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet4_address` is merged to `address` and will be removed in sing-box 1.11.0.
|
||||
|
||||
IPv4 prefix for the tun interface.
|
||||
|
||||
#### inet6_address
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet6_address` is merged to `address` and will be removed in sing-box 1.11.0.
|
||||
|
||||
IPv6 prefix for the tun interface.
|
||||
|
||||
#### mtu
|
||||
@@ -145,9 +203,10 @@ Enforce strict routing rules when `auto_route` is enabled:
|
||||
*In Linux*:
|
||||
|
||||
* Let unsupported network unreachable
|
||||
* Make ICMP traffic route to tun instead of upstream interfaces
|
||||
* Route all connections to tun
|
||||
|
||||
It prevents address leaks and makes DNS hijacking work on Android, but your device will not be accessible by others.
|
||||
It prevents IP address leaks and makes DNS hijacking work on Android.
|
||||
|
||||
*In Windows*:
|
||||
|
||||
@@ -156,22 +215,95 @@ It prevents address leaks and makes DNS hijacking work on Android, but your devi
|
||||
|
||||
It may prevent some applications (such as VirtualBox) from working properly in certain situations.
|
||||
|
||||
#### auto_redirect
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux with `auto_route` enabled.
|
||||
|
||||
Automatically configure iptables/nftables to redirect connections.
|
||||
|
||||
*In Android*:
|
||||
|
||||
Only local connections are forwarded. To share your VPN connection over hotspot or repeater,
|
||||
use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
|
||||
|
||||
*In Linux*:
|
||||
|
||||
`auto_route` with `auto_redirect` now works as expected on routers **without intervention**.
|
||||
|
||||
#### route_address
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
Use custom routes instead of default when `auto_route` is enabled.
|
||||
|
||||
#### inet4_route_address
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet4_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address) instead.
|
||||
|
||||
Use custom routes instead of default when `auto_route` is enabled.
|
||||
|
||||
#### inet6_route_address
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet6_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address) instead.
|
||||
|
||||
Use custom routes instead of default when `auto_route` is enabled.
|
||||
|
||||
#### route_exclude_address
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
Exclude custom routes when `auto_route` is enabled.
|
||||
|
||||
#### inet4_route_exclude_address
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet4_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_exclude_address](#route_exclude_address) instead.
|
||||
|
||||
Exclude custom routes when `auto_route` is enabled.
|
||||
|
||||
#### inet6_route_exclude_address
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet6_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_exclude_address](#route_exclude_address) instead.
|
||||
|
||||
Exclude custom routes when `auto_route` is enabled.
|
||||
|
||||
#### route_address_set
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
|
||||
|
||||
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
|
||||
Unmatched traffic will bypass the sing-box routes.
|
||||
|
||||
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
|
||||
|
||||
#### route_exclude_address_set
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
|
||||
|
||||
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
|
||||
Matched traffic will bypass the sing-box routes.
|
||||
|
||||
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
|
||||
|
||||
#### endpoint_independent_nat
|
||||
|
||||
!!! info ""
|
||||
@@ -214,6 +346,10 @@ Conflict with `exclude_interface`.
|
||||
|
||||
#### exclude_interface
|
||||
|
||||
!!! warning ""
|
||||
|
||||
When `strict_route` enabled, return traffic to excluded interfaces will not be automatically excluded, so add them as well (example: `br-lan` and `pppoe-wan`).
|
||||
|
||||
Exclude interfaces in route.
|
||||
|
||||
Conflict with `include_interface`.
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
|
||||
:material-plus: [address](#address)
|
||||
:material-delete-clock: [inet4_address](#inet4_address)
|
||||
:material-delete-clock: [inet6_address](#inet6_address)
|
||||
:material-plus: [route_address](#route_address)
|
||||
:material-delete-clock: [inet4_route_address](#inet4_route_address)
|
||||
:material-delete-clock: [inet6_route_address](#inet6_route_address)
|
||||
:material-plus: [route_exclude_address](#route_address)
|
||||
:material-delete-clock: [inet4_route_exclude_address](#inet4_route_exclude_address)
|
||||
:material-delete-clock: [inet6_route_exclude_address](#inet6_route_exclude_address)
|
||||
:material-plus: [auto_redirect](#auto_redirect)
|
||||
:material-plus: [route_address_set](#route_address_set)
|
||||
:material-plus: [route_exclude_address_set](#route_address_set)
|
||||
|
||||
!!! quote "sing-box 1.9.0 中的更改"
|
||||
|
||||
:material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain)
|
||||
@@ -23,26 +38,57 @@ icon: material/new-box
|
||||
"type": "tun",
|
||||
"tag": "tun-in",
|
||||
"interface_name": "tun0",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||
"address": [
|
||||
"172.18.0.1/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
// 已弃用
|
||||
"inet4_address": [
|
||||
"172.19.0.1/30"
|
||||
],
|
||||
// 已弃用
|
||||
"inet6_address": [
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"mtu": 9000,
|
||||
"gso": false,
|
||||
"auto_route": true,
|
||||
"strict_route": true,
|
||||
"auto_redirect": false,
|
||||
"route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1",
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
// 已弃用
|
||||
"inet4_route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1"
|
||||
],
|
||||
// 已弃用
|
||||
"inet6_route_address": [
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"route_exclude_address": [
|
||||
"192.168.0.0/16",
|
||||
"fc00::/7"
|
||||
],
|
||||
// 已弃用
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
// 已弃用
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
],
|
||||
"route_address_set": [
|
||||
"geoip-cloudflare"
|
||||
],
|
||||
"route_exclude_address_set": [
|
||||
"geoip-cn"
|
||||
],
|
||||
"endpoint_independent_nat": false,
|
||||
"udp_timeout": "5m",
|
||||
"stack": "system",
|
||||
@@ -102,14 +148,30 @@ icon: material/new-box
|
||||
|
||||
虚拟设备名称,默认自动选择。
|
||||
|
||||
#### address
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
==必填==
|
||||
|
||||
tun 接口的 IPv4 和 IPv6 前缀。
|
||||
|
||||
#### inet4_address
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet4_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除.
|
||||
|
||||
==必填==
|
||||
|
||||
tun 接口的 IPv4 前缀。
|
||||
|
||||
#### inet6_address
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet6_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除.
|
||||
|
||||
tun 接口的 IPv6 前缀。
|
||||
|
||||
#### mtu
|
||||
@@ -145,9 +207,10 @@ tun 接口的 IPv6 前缀。
|
||||
*在 Linux 中*:
|
||||
|
||||
* 让不支持的网络无法到达
|
||||
* 使 ICMP 流量路由到 tun 而不是上游接口
|
||||
* 将所有连接路由到 tun
|
||||
|
||||
它可以防止地址泄漏,并使 DNS 劫持在 Android 上工作,但你的设备将无法其他设备被访问。
|
||||
它可以防止 IP 地址泄漏,并使 DNS 劫持在 Android 上工作。
|
||||
|
||||
*在 Windows 中*:
|
||||
|
||||
@@ -157,22 +220,94 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
它可能会使某些应用程序(如 VirtualBox)在某些情况下无法正常工作。
|
||||
|
||||
#### auto_redirect
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux。
|
||||
|
||||
自动配置 iptables 以重定向 TCP 连接。
|
||||
|
||||
*在 Android 中*:
|
||||
|
||||
仅转发本地 IPv4 连接。 要通过热点或中继共享您的 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
|
||||
|
||||
*在 Linux 中*:
|
||||
|
||||
带有 `auto_redirect `的 `auto_route` 现在可以在路由器上按预期工作,**无需干预**。
|
||||
|
||||
#### route_address
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
设置到 Tun 的自定义路由。
|
||||
|
||||
#### inet4_route_address
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除.
|
||||
|
||||
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
||||
|
||||
#### inet6_route_address
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除.
|
||||
|
||||
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
||||
|
||||
#### route_exclude_address
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
设置到 Tun 的排除自定义路由。
|
||||
|
||||
#### inet4_route_exclude_address
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除.
|
||||
|
||||
启用 `auto_route` 时排除自定义路由。
|
||||
|
||||
#### inet6_route_exclude_address
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除.
|
||||
|
||||
启用 `auto_route` 时排除自定义路由。
|
||||
|
||||
#### route_address_set
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。
|
||||
|
||||
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
|
||||
不匹配的流量将绕过 sing-box 路由。
|
||||
|
||||
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
||||
|
||||
#### route_exclude_address_set
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。
|
||||
|
||||
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
|
||||
匹配的流量将绕过 sing-box 路由。
|
||||
|
||||
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
||||
|
||||
#### endpoint_independent_nat
|
||||
|
||||
启用独立于端点的 NAT。
|
||||
@@ -211,6 +346,10 @@ TCP/IP 栈。
|
||||
|
||||
#### exclude_interface
|
||||
|
||||
!!! warning ""
|
||||
|
||||
当 `strict_route` 启用,到被排除接口的回程流量将不会被自动排除,因此也要添加它们(例:`br-lan` 与 `pppoe-wan`)。
|
||||
|
||||
排除路由的接口。
|
||||
|
||||
与 `include_interface` 冲突。
|
||||
@@ -284,7 +423,7 @@ TCP/IP 栈。
|
||||
|
||||
!!! note ""
|
||||
|
||||
在 Apple 平台,`bypass_domain` 项匹配主机名 **后缀**.
|
||||
在 Apple 平台,`bypass_domain` 项匹配主机名 **后缀**.
|
||||
|
||||
绕过代理的主机名列表。
|
||||
|
||||
|
||||
@@ -2,10 +2,11 @@ If enabled in the inbound, the protocol and domain name (if present) of by the c
|
||||
|
||||
#### Supported Protocols
|
||||
|
||||
| Network | Protocol | Domain Name |
|
||||
|:-------:|:--------:|:-----------:|
|
||||
| TCP | HTTP | Host |
|
||||
| TCP | TLS | Server Name |
|
||||
| UDP | QUIC | Server Name |
|
||||
| UDP | STUN | / |
|
||||
| TCP/UDP | DNS | / |
|
||||
| Network | Protocol | Domain Name |
|
||||
|:-------:|:-----------:|:-----------:|
|
||||
| TCP | HTTP | Host |
|
||||
| TCP | TLS | Server Name |
|
||||
| UDP | QUIC | Server Name |
|
||||
| UDP | STUN | / |
|
||||
| TCP/UDP | DNS | / |
|
||||
| TCP/UDP | BitTorrent | / |
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
#### 支持的协议
|
||||
|
||||
| 网络 | 协议 | 域名 |
|
||||
|:-------:|:----:|:-----------:|
|
||||
| TCP | HTTP | Host |
|
||||
| TCP | TLS | Server Name |
|
||||
| UDP | QUIC | Server Name |
|
||||
| UDP | STUN | / |
|
||||
| TCP/UDP | DNS | / |
|
||||
| 网络 | 协议 | 域名 |
|
||||
|:-------:|:-----------:|:-----------:|
|
||||
| TCP | HTTP | Host |
|
||||
| TCP | TLS | Server Name |
|
||||
| UDP | QUIC | Server Name |
|
||||
| UDP | STUN | / |
|
||||
| TCP/UDP | DNS | / |
|
||||
| TCP/UDP | BitTorrent | / |
|
||||
@@ -4,6 +4,20 @@ icon: material/delete-alert
|
||||
|
||||
# Deprecated Feature List
|
||||
|
||||
## 1.10.0
|
||||
|
||||
#### TUN address fields are merged
|
||||
|
||||
`inet4_address` and `inet6_address` are merged into `address`,
|
||||
`inet4_route_address` and `inet6_route_address` are merged into `route_address`,
|
||||
`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.
|
||||
|
||||
Old fields are deprecated and will be removed in sing-box 1.11.0.
|
||||
|
||||
#### Drop support for go1.18 and go1.19
|
||||
|
||||
Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.
|
||||
|
||||
## 1.8.0
|
||||
|
||||
#### Cache file and related features in Clash API
|
||||
|
||||
@@ -4,6 +4,20 @@ icon: material/delete-alert
|
||||
|
||||
# 废弃功能列表
|
||||
|
||||
## 1.10.0
|
||||
|
||||
#### TUN 地址字段已合并
|
||||
|
||||
`inet4_address` 和 `inet6_address` 已合并为 `address`,
|
||||
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
||||
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
||||
|
||||
旧字段已废弃,且将在 sing-box 1.11.0 中移除。
|
||||
|
||||
#### 移除对 go1.18 和 go1.19 的支持
|
||||
|
||||
由于维护困难,sing-box 1.10.0 要求至少 Go 1.20 才能编译。
|
||||
|
||||
## 1.8.0
|
||||
|
||||
#### Clash API 中的 Cache file 及相关功能
|
||||
|
||||
@@ -57,16 +57,16 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
|
||||
| Build Tag | Enabled by default | Description |
|
||||
|------------------------------------|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `with_quic` | :material-check: | Build with QUIC support, see [QUIC and HTTP3 DNS transports](/configuration/dns/server/), [Naive inbound](/configuration/inbound/naive/), [Hysteria Inbound](/configuration/inbound/hysteria/), [Hysteria Outbound](/configuration/outbound/hysteria/) and [V2Ray Transport#QUIC](/configuration/shared/v2ray-transport#quic). |
|
||||
| `with_grpc` | :material-close:️ | Build with standard gRPC support, see [V2Ray Transport#gRPC](/configuration/shared/v2ray-transport#grpc). |
|
||||
| `with_grpc` | :material-close:️ | Build with standard gRPC support, see [V2Ray Transport#gRPC](/configuration/shared/v2ray-transport#grpc). |
|
||||
| `with_dhcp` | :material-check: | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/). |
|
||||
| `with_wireguard` | :material-check: | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/). |
|
||||
| `with_ech` | :material-check: | Build with TLS ECH extension support for TLS outbound, see [TLS](/configuration/shared/tls#ech). |
|
||||
| `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). |
|
||||
| `with_ech` | :material-check: | Build with TLS ECH extension support for TLS outbound, see [TLS](/configuration/shared/tls#ech). |
|
||||
| `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). |
|
||||
| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). |
|
||||
| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). |
|
||||
| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). |
|
||||
| `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
|
||||
| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
|
||||
| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). |
|
||||
| `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
|
||||
| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
|
||||
| `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). |
|
||||
|
||||
It is not recommended to change the default build tag list unless you really know what you are adding.
|
||||
|
||||
@@ -54,19 +54,19 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
|
||||
|
||||
## :material-folder-settings: 构建标记
|
||||
|
||||
| 构建标记 | 默认启动 | 说明 |
|
||||
|------------------------------------|-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| 构建标记 | 默认启动 | 说明 |
|
||||
|------------------------------------|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `with_quic` | :material-check: | Build with QUIC support, see [QUIC and HTTP3 DNS transports](/configuration/dns/server/), [Naive inbound](/configuration/inbound/naive/), [Hysteria Inbound](/configuration/inbound/hysteria/), [Hysteria Outbound](/configuration/outbound/hysteria/) and [V2Ray Transport#QUIC](/configuration/shared/v2ray-transport#quic). |
|
||||
| `with_grpc` | :material-close:️ | Build with standard gRPC support, see [V2Ray Transport#gRPC](/configuration/shared/v2ray-transport#grpc). |
|
||||
| `with_dhcp` | :material-check: | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/). |
|
||||
| `with_wireguard` | :material-check: | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/). |
|
||||
| `with_ech` | :material-check: | Build with TLS ECH extension support for TLS outbound, see [TLS](/configuration/shared/tls#ech). |
|
||||
| `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). |
|
||||
| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). |
|
||||
| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). |
|
||||
| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). |
|
||||
| `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
|
||||
| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
|
||||
| `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). |
|
||||
| `with_grpc` | :material-close:️ | Build with standard gRPC support, see [V2Ray Transport#gRPC](/configuration/shared/v2ray-transport#grpc). |
|
||||
| `with_dhcp` | :material-check: | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/). |
|
||||
| `with_wireguard` | :material-check: | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/). |
|
||||
| `with_ech` | :material-check: | Build with TLS ECH extension support for TLS outbound, see [TLS](/configuration/shared/tls#ech). |
|
||||
| `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). |
|
||||
| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). |
|
||||
| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). |
|
||||
| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). |
|
||||
| `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
|
||||
| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
|
||||
| `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). |
|
||||
|
||||
除非您确实知道您正在启用什么,否则不建议更改默认构建标签列表。
|
||||
|
||||
@@ -4,6 +4,35 @@ icon: material/package
|
||||
|
||||
# Package Manager
|
||||
|
||||
## :material-tram: Repository Installation
|
||||
|
||||
=== ":material-debian: Debian / APT"
|
||||
|
||||
```bash
|
||||
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc
|
||||
sudo chmod a+r /etc/apt/keyrings/sagernet.asc
|
||||
echo "deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/sagernet.asc] https://deb.sagernet.org/ * *" | \
|
||||
sudo tee /etc/apt/sources.list.d/sagernet.list > /dev/null
|
||||
sudo apt-get update
|
||||
sudo apt-get install sing-box # or sing-box-beta
|
||||
```
|
||||
|
||||
=== ":material-redhat: Redhat / DNF"
|
||||
|
||||
```bash
|
||||
sudo dnf -y install dnf-plugins-core
|
||||
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo
|
||||
sudo dnf install sing-box # or sing-box-beta
|
||||
```
|
||||
|
||||
=== ":material-redhat: CentOS / YUM"
|
||||
|
||||
```bash
|
||||
sudo yum install -y yum-utils
|
||||
sudo yum-config-manager --add-repo https://sing-box.app/sing-box.repo
|
||||
sudo yum install sing-box # or sing-box-beta
|
||||
```
|
||||
|
||||
## :material-download-box: Manual Installation
|
||||
|
||||
=== ":material-debian: Debian / DEB"
|
||||
@@ -28,38 +57,38 @@ icon: material/package
|
||||
|
||||
=== ":material-linux: Linux"
|
||||
|
||||
| Type | Platform | Link | Command | Actively maintained |
|
||||
|----------|---------------|-------------------------|------------------------------|---------------------|
|
||||
| APK | Alpine | [sing-box][alpine] | `apk add sing-box` | :material-check: |
|
||||
| AUR | Arch Linux | [sing-box][aur] ᴬᵁᴿ | `? -S sing-box` | :material-check: |
|
||||
| nixpkgs | NixOS | [sing-box][nixpkgs] | `nix-env -iA nixos.sing-box` | :material-check: |
|
||||
| Homebrew | macOS / Linux | [sing-box][brew] | `brew install sing-box` | :material-check: |
|
||||
| Type | Platform | Command | Link |
|
||||
|----------|---------------|------------------------------|---------------------------------------------------------------------------------------------------------------|
|
||||
| AUR | Arch Linux | `? -S sing-box` | [][aur] |
|
||||
| nixpkgs | NixOS | `nix-env -iA nixos.sing-box` | [][nixpkgs] |
|
||||
| Homebrew | macOS / Linux | `brew install sing-box` | [][brew] |
|
||||
| APK | Alpine | `apk add sing-box` | [][alpine] |
|
||||
|
||||
=== ":material-apple: macOS"
|
||||
|
||||
| Type | Platform | Link | Command | Actively maintained |
|
||||
|----------|----------|------------------|-------------------------|---------------------|
|
||||
| Homebrew | macOS | [sing-box][brew] | `brew install sing-box` | :material-check: |
|
||||
| Type | Platform | Command | Link |
|
||||
|----------|----------|-------------------------|------------------------------------------------------------------------------------------------|
|
||||
| Homebrew | macOS | `brew install sing-box` | [][brew] |
|
||||
|
||||
=== ":material-microsoft-windows: Windows"
|
||||
|
||||
| Type | Platform | Link | Command | Actively maintained |
|
||||
|------------|--------------------|---------------------|------------------------------|---------------------|
|
||||
| Scoop | Windows | [sing-box][scoop] | `scoop install sing-box` | :material-check: |
|
||||
| Chocolatey | Windows | [sing-box][choco] | `choco install sing-box` | :material-check: |
|
||||
| winget | Windows | [sing-box][winget] | `winget install sing-box` | :material-alert: |
|
||||
| Type | Platform | Command | Link |
|
||||
|------------|----------|---------------------------|-----------------------------------------------------------------------------------------------------|
|
||||
| Scoop | Windows | `scoop install sing-box` | [][scoop] |
|
||||
| Chocolatey | Windows | `choco install sing-box` | [][choco] |
|
||||
| winget | Windows | `winget install sing-box` | [][winget] |
|
||||
|
||||
=== ":material-android: Android"
|
||||
|
||||
| Type | Platform | Link | Command | Actively maintained |
|
||||
|------------|--------------------|---------------------|------------------------------|---------------------|
|
||||
| Termux | Android | [sing-box][termux] | `pkg add sing-box` | :material-check: |
|
||||
| Type | Platform | Command | Link |
|
||||
|--------|----------|--------------------|----------------------------------------------------------------------------------------------|
|
||||
| Termux | Android | `pkg add sing-box` | [][termux] |
|
||||
|
||||
=== ":material-freebsd: FreeBSD"
|
||||
|
||||
| Type | Platform | Link | Command | Actively maintained |
|
||||
|------------|----------|-------------------|------------------------|---------------------|
|
||||
| FreshPorts | FreeBSD | [sing-box][ports] | `pkg install sing-box` | :material-alert: |
|
||||
| Type | Platform | Command | Link |
|
||||
|------------|----------|------------------------|--------------------------------------------------------------------------------------------|
|
||||
| FreshPorts | FreeBSD | `pkg install sing-box` | [][ports] |
|
||||
|
||||
## :material-book-multiple: Service Management
|
||||
|
||||
|
||||
@@ -4,6 +4,35 @@ icon: material/package
|
||||
|
||||
# 包管理器
|
||||
|
||||
## :material-tram: 仓库安装
|
||||
|
||||
=== ":material-debian: Debian / APT"
|
||||
|
||||
```bash
|
||||
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc
|
||||
sudo chmod a+r /etc/apt/keyrings/sagernet.asc
|
||||
echo "deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/sagernet.asc] https://deb.sagernet.org/ * *" | \
|
||||
sudo tee /etc/apt/sources.list.d/sagernet.list > /dev/null
|
||||
sudo apt-get update
|
||||
sudo apt-get install sing-box # or sing-box-beta
|
||||
```
|
||||
|
||||
=== ":material-redhat: Redhat / DNF"
|
||||
|
||||
```bash
|
||||
sudo dnf -y install dnf-plugins-core
|
||||
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo
|
||||
sudo dnf install sing-box # or sing-box-beta
|
||||
```
|
||||
|
||||
=== ":material-redhat: CentOS / YUM"
|
||||
|
||||
```bash
|
||||
sudo yum install -y yum-utils
|
||||
sudo yum-config-manager --add-repo https://sing-box.app/sing-box.repo
|
||||
sudo yum install sing-box # or sing-box-beta
|
||||
```
|
||||
|
||||
## :material-download-box: 手动安装
|
||||
|
||||
=== ":material-debian: Debian / DEB"
|
||||
@@ -28,38 +57,38 @@ icon: material/package
|
||||
|
||||
=== ":material-linux: Linux"
|
||||
|
||||
| 类型 | 平台 | 链接 | 命令 | 活跃维护 |
|
||||
|----------|------------|---------------------|------------------------------|------------------|
|
||||
| Alpine | Alpine | [sing-box][alpine] | `apk add sing-box` | :material-check: |
|
||||
| AUR | Arch Linux | [sing-box][aur] ᴬᵁᴿ | `? -S sing-box` | :material-check: |
|
||||
| nixpkgs | NixOS | [sing-box][nixpkgs] | `nix-env -iA nixos.sing-box` | :material-check: |
|
||||
| Homebrew | Linux | [sing-box][brew] | `brew install sing-box` | :material-check: |
|
||||
| 类型 | 平台 | 链接 | 命令 |
|
||||
|----------|---------------|------------------------------|---------------------------------------------------------------------------------------------------------------|
|
||||
| AUR | Arch Linux | `? -S sing-box` | [][aur] |
|
||||
| nixpkgs | NixOS | `nix-env -iA nixos.sing-box` | [][nixpkgs] |
|
||||
| Homebrew | macOS / Linux | `brew install sing-box` | [][brew] |
|
||||
| APK | Alpine | `apk add sing-box` | [][alpine] |
|
||||
|
||||
=== ":material-apple: macOS"
|
||||
|
||||
| 类型 | 平台 | 链接 | 命令 | 活跃维护 |
|
||||
|----------|-------|------------------|-------------------------|------------------|
|
||||
| Homebrew | macOS | [sing-box][brew] | `brew install sing-box` | :material-check: |
|
||||
| 类型 | 平台 | 链接 | 命令 |
|
||||
|----------|-------|-------------------------|------------------------------------------------------------------------------------------------|
|
||||
| Homebrew | macOS | `brew install sing-box` | [][brew] |
|
||||
|
||||
=== ":material-microsoft-windows: Windows"
|
||||
|
||||
| 类型 | 平台 | 链接 | 命令 | 活跃维护 |
|
||||
|------------|---------|--------------------|---------------------------|------------------|
|
||||
| Scoop | Windows | [sing-box][scoop] | `scoop install sing-box` | :material-check: |
|
||||
| Chocolatey | Windows | [sing-box][choco] | `choco install sing-box` | :material-check: |
|
||||
| winget | Windows | [sing-box][winget] | `winget install sing-box` | :material-alert: |
|
||||
| 类型 | 平台 | 链接 | 命令 |
|
||||
|------------|---------|---------------------------|-----------------------------------------------------------------------------------------------------|
|
||||
| Scoop | Windows | `scoop install sing-box` | [][scoop] |
|
||||
| Chocolatey | Windows | `choco install sing-box` | [][choco] |
|
||||
| winget | Windows | `winget install sing-box` | [][winget] |
|
||||
|
||||
=== ":material-android: Android"
|
||||
|
||||
| 类型 | 平台 | 链接 | 命令 | 活跃维护 |
|
||||
|--------|---------|--------------------|--------------------|------------------|
|
||||
| Termux | Android | [sing-box][termux] | `pkg add sing-box` | :material-check: |
|
||||
| 类型 | 平台 | 链接 | 命令 |
|
||||
|--------|---------|--------------------|----------------------------------------------------------------------------------------------|
|
||||
| Termux | Android | `pkg add sing-box` | [][termux] |
|
||||
|
||||
=== ":material-freebsd: FreeBSD"
|
||||
|
||||
| 类型 | 平台 | 链接 | 命令 | 活跃维护 |
|
||||
|------------|---------|-------------------|------------------------|------------------|
|
||||
| FreshPorts | FreeBSD | [sing-box][ports] | `pkg install sing-box` | :material-alert: |
|
||||
| 类型 | 平台 | 链接 | 命令 |
|
||||
|------------|---------|------------------------|--------------------------------------------------------------------------------------------|
|
||||
| FreshPorts | FreeBSD | `pkg install sing-box` | [][ports] |
|
||||
|
||||
## :material-book-multiple: 服务管理
|
||||
|
||||
|
||||
7
docs/installation/tools/sing-box.repo
Normal file
7
docs/installation/tools/sing-box.repo
Normal file
@@ -0,0 +1,7 @@
|
||||
[sing-box]
|
||||
name=sing-box
|
||||
baseurl=https://rpm.sagernet.org/
|
||||
enabled=1
|
||||
repo_gpgcheck=1
|
||||
gpgcheck=1
|
||||
gpgkey=https://sing-box.app/gpg.key
|
||||
38
docs/manual/misc/tunnelvision.md
Normal file
38
docs/manual/misc/tunnelvision.md
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
icon: material/book-lock-open
|
||||
---
|
||||
|
||||
# TunnelVision
|
||||
|
||||
TunnelVision is an attack that uses DHCP option 121 to set higher priority routes
|
||||
so that traffic does not go through the VPN.
|
||||
|
||||
Reference: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-3661
|
||||
|
||||
## Status
|
||||
|
||||
### Android
|
||||
|
||||
Android does not handle DHCP option 121 and is not affected.
|
||||
|
||||
### Apple platforms
|
||||
|
||||
Update [sing-box graphical client](/clients/apple/#download) to `1.9.0-rc.16` or newer,
|
||||
then enable `includeAllNetworks` in `Settings` — `Packet Tunnel` and you will be unaffected.
|
||||
|
||||
Note: when `includeAllNetworks` is enabled, the default TUN stack is changed to `gvisor`,
|
||||
and the `system` and `mixed` stacks are not available.
|
||||
|
||||
### Linux
|
||||
|
||||
Update sing-box to `1.9.0-rc.16` or newer, rules generated by `auto-route` are unaffected.
|
||||
|
||||
### Windows
|
||||
|
||||
No solution yet.
|
||||
|
||||
## Workarounds
|
||||
|
||||
* Don't connect to untrusted networks
|
||||
* Relay untrusted network through another device
|
||||
* Just ignore it
|
||||
@@ -1,208 +0,0 @@
|
||||
---
|
||||
icon: material/alpha-t-box
|
||||
---
|
||||
|
||||
# TUIC
|
||||
|
||||
A recently popular Chinese-made simple protocol based on QUIC, the selling point is the BBR congestion control algorithm.
|
||||
|
||||
!!! warning
|
||||
|
||||
Even though GFW rarely blocks UDP-based proxies, such protocols actually have far more characteristics than TCP based proxies.
|
||||
|
||||
| Specification | Binary Characteristics | Active Detect Hiddenness |
|
||||
|-----------------------------------------------------------|------------------------|--------------------------|
|
||||
| [GitHub](https://github.com/EAimTY/tuic/blob/dev/SPEC.md) | :material-alert: | :material-check: |
|
||||
|
||||
## Password Generator
|
||||
|
||||
| Generated UUID | Generated Password | Action |
|
||||
|------------------------|----------------------------|-----------------------------------------------------------------|
|
||||
| <code id="uuid"><code> | <code id="password"><code> | <button class="md-button" onclick="generate()">Refresh</button> |
|
||||
|
||||
<script>
|
||||
function generateUUID() {
|
||||
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
let r = Math.random() * 16 | 0,
|
||||
v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
document.getElementById("uuid").textContent = uuid;
|
||||
}
|
||||
function generatePassword() {
|
||||
const array = new Uint8Array(16);
|
||||
window.crypto.getRandomValues(array);
|
||||
document.getElementById("password").textContent = btoa(String.fromCharCode.apply(null, array));
|
||||
}
|
||||
function generate() {
|
||||
generateUUID();
|
||||
generatePassword();
|
||||
}
|
||||
generate();
|
||||
</script>
|
||||
|
||||
## :material-server: Server Example
|
||||
|
||||
=== ":material-harddisk: With local certificate"
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tuic",
|
||||
"listen": "::",
|
||||
"listen_port": 8080,
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"uuid": "<uuid>",
|
||||
"password": "<password>"
|
||||
}
|
||||
],
|
||||
"congestion_control": "bbr",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "example.org",
|
||||
"key_path": "/path/to/key.pem",
|
||||
"certificate_path": "/path/to/certificate.pem"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-auto-fix: With ACME"
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tuic",
|
||||
"listen": "::",
|
||||
"listen_port": 8080,
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"uuid": "<uuid>",
|
||||
"password": "<password>"
|
||||
}
|
||||
],
|
||||
"congestion_control": "bbr",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "example.org",
|
||||
"acme": {
|
||||
"domain": "example.org",
|
||||
"email": "admin@example.org"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-cloud: With ACME and Cloudflare API"
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tuic",
|
||||
"listen": "::",
|
||||
"listen_port": 8080,
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"uuid": "<uuid>",
|
||||
"password": "<password>"
|
||||
}
|
||||
],
|
||||
"congestion_control": "bbr",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "example.org",
|
||||
"acme": {
|
||||
"domain": "example.org",
|
||||
"email": "admin@example.org",
|
||||
"dns01_challenge": {
|
||||
"provider": "cloudflare",
|
||||
"api_token": "my_token"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## :material-cellphone-link: Client Example
|
||||
|
||||
=== ":material-web-check: With valid certificate"
|
||||
|
||||
```json
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "tuic",
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8080,
|
||||
"uuid": "<uuid>",
|
||||
"password": "<password>",
|
||||
"congestion_control": "bbr",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "example.org"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-check: With self-sign certificate"
|
||||
|
||||
!!! info "Tip"
|
||||
|
||||
Use `sing-box merge` command to merge configuration and certificate into one file.
|
||||
|
||||
```json
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "tuic",
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8080,
|
||||
"uuid": "<uuid>",
|
||||
"password": "<password>",
|
||||
"congestion_control": "bbr",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "example.org",
|
||||
"certificate_path": "/path/to/certificate.pem"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-alert: Ignore certificate verification"
|
||||
|
||||
```json
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "tuic",
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8080,
|
||||
"uuid": "<uuid>",
|
||||
"password": "<password>",
|
||||
"congestion_control": "bbr",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "example.org",
|
||||
"insecure": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -336,10 +336,10 @@ flowchart TB
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-dns: DNS rules (1.9.0+)"
|
||||
=== ":material-dns: DNS rules (Enhanced, but slower) (1.9.0+)"
|
||||
|
||||
=== ":material-shield-off: With DNS leaks"
|
||||
|
||||
=== ":material-shield-off: With DNS Leaks"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -376,7 +376,17 @@ flowchart TB
|
||||
"server": "google"
|
||||
},
|
||||
{
|
||||
"rule_set": "geoip-cn",
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{
|
||||
"rule_set": "geosite-geolocation-!cn",
|
||||
"invert": true
|
||||
},
|
||||
{
|
||||
"rule_set": "geoip-cn"
|
||||
}
|
||||
],
|
||||
"server": "local"
|
||||
}
|
||||
]
|
||||
@@ -389,6 +399,12 @@ flowchart TB
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-geolocation-!cn",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-!cn.srs"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geoip-cn",
|
||||
@@ -398,14 +414,18 @@ flowchart TB
|
||||
]
|
||||
},
|
||||
"experimental": {
|
||||
"cache_file": {
|
||||
"enabled": true,
|
||||
"store_rdrc": true
|
||||
},
|
||||
"clash_api": {
|
||||
"default_mode": "Leak"
|
||||
"default_mode": "Enhanced"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-security: Without DNS Leaks (1.9.0-alpha.2+)"
|
||||
=== ":material-security: Without DNS leaks, but slower (1.9.0-alpha.2+)"
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -439,9 +459,19 @@ flowchart TB
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"rule_set": "geoip-cn",
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{
|
||||
"rule_set": "geosite-geolocation-!cn",
|
||||
"invert": true
|
||||
},
|
||||
{
|
||||
"rule_set": "geoip-cn"
|
||||
}
|
||||
],
|
||||
"server": "google",
|
||||
"client_subnet": "114.114.114.114" // Any China client IP address
|
||||
"client_subnet": "114.114.114.114/24" // Any China client IP address
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -453,6 +483,12 @@ flowchart TB
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-geolocation-!cn",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-!cn.srs"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geoip-cn",
|
||||
@@ -460,6 +496,15 @@ flowchart TB
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs"
|
||||
}
|
||||
]
|
||||
},
|
||||
"experimental": {
|
||||
"cache_file": {
|
||||
"enabled": true,
|
||||
"store_rdrc": true
|
||||
},
|
||||
"clash_api": {
|
||||
"default_mode": "Enhanced"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -2,12 +2,76 @@
|
||||
icon: material/arrange-bring-forward
|
||||
---
|
||||
|
||||
## 1.10.0
|
||||
|
||||
### TUN address fields are merged
|
||||
|
||||
`inet4_address` and `inet6_address` are merged into `address`,
|
||||
`inet4_route_address` and `inet6_route_address` are merged into `route_address`,
|
||||
`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.
|
||||
|
||||
Old fields are deprecated and will be removed in sing-box 1.11.0.
|
||||
|
||||
!!! info "References"
|
||||
|
||||
[TUN](/configuration/inbound/tun/)
|
||||
|
||||
=== ":material-card-remove: Deprecated"
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||
"inet4_route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1"
|
||||
],
|
||||
"inet6_route_address": [
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-card-multiple: New"
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"address": [
|
||||
"172.19.0.1/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1",
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"route_exclude_address": [
|
||||
"192.168.0.0/16",
|
||||
"fc00::/7"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 1.9.0
|
||||
|
||||
!!! warning "Unstable"
|
||||
|
||||
This version is still under development, and the following migration guide may be changed in the future.
|
||||
|
||||
### `domain_suffix` behavior update
|
||||
|
||||
For historical reasons, sing-box's `domain_suffix` rule matches literal prefixes instead of the same as other projects.
|
||||
|
||||
@@ -2,12 +2,76 @@
|
||||
icon: material/arrange-bring-forward
|
||||
---
|
||||
|
||||
## 1.10.0
|
||||
|
||||
### TUN 地址字段已合并
|
||||
|
||||
`inet4_address` 和 `inet6_address` 已合并为 `address`,
|
||||
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
||||
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
||||
|
||||
旧字段已废弃,且将在 sing-box 1.11.0 中移除。
|
||||
|
||||
!!! info "参考"
|
||||
|
||||
[TUN](/zh/configuration/inbound/tun/)
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||
"inet4_route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1"
|
||||
],
|
||||
"inet6_route_address": [
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"address": [
|
||||
"172.19.0.1/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1",
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"route_exclude_address": [
|
||||
"192.168.0.0/16",
|
||||
"fc00::/7"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 1.9.0
|
||||
|
||||
!!! warning "不稳定的"
|
||||
|
||||
该版本仍在开发中,迁移指南可能将在未来更改。
|
||||
|
||||
### `domain_suffix` 行为更新
|
||||
|
||||
由于历史原因,sing-box 的 `domain_suffix` 规则匹配字面前缀,而不与其他项目相同。
|
||||
|
||||
@@ -57,6 +57,7 @@ type CacheFile struct {
|
||||
type saveRDRCCacheKey struct {
|
||||
TransportName string
|
||||
QuestionName string
|
||||
QType uint16
|
||||
}
|
||||
|
||||
func New(ctx context.Context, options option.CacheFileOptions) *CacheFile {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/sagernet/bbolt"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
@@ -58,12 +59,13 @@ func (c *CacheFile) FakeIPSaveMetadata(metadata *adapter.FakeIPMetadata) error {
|
||||
}
|
||||
|
||||
func (c *CacheFile) FakeIPSaveMetadataAsync(metadata *adapter.FakeIPMetadata) {
|
||||
if timer := c.saveMetadataTimer; timer != nil {
|
||||
timer.Stop()
|
||||
if c.saveMetadataTimer == nil {
|
||||
c.saveMetadataTimer = time.AfterFunc(C.FakeIPMetadataSaveInterval, func() {
|
||||
_ = c.FakeIPSaveMetadata(metadata)
|
||||
})
|
||||
} else {
|
||||
c.saveMetadataTimer.Reset(C.FakeIPMetadataSaveInterval)
|
||||
}
|
||||
c.saveMetadataTimer = time.AfterFunc(10*time.Second, func() {
|
||||
_ = c.FakeIPSaveMetadata(metadata)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *CacheFile) FakeIPStore(address netip.Addr, domain string) error {
|
||||
@@ -72,6 +74,7 @@ func (c *CacheFile) FakeIPStore(address netip.Addr, domain string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldDomain := bucket.Get(address.AsSlice())
|
||||
err = bucket.Put(address.AsSlice(), []byte(domain))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -84,12 +87,24 @@ func (c *CacheFile) FakeIPStore(address netip.Addr, domain string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if oldDomain != nil {
|
||||
if err := bucket.Delete(oldDomain); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return bucket.Put([]byte(domain), address.AsSlice())
|
||||
})
|
||||
}
|
||||
|
||||
func (c *CacheFile) FakeIPStoreAsync(address netip.Addr, domain string, logger logger.Logger) {
|
||||
c.saveFakeIPAccess.Lock()
|
||||
if oldDomain, loaded := c.saveDomain[address]; loaded {
|
||||
if address.Is4() {
|
||||
delete(c.saveAddress4, oldDomain)
|
||||
} else {
|
||||
delete(c.saveAddress6, oldDomain)
|
||||
}
|
||||
}
|
||||
c.saveDomain[address] = domain
|
||||
if address.Is4() {
|
||||
c.saveAddress4[domain] = address
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
)
|
||||
|
||||
var bucketRDRC = []byte("rdrc")
|
||||
var bucketRDRC = []byte("rdrc2")
|
||||
|
||||
func (c *CacheFile) StoreRDRC() bool {
|
||||
return c.storeRDRC
|
||||
@@ -19,13 +19,17 @@ func (c *CacheFile) RDRCTimeout() time.Duration {
|
||||
return c.rdrcTimeout
|
||||
}
|
||||
|
||||
func (c *CacheFile) LoadRDRC(transportName string, qName string) (rejected bool) {
|
||||
func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) (rejected bool) {
|
||||
c.saveRDRCAccess.RLock()
|
||||
rejected, cached := c.saveRDRC[saveRDRCCacheKey{transportName, qName}]
|
||||
rejected, cached := c.saveRDRC[saveRDRCCacheKey{transportName, qName, qType}]
|
||||
c.saveRDRCAccess.RUnlock()
|
||||
if cached {
|
||||
return
|
||||
}
|
||||
key := buf.Get(2 + len(qName))
|
||||
binary.BigEndian.PutUint16(key, qType)
|
||||
copy(key[2:], qName)
|
||||
defer buf.Put(key)
|
||||
var deleteCache bool
|
||||
err := c.DB.View(func(tx *bbolt.Tx) error {
|
||||
bucket := c.bucket(tx, bucketRDRC)
|
||||
@@ -36,7 +40,7 @@ func (c *CacheFile) LoadRDRC(transportName string, qName string) (rejected bool)
|
||||
if bucket == nil {
|
||||
return nil
|
||||
}
|
||||
content := bucket.Get([]byte(qName))
|
||||
content := bucket.Get(key)
|
||||
if content == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -61,13 +65,13 @@ func (c *CacheFile) LoadRDRC(transportName string, qName string) (rejected bool)
|
||||
if bucket == nil {
|
||||
return nil
|
||||
}
|
||||
return bucket.Delete([]byte(qName))
|
||||
return bucket.Delete(key)
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CacheFile) SaveRDRC(transportName string, qName string) error {
|
||||
func (c *CacheFile) SaveRDRC(transportName string, qName string, qType uint16) error {
|
||||
return c.DB.Batch(func(tx *bbolt.Tx) error {
|
||||
bucket, err := c.createBucket(tx, bucketRDRC)
|
||||
if err != nil {
|
||||
@@ -77,20 +81,24 @@ func (c *CacheFile) SaveRDRC(transportName string, qName string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := buf.Get(2 + len(qName))
|
||||
binary.BigEndian.PutUint16(key, qType)
|
||||
copy(key[2:], qName)
|
||||
defer buf.Put(key)
|
||||
expiresAt := buf.Get(8)
|
||||
defer buf.Put(expiresAt)
|
||||
binary.BigEndian.PutUint64(expiresAt, uint64(time.Now().Add(c.rdrcTimeout).Unix()))
|
||||
return bucket.Put([]byte(qName), expiresAt)
|
||||
return bucket.Put(key, expiresAt)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *CacheFile) SaveRDRCAsync(transportName string, qName string, logger logger.Logger) {
|
||||
saveKey := saveRDRCCacheKey{transportName, qName}
|
||||
func (c *CacheFile) SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger) {
|
||||
saveKey := saveRDRCCacheKey{transportName, qName, qType}
|
||||
c.saveRDRCAccess.Lock()
|
||||
c.saveRDRC[saveKey] = true
|
||||
c.saveRDRCAccess.Unlock()
|
||||
go func() {
|
||||
err := c.SaveRDRC(transportName, qName)
|
||||
err := c.SaveRDRC(transportName, qName, qType)
|
||||
if err != nil {
|
||||
logger.Warn("save RDRC: ", err)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
func connectionRouter(router adapter.Router, trafficManager *trafficontrol.Manager) http.Handler {
|
||||
@@ -76,10 +77,10 @@ func getConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseW
|
||||
|
||||
func closeConnection(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
id := uuid.FromStringOrNil(chi.URLParam(r, "id"))
|
||||
snapshot := trafficManager.Snapshot()
|
||||
for _, c := range snapshot.Connections {
|
||||
if id == c.ID() {
|
||||
if id == c.Metadata().ID {
|
||||
c.Close()
|
||||
break
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/service"
|
||||
@@ -218,58 +217,15 @@ func (s *Server) TrafficManager() *trafficontrol.Manager {
|
||||
}
|
||||
|
||||
func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) {
|
||||
tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule)
|
||||
tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.router, matchedRule)
|
||||
return tracker, tracker
|
||||
}
|
||||
|
||||
func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule) (N.PacketConn, adapter.Tracker) {
|
||||
tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule)
|
||||
tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.router, matchedRule)
|
||||
return tracker, tracker
|
||||
}
|
||||
|
||||
func castMetadata(metadata adapter.InboundContext) trafficontrol.Metadata {
|
||||
var inbound string
|
||||
if metadata.Inbound != "" {
|
||||
inbound = metadata.InboundType + "/" + metadata.Inbound
|
||||
} else {
|
||||
inbound = metadata.InboundType
|
||||
}
|
||||
var domain string
|
||||
if metadata.Domain != "" {
|
||||
domain = metadata.Domain
|
||||
} else {
|
||||
domain = metadata.Destination.Fqdn
|
||||
}
|
||||
var processPath string
|
||||
if metadata.ProcessInfo != nil {
|
||||
if metadata.ProcessInfo.ProcessPath != "" {
|
||||
processPath = metadata.ProcessInfo.ProcessPath
|
||||
} else if metadata.ProcessInfo.PackageName != "" {
|
||||
processPath = metadata.ProcessInfo.PackageName
|
||||
}
|
||||
if processPath == "" {
|
||||
if metadata.ProcessInfo.UserId != -1 {
|
||||
processPath = F.ToString(metadata.ProcessInfo.UserId)
|
||||
}
|
||||
} else if metadata.ProcessInfo.User != "" {
|
||||
processPath = F.ToString(processPath, " (", metadata.ProcessInfo.User, ")")
|
||||
} else if metadata.ProcessInfo.UserId != -1 {
|
||||
processPath = F.ToString(processPath, " (", metadata.ProcessInfo.UserId, ")")
|
||||
}
|
||||
}
|
||||
return trafficontrol.Metadata{
|
||||
NetWork: metadata.Network,
|
||||
Type: inbound,
|
||||
SrcIP: metadata.Source.Addr,
|
||||
DstIP: metadata.Destination.Addr,
|
||||
SrcPort: F.ToString(metadata.Source.Port),
|
||||
DstPort: F.ToString(metadata.Destination.Port),
|
||||
Host: domain,
|
||||
DNSMode: "normal",
|
||||
ProcessPath: processPath,
|
||||
}
|
||||
}
|
||||
|
||||
func authentication(serverSecret string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -2,10 +2,17 @@ package trafficontrol
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/compatible"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
@@ -16,9 +23,11 @@ type Manager struct {
|
||||
uploadTotal atomic.Int64
|
||||
downloadTotal atomic.Int64
|
||||
|
||||
connections compatible.Map[string, tracker]
|
||||
ticker *time.Ticker
|
||||
done chan struct{}
|
||||
connections compatible.Map[uuid.UUID, Tracker]
|
||||
closedConnectionsAccess sync.Mutex
|
||||
closedConnections list.List[TrackerMetadata]
|
||||
ticker *time.Ticker
|
||||
done chan struct{}
|
||||
// process *process.Process
|
||||
memory uint64
|
||||
}
|
||||
@@ -33,12 +42,22 @@ func NewManager() *Manager {
|
||||
return manager
|
||||
}
|
||||
|
||||
func (m *Manager) Join(c tracker) {
|
||||
m.connections.Store(c.ID(), c)
|
||||
func (m *Manager) Join(c Tracker) {
|
||||
m.connections.Store(c.Metadata().ID, c)
|
||||
}
|
||||
|
||||
func (m *Manager) Leave(c tracker) {
|
||||
m.connections.Delete(c.ID())
|
||||
func (m *Manager) Leave(c Tracker) {
|
||||
metadata := c.Metadata()
|
||||
_, loaded := m.connections.LoadAndDelete(metadata.ID)
|
||||
if loaded {
|
||||
metadata.ClosedAt = time.Now()
|
||||
m.closedConnectionsAccess.Lock()
|
||||
defer m.closedConnectionsAccess.Unlock()
|
||||
if m.closedConnections.Len() >= 1000 {
|
||||
m.closedConnections.PopFront()
|
||||
}
|
||||
m.closedConnections.PushBack(metadata)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) PushUploaded(size int64) {
|
||||
@@ -59,14 +78,39 @@ func (m *Manager) Total() (up int64, down int64) {
|
||||
return m.uploadTotal.Load(), m.downloadTotal.Load()
|
||||
}
|
||||
|
||||
func (m *Manager) Connections() int {
|
||||
func (m *Manager) ConnectionsLen() int {
|
||||
return m.connections.Len()
|
||||
}
|
||||
|
||||
func (m *Manager) Connections() []TrackerMetadata {
|
||||
var connections []TrackerMetadata
|
||||
m.connections.Range(func(_ uuid.UUID, value Tracker) bool {
|
||||
connections = append(connections, value.Metadata())
|
||||
return true
|
||||
})
|
||||
return connections
|
||||
}
|
||||
|
||||
func (m *Manager) ClosedConnections() []TrackerMetadata {
|
||||
m.closedConnectionsAccess.Lock()
|
||||
defer m.closedConnectionsAccess.Unlock()
|
||||
return m.closedConnections.Array()
|
||||
}
|
||||
|
||||
func (m *Manager) Connection(id uuid.UUID) Tracker {
|
||||
connection, loaded := m.connections.Load(id)
|
||||
if !loaded {
|
||||
return nil
|
||||
}
|
||||
return connection
|
||||
}
|
||||
|
||||
func (m *Manager) Snapshot() *Snapshot {
|
||||
var connections []tracker
|
||||
m.connections.Range(func(_ string, value tracker) bool {
|
||||
connections = append(connections, value)
|
||||
var connections []Tracker
|
||||
m.connections.Range(func(_ uuid.UUID, value Tracker) bool {
|
||||
if value.Metadata().OutboundType != C.TypeDNS {
|
||||
connections = append(connections, value)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
@@ -75,10 +119,10 @@ func (m *Manager) Snapshot() *Snapshot {
|
||||
m.memory = memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased
|
||||
|
||||
return &Snapshot{
|
||||
UploadTotal: m.uploadTotal.Load(),
|
||||
DownloadTotal: m.downloadTotal.Load(),
|
||||
Connections: connections,
|
||||
Memory: m.memory,
|
||||
Upload: m.uploadTotal.Load(),
|
||||
Download: m.downloadTotal.Load(),
|
||||
Connections: connections,
|
||||
Memory: m.memory,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,8 +158,17 @@ func (m *Manager) Close() error {
|
||||
}
|
||||
|
||||
type Snapshot struct {
|
||||
DownloadTotal int64 `json:"downloadTotal"`
|
||||
UploadTotal int64 `json:"uploadTotal"`
|
||||
Connections []tracker `json:"connections"`
|
||||
Memory uint64 `json:"memory"`
|
||||
Download int64
|
||||
Upload int64
|
||||
Connections []Tracker
|
||||
Memory uint64
|
||||
}
|
||||
|
||||
func (s *Snapshot) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"downloadTotal": s.Download,
|
||||
"uploadTotal": s.Upload,
|
||||
"connections": common.Map(s.Connections, func(t Tracker) TrackerMetadata { return t.Metadata() }),
|
||||
"memory": s.Memory,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,97 +2,135 @@ package trafficontrol
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type Metadata struct {
|
||||
NetWork string `json:"network"`
|
||||
Type string `json:"type"`
|
||||
SrcIP netip.Addr `json:"sourceIP"`
|
||||
DstIP netip.Addr `json:"destinationIP"`
|
||||
SrcPort string `json:"sourcePort"`
|
||||
DstPort string `json:"destinationPort"`
|
||||
Host string `json:"host"`
|
||||
DNSMode string `json:"dnsMode"`
|
||||
ProcessPath string `json:"processPath"`
|
||||
type TrackerMetadata struct {
|
||||
ID uuid.UUID
|
||||
Metadata adapter.InboundContext
|
||||
CreatedAt time.Time
|
||||
ClosedAt time.Time
|
||||
Upload *atomic.Int64
|
||||
Download *atomic.Int64
|
||||
Chain []string
|
||||
Rule adapter.Rule
|
||||
Outbound string
|
||||
OutboundType string
|
||||
}
|
||||
|
||||
type tracker interface {
|
||||
ID() string
|
||||
Close() error
|
||||
Leave()
|
||||
}
|
||||
|
||||
type trackerInfo struct {
|
||||
UUID uuid.UUID `json:"id"`
|
||||
Metadata Metadata `json:"metadata"`
|
||||
UploadTotal *atomic.Int64 `json:"upload"`
|
||||
DownloadTotal *atomic.Int64 `json:"download"`
|
||||
Start time.Time `json:"start"`
|
||||
Chain []string `json:"chains"`
|
||||
Rule string `json:"rule"`
|
||||
RulePayload string `json:"rulePayload"`
|
||||
}
|
||||
|
||||
func (t trackerInfo) MarshalJSON() ([]byte, error) {
|
||||
func (t TrackerMetadata) MarshalJSON() ([]byte, error) {
|
||||
var inbound string
|
||||
if t.Metadata.Inbound != "" {
|
||||
inbound = t.Metadata.InboundType + "/" + t.Metadata.Inbound
|
||||
} else {
|
||||
inbound = t.Metadata.InboundType
|
||||
}
|
||||
var domain string
|
||||
if t.Metadata.Domain != "" {
|
||||
domain = t.Metadata.Domain
|
||||
} else {
|
||||
domain = t.Metadata.Destination.Fqdn
|
||||
}
|
||||
var processPath string
|
||||
if t.Metadata.ProcessInfo != nil {
|
||||
if t.Metadata.ProcessInfo.ProcessPath != "" {
|
||||
processPath = t.Metadata.ProcessInfo.ProcessPath
|
||||
} else if t.Metadata.ProcessInfo.PackageName != "" {
|
||||
processPath = t.Metadata.ProcessInfo.PackageName
|
||||
}
|
||||
if processPath == "" {
|
||||
if t.Metadata.ProcessInfo.UserId != -1 {
|
||||
processPath = F.ToString(t.Metadata.ProcessInfo.UserId)
|
||||
}
|
||||
} else if t.Metadata.ProcessInfo.User != "" {
|
||||
processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.User, ")")
|
||||
} else if t.Metadata.ProcessInfo.UserId != -1 {
|
||||
processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.UserId, ")")
|
||||
}
|
||||
}
|
||||
var rule string
|
||||
if t.Rule != nil {
|
||||
rule = F.ToString(t.Rule, " => ", t.Rule.Outbound())
|
||||
} else {
|
||||
rule = "final"
|
||||
}
|
||||
return json.Marshal(map[string]any{
|
||||
"id": t.UUID.String(),
|
||||
"metadata": t.Metadata,
|
||||
"upload": t.UploadTotal.Load(),
|
||||
"download": t.DownloadTotal.Load(),
|
||||
"start": t.Start,
|
||||
"id": t.ID,
|
||||
"metadata": map[string]any{
|
||||
"network": t.Metadata.Network,
|
||||
"type": inbound,
|
||||
"sourceIP": t.Metadata.Source.Addr,
|
||||
"destinationIP": t.Metadata.Destination.Addr,
|
||||
"sourcePort": F.ToString(t.Metadata.Source.Port),
|
||||
"destinationPort": F.ToString(t.Metadata.Destination.Port),
|
||||
"host": domain,
|
||||
"dnsMode": "normal",
|
||||
"processPath": processPath,
|
||||
},
|
||||
"upload": t.Upload.Load(),
|
||||
"download": t.Download.Load(),
|
||||
"start": t.CreatedAt,
|
||||
"chains": t.Chain,
|
||||
"rule": t.Rule,
|
||||
"rulePayload": t.RulePayload,
|
||||
"rule": rule,
|
||||
"rulePayload": "",
|
||||
})
|
||||
}
|
||||
|
||||
type tcpTracker struct {
|
||||
N.ExtendedConn `json:"-"`
|
||||
*trackerInfo
|
||||
manager *Manager
|
||||
type Tracker interface {
|
||||
adapter.Tracker
|
||||
Metadata() TrackerMetadata
|
||||
Close() error
|
||||
}
|
||||
|
||||
func (tt *tcpTracker) ID() string {
|
||||
return tt.UUID.String()
|
||||
type TCPConn struct {
|
||||
N.ExtendedConn
|
||||
metadata TrackerMetadata
|
||||
manager *Manager
|
||||
}
|
||||
|
||||
func (tt *tcpTracker) Close() error {
|
||||
func (tt *TCPConn) Metadata() TrackerMetadata {
|
||||
return tt.metadata
|
||||
}
|
||||
|
||||
func (tt *TCPConn) Close() error {
|
||||
tt.manager.Leave(tt)
|
||||
return tt.ExtendedConn.Close()
|
||||
}
|
||||
|
||||
func (tt *tcpTracker) Leave() {
|
||||
func (tt *TCPConn) Leave() {
|
||||
tt.manager.Leave(tt)
|
||||
}
|
||||
|
||||
func (tt *tcpTracker) Upstream() any {
|
||||
func (tt *TCPConn) Upstream() any {
|
||||
return tt.ExtendedConn
|
||||
}
|
||||
|
||||
func (tt *tcpTracker) ReaderReplaceable() bool {
|
||||
func (tt *TCPConn) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (tt *tcpTracker) WriterReplaceable() bool {
|
||||
func (tt *TCPConn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule) *tcpTracker {
|
||||
uuid, _ := uuid.NewV4()
|
||||
|
||||
var chain []string
|
||||
var next string
|
||||
func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, router adapter.Router, rule adapter.Rule) *TCPConn {
|
||||
id, _ := uuid.NewV4()
|
||||
var (
|
||||
chain []string
|
||||
next string
|
||||
outbound string
|
||||
outboundType string
|
||||
)
|
||||
if rule == nil {
|
||||
if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil {
|
||||
next = defaultOutbound.Tag()
|
||||
@@ -106,17 +144,17 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router ad
|
||||
if !loaded {
|
||||
break
|
||||
}
|
||||
outbound = detour.Tag()
|
||||
outboundType = detour.Type()
|
||||
group, isGroup := detour.(adapter.OutboundGroup)
|
||||
if !isGroup {
|
||||
break
|
||||
}
|
||||
next = group.Now()
|
||||
}
|
||||
|
||||
upload := new(atomic.Int64)
|
||||
download := new(atomic.Int64)
|
||||
|
||||
t := &tcpTracker{
|
||||
tracker := &TCPConn{
|
||||
ExtendedConn: bufio.NewCounterConn(conn, []N.CountFunc{func(n int64) {
|
||||
upload.Add(n)
|
||||
manager.PushUploaded(n)
|
||||
@@ -124,64 +162,62 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router ad
|
||||
download.Add(n)
|
||||
manager.PushDownloaded(n)
|
||||
}}),
|
||||
manager: manager,
|
||||
trackerInfo: &trackerInfo{
|
||||
UUID: uuid,
|
||||
Start: time.Now(),
|
||||
Metadata: metadata,
|
||||
Chain: common.Reverse(chain),
|
||||
Rule: "",
|
||||
UploadTotal: upload,
|
||||
DownloadTotal: download,
|
||||
metadata: TrackerMetadata{
|
||||
ID: id,
|
||||
Metadata: metadata,
|
||||
CreatedAt: time.Now(),
|
||||
Upload: upload,
|
||||
Download: download,
|
||||
Chain: common.Reverse(chain),
|
||||
Rule: rule,
|
||||
Outbound: outbound,
|
||||
OutboundType: outboundType,
|
||||
},
|
||||
manager: manager,
|
||||
}
|
||||
|
||||
if rule != nil {
|
||||
t.trackerInfo.Rule = rule.String() + " => " + rule.Outbound()
|
||||
} else {
|
||||
t.trackerInfo.Rule = "final"
|
||||
}
|
||||
|
||||
manager.Join(t)
|
||||
return t
|
||||
manager.Join(tracker)
|
||||
return tracker
|
||||
}
|
||||
|
||||
type udpTracker struct {
|
||||
type UDPConn struct {
|
||||
N.PacketConn `json:"-"`
|
||||
*trackerInfo
|
||||
manager *Manager
|
||||
metadata TrackerMetadata
|
||||
manager *Manager
|
||||
}
|
||||
|
||||
func (ut *udpTracker) ID() string {
|
||||
return ut.UUID.String()
|
||||
func (ut *UDPConn) Metadata() TrackerMetadata {
|
||||
return ut.metadata
|
||||
}
|
||||
|
||||
func (ut *udpTracker) Close() error {
|
||||
func (ut *UDPConn) Close() error {
|
||||
ut.manager.Leave(ut)
|
||||
return ut.PacketConn.Close()
|
||||
}
|
||||
|
||||
func (ut *udpTracker) Leave() {
|
||||
func (ut *UDPConn) Leave() {
|
||||
ut.manager.Leave(ut)
|
||||
}
|
||||
|
||||
func (ut *udpTracker) Upstream() any {
|
||||
func (ut *UDPConn) Upstream() any {
|
||||
return ut.PacketConn
|
||||
}
|
||||
|
||||
func (ut *udpTracker) ReaderReplaceable() bool {
|
||||
func (ut *UDPConn) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (ut *udpTracker) WriterReplaceable() bool {
|
||||
func (ut *UDPConn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule) *udpTracker {
|
||||
uuid, _ := uuid.NewV4()
|
||||
|
||||
var chain []string
|
||||
var next string
|
||||
func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, router adapter.Router, rule adapter.Rule) *UDPConn {
|
||||
id, _ := uuid.NewV4()
|
||||
var (
|
||||
chain []string
|
||||
next string
|
||||
outbound string
|
||||
outboundType string
|
||||
)
|
||||
if rule == nil {
|
||||
if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil {
|
||||
next = defaultOutbound.Tag()
|
||||
@@ -195,17 +231,17 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, route
|
||||
if !loaded {
|
||||
break
|
||||
}
|
||||
outbound = detour.Tag()
|
||||
outboundType = detour.Type()
|
||||
group, isGroup := detour.(adapter.OutboundGroup)
|
||||
if !isGroup {
|
||||
break
|
||||
}
|
||||
next = group.Now()
|
||||
}
|
||||
|
||||
upload := new(atomic.Int64)
|
||||
download := new(atomic.Int64)
|
||||
|
||||
ut := &udpTracker{
|
||||
trackerConn := &UDPConn{
|
||||
PacketConn: bufio.NewCounterPacketConn(conn, []N.CountFunc{func(n int64) {
|
||||
upload.Add(n)
|
||||
manager.PushUploaded(n)
|
||||
@@ -213,24 +249,19 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, route
|
||||
download.Add(n)
|
||||
manager.PushDownloaded(n)
|
||||
}}),
|
||||
manager: manager,
|
||||
trackerInfo: &trackerInfo{
|
||||
UUID: uuid,
|
||||
Start: time.Now(),
|
||||
Metadata: metadata,
|
||||
Chain: common.Reverse(chain),
|
||||
Rule: "",
|
||||
UploadTotal: upload,
|
||||
DownloadTotal: download,
|
||||
metadata: TrackerMetadata{
|
||||
ID: id,
|
||||
Metadata: metadata,
|
||||
CreatedAt: time.Now(),
|
||||
Upload: upload,
|
||||
Download: download,
|
||||
Chain: common.Reverse(chain),
|
||||
Rule: rule,
|
||||
Outbound: outbound,
|
||||
OutboundType: outboundType,
|
||||
},
|
||||
manager: manager,
|
||||
}
|
||||
|
||||
if rule != nil {
|
||||
ut.trackerInfo.Rule = rule.String() + " => " + rule.Outbound()
|
||||
} else {
|
||||
ut.trackerInfo.Rule = "final"
|
||||
}
|
||||
|
||||
manager.Join(ut)
|
||||
return ut
|
||||
manager.Join(trackerConn)
|
||||
return trackerConn
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ const (
|
||||
CommandLog int32 = iota
|
||||
CommandStatus
|
||||
CommandServiceReload
|
||||
CommandServiceClose
|
||||
CommandCloseConnections
|
||||
CommandGroup
|
||||
CommandSelectOutbound
|
||||
@@ -13,4 +14,6 @@ const (
|
||||
CommandSetClashMode
|
||||
CommandGetSystemProxyStatus
|
||||
CommandSetSystemProxyEnabled
|
||||
CommandConnections
|
||||
CommandCloseConnection
|
||||
)
|
||||
|
||||
@@ -30,7 +30,6 @@ func (c *CommandClient) SetClashMode(newMode string) error {
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleSetClashMode(conn net.Conn) error {
|
||||
defer conn.Close()
|
||||
newMode, err := rw.ReadVString(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -61,7 +60,6 @@ func (c *CommandClient) handleModeConn(conn net.Conn) {
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleModeConn(conn net.Conn) error {
|
||||
defer conn.Close()
|
||||
ctx := connKeepAlive(conn)
|
||||
for s.service == nil {
|
||||
select {
|
||||
@@ -73,7 +71,6 @@ func (s *CommandServer) handleModeConn(conn net.Conn) error {
|
||||
}
|
||||
clashServer := s.service.instance.Router().ClashServer()
|
||||
if clashServer == nil {
|
||||
defer conn.Close()
|
||||
return binary.Write(conn, binary.BigEndian, uint16(0))
|
||||
}
|
||||
err := writeClashModeList(conn, clashServer)
|
||||
|
||||
@@ -25,12 +25,13 @@ type CommandClientOptions struct {
|
||||
type CommandClientHandler interface {
|
||||
Connected()
|
||||
Disconnected(message string)
|
||||
ClearLog()
|
||||
WriteLog(message string)
|
||||
ClearLogs()
|
||||
WriteLogs(messageList StringIterator)
|
||||
WriteStatus(message *StatusMessage)
|
||||
WriteGroups(message OutboundGroupIterator)
|
||||
InitializeClashMode(modeList StringIterator, currentMode string)
|
||||
UpdateClashMode(newMode string)
|
||||
WriteConnections(message *Connections)
|
||||
}
|
||||
|
||||
func NewStandaloneCommandClient() *CommandClient {
|
||||
@@ -83,6 +84,10 @@ func (c *CommandClient) Connect() error {
|
||||
}
|
||||
switch c.options.Command {
|
||||
case CommandLog:
|
||||
err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
|
||||
if err != nil {
|
||||
return E.Cause(err, "write interval")
|
||||
}
|
||||
c.handler.Connected()
|
||||
go c.handleLogConn(conn)
|
||||
case CommandStatus:
|
||||
@@ -116,6 +121,13 @@ func (c *CommandClient) Connect() error {
|
||||
return nil
|
||||
}
|
||||
go c.handleModeConn(conn)
|
||||
case CommandConnections:
|
||||
err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
|
||||
if err != nil {
|
||||
return E.Cause(err, "write interval")
|
||||
}
|
||||
c.handler.Connected()
|
||||
go c.handleConnectionsConn(conn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
53
experimental/libbox/command_close_connection.go
Normal file
53
experimental/libbox/command_close_connection.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||
"github.com/sagernet/sing/common/binary"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
func (c *CommandClient) CloseConnection(connId string) error {
|
||||
conn, err := c.directConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
writer := bufio.NewWriter(conn)
|
||||
err = binary.WriteData(writer, binary.BigEndian, connId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = writer.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return readError(conn)
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleCloseConnection(conn net.Conn) error {
|
||||
reader := bufio.NewReader(conn)
|
||||
var connId string
|
||||
err := binary.ReadData(reader, binary.BigEndian, &connId)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read connection id")
|
||||
}
|
||||
service := s.service
|
||||
if service == nil {
|
||||
return writeError(conn, E.New("service not ready"))
|
||||
}
|
||||
clashServer := service.instance.Router().ClashServer()
|
||||
if clashServer == nil {
|
||||
return writeError(conn, E.New("Clash API disabled"))
|
||||
}
|
||||
targetConn := clashServer.(*clashapi.Server).TrafficManager().Connection(uuid.FromStringOrNil(connId))
|
||||
if targetConn == nil {
|
||||
return writeError(conn, E.New("connection already closed"))
|
||||
}
|
||||
targetConn.Close()
|
||||
return writeError(conn, nil)
|
||||
}
|
||||
268
experimental/libbox/command_connections.go
Normal file
268
experimental/libbox/command_connections.go
Normal file
@@ -0,0 +1,268 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||
"github.com/sagernet/sing/common/binary"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
func (c *CommandClient) handleConnectionsConn(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
reader := bufio.NewReader(conn)
|
||||
var connections Connections
|
||||
for {
|
||||
err := binary.ReadData(reader, binary.BigEndian, &connections.connections)
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
c.handler.WriteConnections(&connections)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleConnectionsConn(conn net.Conn) error {
|
||||
var interval int64
|
||||
err := binary.Read(conn, binary.BigEndian, &interval)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read interval")
|
||||
}
|
||||
ticker := time.NewTicker(time.Duration(interval))
|
||||
defer ticker.Stop()
|
||||
ctx := connKeepAlive(conn)
|
||||
var trafficManager *trafficontrol.Manager
|
||||
for {
|
||||
service := s.service
|
||||
if service != nil {
|
||||
clashServer := service.instance.Router().ClashServer()
|
||||
if clashServer == nil {
|
||||
return E.New("Clash API disabled")
|
||||
}
|
||||
trafficManager = clashServer.(*clashapi.Server).TrafficManager()
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
var (
|
||||
connections = make(map[uuid.UUID]*Connection)
|
||||
outConnections []Connection
|
||||
)
|
||||
writer := bufio.NewWriter(conn)
|
||||
for {
|
||||
outConnections = outConnections[:0]
|
||||
for _, connection := range trafficManager.Connections() {
|
||||
outConnections = append(outConnections, newConnection(connections, connection, false))
|
||||
}
|
||||
for _, connection := range trafficManager.ClosedConnections() {
|
||||
outConnections = append(outConnections, newConnection(connections, connection, true))
|
||||
}
|
||||
err = binary.WriteData(writer, binary.BigEndian, outConnections)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = writer.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
ConnectionStateAll = iota
|
||||
ConnectionStateActive
|
||||
ConnectionStateClosed
|
||||
)
|
||||
|
||||
type Connections struct {
|
||||
connections []Connection
|
||||
filteredConnections []Connection
|
||||
outConnections *[]Connection
|
||||
}
|
||||
|
||||
func (c *Connections) FilterState(state int32) {
|
||||
c.filteredConnections = c.filteredConnections[:0]
|
||||
switch state {
|
||||
case ConnectionStateAll:
|
||||
c.filteredConnections = append(c.filteredConnections, c.connections...)
|
||||
case ConnectionStateActive:
|
||||
for _, connection := range c.connections {
|
||||
if connection.ClosedAt == 0 {
|
||||
c.filteredConnections = append(c.filteredConnections, connection)
|
||||
}
|
||||
}
|
||||
case ConnectionStateClosed:
|
||||
for _, connection := range c.connections {
|
||||
if connection.ClosedAt != 0 {
|
||||
c.filteredConnections = append(c.filteredConnections, connection)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connections) SortByDate() {
|
||||
slices.SortStableFunc(c.filteredConnections, func(x, y Connection) int {
|
||||
if x.CreatedAt < y.CreatedAt {
|
||||
return 1
|
||||
} else if x.CreatedAt > y.CreatedAt {
|
||||
return -1
|
||||
} else {
|
||||
return strings.Compare(y.ID, x.ID)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Connections) SortByTraffic() {
|
||||
slices.SortStableFunc(c.filteredConnections, func(x, y Connection) int {
|
||||
xTraffic := x.Uplink + x.Downlink
|
||||
yTraffic := y.Uplink + y.Downlink
|
||||
if xTraffic < yTraffic {
|
||||
return 1
|
||||
} else if xTraffic > yTraffic {
|
||||
return -1
|
||||
} else {
|
||||
return strings.Compare(y.ID, x.ID)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Connections) SortByTrafficTotal() {
|
||||
slices.SortStableFunc(c.filteredConnections, func(x, y Connection) int {
|
||||
xTraffic := x.UplinkTotal + x.DownlinkTotal
|
||||
yTraffic := y.UplinkTotal + y.DownlinkTotal
|
||||
if xTraffic < yTraffic {
|
||||
return 1
|
||||
} else if xTraffic > yTraffic {
|
||||
return -1
|
||||
} else {
|
||||
return strings.Compare(y.ID, x.ID)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Connections) Iterator() ConnectionIterator {
|
||||
return newPtrIterator(c.filteredConnections)
|
||||
}
|
||||
|
||||
type Connection struct {
|
||||
ID string
|
||||
Inbound string
|
||||
InboundType string
|
||||
IPVersion int32
|
||||
Network string
|
||||
Source string
|
||||
Destination string
|
||||
Domain string
|
||||
Protocol string
|
||||
User string
|
||||
FromOutbound string
|
||||
CreatedAt int64
|
||||
ClosedAt int64
|
||||
Uplink int64
|
||||
Downlink int64
|
||||
UplinkTotal int64
|
||||
DownlinkTotal int64
|
||||
Rule string
|
||||
Outbound string
|
||||
OutboundType string
|
||||
ChainList []string
|
||||
}
|
||||
|
||||
func (c *Connection) Chain() StringIterator {
|
||||
return newIterator(c.ChainList)
|
||||
}
|
||||
|
||||
func (c *Connection) DisplayDestination() string {
|
||||
destination := M.ParseSocksaddr(c.Destination)
|
||||
if destination.IsIP() && c.Domain != "" {
|
||||
destination = M.Socksaddr{
|
||||
Fqdn: c.Domain,
|
||||
Port: destination.Port,
|
||||
}
|
||||
return destination.String()
|
||||
}
|
||||
return c.Destination
|
||||
}
|
||||
|
||||
type ConnectionIterator interface {
|
||||
Next() *Connection
|
||||
HasNext() bool
|
||||
}
|
||||
|
||||
func newConnection(connections map[uuid.UUID]*Connection, metadata trafficontrol.TrackerMetadata, isClosed bool) Connection {
|
||||
if oldConnection, loaded := connections[metadata.ID]; loaded {
|
||||
if isClosed {
|
||||
if oldConnection.ClosedAt == 0 {
|
||||
oldConnection.Uplink = 0
|
||||
oldConnection.Downlink = 0
|
||||
oldConnection.ClosedAt = metadata.ClosedAt.UnixMilli()
|
||||
}
|
||||
return *oldConnection
|
||||
}
|
||||
lastUplink := oldConnection.UplinkTotal
|
||||
lastDownlink := oldConnection.DownlinkTotal
|
||||
uplinkTotal := metadata.Upload.Load()
|
||||
downlinkTotal := metadata.Download.Load()
|
||||
oldConnection.Uplink = uplinkTotal - lastUplink
|
||||
oldConnection.Downlink = downlinkTotal - lastDownlink
|
||||
oldConnection.UplinkTotal = uplinkTotal
|
||||
oldConnection.DownlinkTotal = downlinkTotal
|
||||
return *oldConnection
|
||||
}
|
||||
var rule string
|
||||
if metadata.Rule != nil {
|
||||
rule = metadata.Rule.String()
|
||||
}
|
||||
uplinkTotal := metadata.Upload.Load()
|
||||
downlinkTotal := metadata.Download.Load()
|
||||
uplink := uplinkTotal
|
||||
downlink := downlinkTotal
|
||||
var closedAt int64
|
||||
if !metadata.ClosedAt.IsZero() {
|
||||
closedAt = metadata.ClosedAt.UnixMilli()
|
||||
uplink = 0
|
||||
downlink = 0
|
||||
}
|
||||
connection := Connection{
|
||||
ID: metadata.ID.String(),
|
||||
Inbound: metadata.Metadata.Inbound,
|
||||
InboundType: metadata.Metadata.InboundType,
|
||||
IPVersion: int32(metadata.Metadata.IPVersion),
|
||||
Network: metadata.Metadata.Network,
|
||||
Source: metadata.Metadata.Source.String(),
|
||||
Destination: metadata.Metadata.Destination.String(),
|
||||
Domain: metadata.Metadata.Domain,
|
||||
Protocol: metadata.Metadata.Protocol,
|
||||
User: metadata.Metadata.User,
|
||||
FromOutbound: metadata.Metadata.Outbound,
|
||||
CreatedAt: metadata.CreatedAt.UnixMilli(),
|
||||
ClosedAt: closedAt,
|
||||
Uplink: uplink,
|
||||
Downlink: downlink,
|
||||
UplinkTotal: uplinkTotal,
|
||||
DownlinkTotal: downlinkTotal,
|
||||
Rule: rule,
|
||||
Outbound: metadata.Outbound,
|
||||
OutboundType: metadata.OutboundType,
|
||||
ChainList: metadata.Chain,
|
||||
}
|
||||
connections[metadata.ID] = &connection
|
||||
return connection
|
||||
}
|
||||
@@ -14,6 +14,54 @@ import (
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
func (c *CommandClient) handleGroupConn(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
for {
|
||||
groups, err := readGroups(conn)
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
c.handler.WriteGroups(groups)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleGroupConn(conn net.Conn) error {
|
||||
var interval int64
|
||||
err := binary.Read(conn, binary.BigEndian, &interval)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read interval")
|
||||
}
|
||||
ticker := time.NewTicker(time.Duration(interval))
|
||||
defer ticker.Stop()
|
||||
ctx := connKeepAlive(conn)
|
||||
for {
|
||||
service := s.service
|
||||
if service != nil {
|
||||
err := writeGroups(conn, service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := binary.Write(conn, binary.BigEndian, uint16(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-s.urlTestUpdate:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type OutboundGroup struct {
|
||||
Tag string
|
||||
Type string
|
||||
@@ -44,48 +92,6 @@ type OutboundGroupItemIterator interface {
|
||||
HasNext() bool
|
||||
}
|
||||
|
||||
func (c *CommandClient) handleGroupConn(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
for {
|
||||
groups, err := readGroups(conn)
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
c.handler.WriteGroups(groups)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleGroupConn(conn net.Conn) error {
|
||||
defer conn.Close()
|
||||
ctx := connKeepAlive(conn)
|
||||
for {
|
||||
service := s.service
|
||||
if service != nil {
|
||||
err := writeGroups(conn, service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := binary.Write(conn, binary.BigEndian, uint16(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(2 * time.Second):
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-s.urlTestUpdate:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readGroups(reader io.Reader) (OutboundGroupIterator, error) {
|
||||
var groupLength uint16
|
||||
err := binary.Read(reader, binary.BigEndian, &groupLength)
|
||||
@@ -274,7 +280,6 @@ func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error {
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleSetGroupExpand(conn net.Conn) error {
|
||||
defer conn.Close()
|
||||
groupTag, err := rw.ReadVString(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common/binary"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func (s *CommandServer) WriteMessage(message string) {
|
||||
@@ -17,43 +21,39 @@ func (s *CommandServer) WriteMessage(message string) {
|
||||
s.access.Unlock()
|
||||
}
|
||||
|
||||
func readLog(reader io.Reader) ([]byte, error) {
|
||||
var messageLength uint16
|
||||
err := binary.Read(reader, binary.BigEndian, &messageLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if messageLength == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
data := make([]byte, messageLength)
|
||||
_, err = io.ReadFull(reader, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func writeLog(writer io.Writer, message []byte) error {
|
||||
func writeLog(writer *bufio.Writer, messages []string) error {
|
||||
err := binary.Write(writer, binary.BigEndian, uint8(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = binary.Write(writer, binary.BigEndian, uint16(len(message)))
|
||||
err = binary.WriteData(writer, binary.BigEndian, messages)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(message) > 0 {
|
||||
_, err = writer.Write(message)
|
||||
}
|
||||
return err
|
||||
return writer.Flush()
|
||||
}
|
||||
|
||||
func writeClearLog(writer io.Writer) error {
|
||||
return binary.Write(writer, binary.BigEndian, uint8(1))
|
||||
func writeClearLog(writer *bufio.Writer) error {
|
||||
err := binary.Write(writer, binary.BigEndian, uint8(1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writer.Flush()
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleLogConn(conn net.Conn) error {
|
||||
var (
|
||||
interval int64
|
||||
timer *time.Timer
|
||||
)
|
||||
err := binary.Read(conn, binary.BigEndian, &interval)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read interval")
|
||||
}
|
||||
timer = time.NewTimer(time.Duration(interval))
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
var savedLines []string
|
||||
s.access.Lock()
|
||||
savedLines = make([]string, 0, s.savedLines.Len())
|
||||
@@ -66,52 +66,67 @@ func (s *CommandServer) handleLogConn(conn net.Conn) error {
|
||||
return err
|
||||
}
|
||||
defer s.observer.UnSubscribe(subscription)
|
||||
for _, line := range savedLines {
|
||||
err = writeLog(conn, []byte(line))
|
||||
writer := bufio.NewWriter(conn)
|
||||
if len(savedLines) > 0 {
|
||||
err = writeLog(writer, savedLines)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
ctx := connKeepAlive(conn)
|
||||
var logLines []string
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case message := <-subscription:
|
||||
err = writeLog(conn, []byte(message))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case <-s.logReset:
|
||||
err = writeClearLog(conn)
|
||||
err = writeClearLog(writer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case <-done:
|
||||
return nil
|
||||
case logLine := <-subscription:
|
||||
logLines = logLines[:0]
|
||||
logLines = append(logLines, logLine)
|
||||
timer.Reset(time.Duration(interval))
|
||||
loopLogs:
|
||||
for {
|
||||
select {
|
||||
case logLine = <-subscription:
|
||||
logLines = append(logLines, logLine)
|
||||
case <-timer.C:
|
||||
break loopLogs
|
||||
}
|
||||
}
|
||||
err = writeLog(writer, logLines)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandClient) handleLogConn(conn net.Conn) {
|
||||
reader := bufio.NewReader(conn)
|
||||
for {
|
||||
var messageType uint8
|
||||
err := binary.Read(conn, binary.BigEndian, &messageType)
|
||||
err := binary.Read(reader, binary.BigEndian, &messageType)
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
var message []byte
|
||||
var messages []string
|
||||
switch messageType {
|
||||
case 0:
|
||||
message, err = readLog(conn)
|
||||
err = binary.ReadData(reader, binary.BigEndian, &messages)
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
c.handler.WriteLog(string(message))
|
||||
c.handler.WriteLogs(newIterator(messages))
|
||||
case 1:
|
||||
c.handler.ClearLog()
|
||||
c.handler.ClearLogs()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,7 +135,7 @@ func connKeepAlive(reader io.Reader) context.Context {
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
go func() {
|
||||
for {
|
||||
_, err := readLog(reader)
|
||||
_, err := reader.Read(make([]byte, 1))
|
||||
if err != nil {
|
||||
cancel(err)
|
||||
return
|
||||
|
||||
@@ -44,3 +44,41 @@ func (s *CommandServer) handleServiceReload(conn net.Conn) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CommandClient) ServiceClose() error {
|
||||
conn, err := c.directConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = binary.Write(conn, binary.BigEndian, uint8(CommandServiceClose))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var hasError bool
|
||||
err = binary.Read(conn, binary.BigEndian, &hasError)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if hasError {
|
||||
errorMessage, err := rw.ReadVString(conn)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return E.New(errorMessage)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleServiceClose(conn net.Conn) error {
|
||||
rErr := s.service.Close()
|
||||
s.handler.PostServiceClose()
|
||||
err := binary.Write(conn, binary.BigEndian, rErr != nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rErr != nil {
|
||||
return rw.WriteVString(conn, rErr.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -31,7 +31,6 @@ func (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) erro
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleSelectOutbound(conn net.Conn) error {
|
||||
defer conn.Close()
|
||||
groupTag, err := rw.ReadVString(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -33,10 +33,13 @@ type CommandServer struct {
|
||||
urlTestUpdate chan struct{}
|
||||
modeUpdate chan struct{}
|
||||
logReset chan struct{}
|
||||
|
||||
closedConnections []Connection
|
||||
}
|
||||
|
||||
type CommandServerHandler interface {
|
||||
ServiceReload() error
|
||||
PostServiceClose()
|
||||
GetSystemProxyStatus() *SystemProxyStatus
|
||||
SetSystemProxyEnabled(isEnabled bool) error
|
||||
}
|
||||
@@ -58,16 +61,19 @@ func (s *CommandServer) SetService(newService *BoxService) {
|
||||
if newService != nil {
|
||||
service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate)
|
||||
newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate)
|
||||
s.savedLines.Init()
|
||||
select {
|
||||
case s.logReset <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
s.service = newService
|
||||
s.notifyURLTestUpdate()
|
||||
}
|
||||
|
||||
func (s *CommandServer) ResetLog() {
|
||||
s.savedLines.Init()
|
||||
select {
|
||||
case s.logReset <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CommandServer) notifyURLTestUpdate() {
|
||||
select {
|
||||
case s.urlTestUpdate <- struct{}{}:
|
||||
@@ -152,6 +158,8 @@ func (s *CommandServer) handleConnection(conn net.Conn) error {
|
||||
return s.handleStatusConn(conn)
|
||||
case CommandServiceReload:
|
||||
return s.handleServiceReload(conn)
|
||||
case CommandServiceClose:
|
||||
return s.handleServiceClose(conn)
|
||||
case CommandCloseConnections:
|
||||
return s.handleCloseConnections(conn)
|
||||
case CommandGroup:
|
||||
@@ -170,6 +178,10 @@ func (s *CommandServer) handleConnection(conn net.Conn) error {
|
||||
return s.handleGetSystemProxyStatus(conn)
|
||||
case CommandSetSystemProxyEnabled:
|
||||
return s.handleSetSystemProxyEnabled(conn)
|
||||
case CommandConnections:
|
||||
return s.handleConnectionsConn(conn)
|
||||
case CommandCloseConnection:
|
||||
return s.handleCloseConnection(conn)
|
||||
default:
|
||||
return E.New("unknown command: ", command)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func (s *CommandServer) readStatus() StatusMessage {
|
||||
trafficManager := clashServer.(*clashapi.Server).TrafficManager()
|
||||
message.Uplink, message.Downlink = trafficManager.Now()
|
||||
message.UplinkTotal, message.DownlinkTotal = trafficManager.Total()
|
||||
message.ConnectionsIn = int32(trafficManager.Connections())
|
||||
message.ConnectionsIn = int32(trafficManager.ConnectionsLen())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ func (c *CommandClient) GetSystemProxyStatus() (*SystemProxyStatus, error) {
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleGetSystemProxyStatus(conn net.Conn) error {
|
||||
defer conn.Close()
|
||||
status := s.handler.GetSystemProxyStatus()
|
||||
err := binary.Write(conn, binary.BigEndian, status.Available)
|
||||
if err != nil {
|
||||
@@ -68,7 +67,6 @@ func (c *CommandClient) SetSystemProxyEnabled(isEnabled bool) error {
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleSetSystemProxyEnabled(conn net.Conn) error {
|
||||
defer conn.Close()
|
||||
var isEnabled bool
|
||||
err := binary.Read(conn, binary.BigEndian, &isEnabled)
|
||||
if err != nil {
|
||||
|
||||
@@ -33,7 +33,6 @@ func (c *CommandClient) URLTest(groupTag string) error {
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleURLTest(conn net.Conn) error {
|
||||
defer conn.Close()
|
||||
groupTag, err := rw.ReadVString(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
@@ -75,7 +74,7 @@ func (s *platformInterfaceStub) UsePlatformInterfaceGetter() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) Interfaces() ([]platform.NetworkInterface, error) {
|
||||
func (s *platformInterfaceStub) Interfaces() ([]control.Interface, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
@@ -83,6 +82,10 @@ func (s *platformInterfaceStub) UnderNetworkExtension() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) IncludeAllNetworks() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) ClearDNSCache() {
|
||||
}
|
||||
|
||||
@@ -137,7 +140,6 @@ func FormatConfig(configContent string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
json.NewEncoder(&buffer)
|
||||
encoder := json.NewEncoder(&buffer)
|
||||
encoder.SetIndent("", " ")
|
||||
err = encoder.Encode(options)
|
||||
|
||||
@@ -3,8 +3,9 @@ package libbox
|
||||
import "github.com/sagernet/sing/common"
|
||||
|
||||
type StringIterator interface {
|
||||
Next() string
|
||||
Len() int32
|
||||
HasNext() bool
|
||||
Next() string
|
||||
}
|
||||
|
||||
var _ StringIterator = (*iterator[string])(nil)
|
||||
@@ -17,6 +18,18 @@ func newIterator[T any](values []T) *iterator[T] {
|
||||
return &iterator[T]{values}
|
||||
}
|
||||
|
||||
func newPtrIterator[T any](values []T) *iterator[*T] {
|
||||
return &iterator[*T]{common.Map(values, func(value T) *T { return &value })}
|
||||
}
|
||||
|
||||
func (i *iterator[T]) Len() int32 {
|
||||
return int32(len(i.values))
|
||||
}
|
||||
|
||||
func (i *iterator[T]) HasNext() bool {
|
||||
return len(i.values) > 0
|
||||
}
|
||||
|
||||
func (i *iterator[T]) Next() T {
|
||||
if len(i.values) == 0 {
|
||||
return common.DefaultValue[T]()
|
||||
@@ -26,10 +39,6 @@ func (i *iterator[T]) Next() T {
|
||||
return nextValue
|
||||
}
|
||||
|
||||
func (i *iterator[T]) HasNext() bool {
|
||||
return len(i.values) > 0
|
||||
}
|
||||
|
||||
type abstractIterator[T any] interface {
|
||||
Next() T
|
||||
HasNext() bool
|
||||
|
||||
@@ -19,6 +19,7 @@ type PlatformInterface interface {
|
||||
UsePlatformInterfaceGetter() bool
|
||||
GetInterfaces() (NetworkInterfaceIterator, error)
|
||||
UnderNetworkExtension() bool
|
||||
IncludeAllNetworks() bool
|
||||
ReadWIFIState() *WIFIState
|
||||
ClearDNSCache()
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package platform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
@@ -20,16 +19,10 @@ type Interface interface {
|
||||
UsePlatformDefaultInterfaceMonitor() bool
|
||||
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
|
||||
UsePlatformInterfaceGetter() bool
|
||||
Interfaces() ([]NetworkInterface, error)
|
||||
Interfaces() ([]control.Interface, error)
|
||||
UnderNetworkExtension() bool
|
||||
IncludeAllNetworks() bool
|
||||
ClearDNSCache()
|
||||
ReadWIFIState() adapter.WIFIState
|
||||
process.Searcher
|
||||
}
|
||||
|
||||
type NetworkInterface struct {
|
||||
Index int
|
||||
MTU int
|
||||
Name string
|
||||
Addresses []netip.Prefix
|
||||
}
|
||||
|
||||
@@ -3,14 +3,17 @@ package libbox
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
runtimeDebug "runtime/debug"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
@@ -72,6 +75,16 @@ func (s *BoxService) Start() error {
|
||||
}
|
||||
|
||||
func (s *BoxService) Close() error {
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
go func() {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-time.After(C.FatalStopTimeout):
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
s.cancel()
|
||||
s.urlTestHistoryStorage.Close()
|
||||
return s.instance.Close()
|
||||
@@ -136,6 +149,59 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions
|
||||
return tun.New(*options)
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool {
|
||||
return w.iif.UsePlatformDefaultInterfaceMonitor()
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor {
|
||||
return &platformDefaultInterfaceMonitor{
|
||||
platformInterfaceWrapper: w,
|
||||
defaultInterfaceIndex: -1,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) UsePlatformInterfaceGetter() bool {
|
||||
return w.iif.UsePlatformInterfaceGetter()
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) Interfaces() ([]control.Interface, error) {
|
||||
interfaceIterator, err := w.iif.GetInterfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var interfaces []control.Interface
|
||||
for _, netInterface := range iteratorToArray[*NetworkInterface](interfaceIterator) {
|
||||
interfaces = append(interfaces, control.Interface{
|
||||
Index: int(netInterface.Index),
|
||||
MTU: int(netInterface.MTU),
|
||||
Name: netInterface.Name,
|
||||
Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix),
|
||||
})
|
||||
}
|
||||
return interfaces, nil
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) UnderNetworkExtension() bool {
|
||||
return w.iif.UnderNetworkExtension()
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) IncludeAllNetworks() bool {
|
||||
return w.iif.IncludeAllNetworks()
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) ClearDNSCache() {
|
||||
w.iif.ClearDNSCache()
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState {
|
||||
wifiState := w.iif.ReadWIFIState()
|
||||
if wifiState == nil {
|
||||
return adapter.WIFIState{}
|
||||
}
|
||||
return (adapter.WIFIState)(*wifiState)
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) {
|
||||
var uid int32
|
||||
if w.useProcFS {
|
||||
@@ -163,55 +229,6 @@ func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network
|
||||
return &process.Info{UserId: uid, PackageName: packageName}, nil
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool {
|
||||
return w.iif.UsePlatformDefaultInterfaceMonitor()
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor {
|
||||
return &platformDefaultInterfaceMonitor{
|
||||
platformInterfaceWrapper: w,
|
||||
defaultInterfaceIndex: -1,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) UsePlatformInterfaceGetter() bool {
|
||||
return w.iif.UsePlatformInterfaceGetter()
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) Interfaces() ([]platform.NetworkInterface, error) {
|
||||
interfaceIterator, err := w.iif.GetInterfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var interfaces []platform.NetworkInterface
|
||||
for _, netInterface := range iteratorToArray[*NetworkInterface](interfaceIterator) {
|
||||
interfaces = append(interfaces, platform.NetworkInterface{
|
||||
Index: int(netInterface.Index),
|
||||
MTU: int(netInterface.MTU),
|
||||
Name: netInterface.Name,
|
||||
Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix),
|
||||
})
|
||||
}
|
||||
return interfaces, nil
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) UnderNetworkExtension() bool {
|
||||
return w.iif.UnderNetworkExtension()
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) ClearDNSCache() {
|
||||
w.iif.ClearDNSCache()
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState {
|
||||
wifiState := w.iif.ReadWIFIState()
|
||||
if wifiState == nil {
|
||||
return adapter.WIFIState{}
|
||||
}
|
||||
return (adapter.WIFIState)(*wifiState)
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) DisableColors() bool {
|
||||
return runtime.GOOS != "android"
|
||||
}
|
||||
|
||||
@@ -13,33 +13,21 @@ type servicePauseFields struct {
|
||||
func (s *BoxService) Pause() {
|
||||
s.pauseAccess.Lock()
|
||||
defer s.pauseAccess.Unlock()
|
||||
|
||||
if s.pauseTimer != nil {
|
||||
s.pauseTimer.Stop()
|
||||
}
|
||||
|
||||
s.pauseTimer = time.AfterFunc(time.Minute, s.pause)
|
||||
}
|
||||
|
||||
func (s *BoxService) pause() {
|
||||
s.pauseAccess.Lock()
|
||||
defer s.pauseAccess.Unlock()
|
||||
|
||||
s.pauseManager.DevicePause()
|
||||
_ = s.instance.Router().ResetNetwork()
|
||||
s.pauseTimer = nil
|
||||
s.pauseTimer = time.AfterFunc(3*time.Second, s.ResetNetwork)
|
||||
}
|
||||
|
||||
func (s *BoxService) Wake() {
|
||||
s.pauseAccess.Lock()
|
||||
defer s.pauseAccess.Unlock()
|
||||
|
||||
if s.pauseTimer != nil {
|
||||
s.pauseTimer.Stop()
|
||||
s.pauseTimer = nil
|
||||
return
|
||||
}
|
||||
s.pauseTimer = time.AfterFunc(3*time.Minute, s.ResetNetwork)
|
||||
}
|
||||
|
||||
s.pauseManager.DeviceWake()
|
||||
func (s *BoxService) ResetNetwork() {
|
||||
_ = s.instance.Router().ResetNetwork()
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@ import (
|
||||
"os"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/humanize"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
_ "github.com/sagernet/sing-box/include"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -59,6 +61,10 @@ func FormatMemoryBytes(length int64) string {
|
||||
return humanize.MemoryBytes(uint64(length))
|
||||
}
|
||||
|
||||
func FormatDuration(duration int64) string {
|
||||
return log.FormatDuration(time.Duration(duration) * time.Millisecond)
|
||||
}
|
||||
|
||||
func ProxyDisplayType(proxyType string) string {
|
||||
return C.ProxyDisplayName(proxyType)
|
||||
}
|
||||
|
||||
59
go.mod
59
go.mod
@@ -8,32 +8,32 @@ require (
|
||||
github.com/cloudflare/circl v1.3.7
|
||||
github.com/cretz/bine v0.2.0
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/go-chi/chi/v5 v5.0.11
|
||||
github.com/go-chi/chi/v5 v5.0.12
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/gofrs/uuid/v5 v5.0.0
|
||||
github.com/gofrs/uuid/v5 v5.2.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
|
||||
github.com/libdns/alidns v1.0.3
|
||||
github.com/libdns/cloudflare v0.1.0
|
||||
github.com/libdns/cloudflare v0.1.1
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
github.com/mholt/acmez v1.2.0
|
||||
github.com/miekg/dns v1.1.58
|
||||
github.com/miekg/dns v1.1.59
|
||||
github.com/ooni/go-libtor v1.1.8
|
||||
github.com/oschwald/maxminddb-golang v1.12.0
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
||||
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1
|
||||
github.com/sagernet/gomobile v0.1.1
|
||||
github.com/sagernet/gvisor v0.0.0-20240214044702-a3d61928a32f
|
||||
github.com/sagernet/quic-go v0.41.0-beta.2
|
||||
github.com/sagernet/gomobile v0.1.3
|
||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f
|
||||
github.com/sagernet/quic-go v0.45.0-beta.2
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||
github.com/sagernet/sing v0.3.2-beta.1
|
||||
github.com/sagernet/sing-dns v0.2.0-beta.6
|
||||
github.com/sagernet/sing v0.5.0-alpha.11
|
||||
github.com/sagernet/sing-dns v0.3.0-beta.5
|
||||
github.com/sagernet/sing-mux v0.2.0
|
||||
github.com/sagernet/sing-quic v0.1.9-beta.1
|
||||
github.com/sagernet/sing-quic v0.2.0-beta.9
|
||||
github.com/sagernet/sing-shadowsocks v0.2.6
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0
|
||||
github.com/sagernet/sing-shadowtls v0.1.4
|
||||
github.com/sagernet/sing-tun v0.2.2-beta.3
|
||||
github.com/sagernet/sing-tun v0.4.0-beta.11
|
||||
github.com/sagernet/sing-vmess v0.1.8
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
|
||||
github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6
|
||||
@@ -41,15 +41,15 @@ require (
|
||||
github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
go.uber.org/zap v1.26.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
go.uber.org/zap v1.27.0
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.19.0
|
||||
golang.org/x/net v0.21.0
|
||||
golang.org/x/sys v0.17.0
|
||||
golang.org/x/crypto v0.24.0
|
||||
golang.org/x/net v0.26.0
|
||||
golang.org/x/sys v0.21.0
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||
google.golang.org/grpc v1.60.1
|
||||
google.golang.org/protobuf v1.32.0
|
||||
google.golang.org/grpc v1.63.2
|
||||
google.golang.org/protobuf v1.33.0
|
||||
howett.net/plist v1.0.1
|
||||
)
|
||||
|
||||
@@ -64,34 +64,37 @@ require (
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/libdns/libdns v0.2.1 // indirect
|
||||
github.com/libdns/libdns v0.2.2 // indirect
|
||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.7 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.17.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/blake3 v1.2.1 // indirect
|
||||
|
||||
138
go.sum
138
go.sum
@@ -19,8 +19,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||
github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
|
||||
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
@@ -34,15 +34,13 @@ github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
|
||||
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM=
|
||||
github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||
@@ -65,17 +63,21 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ=
|
||||
github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE=
|
||||
github.com/libdns/cloudflare v0.1.0 h1:93WkJaGaiXCe353LHEP36kAWCUw0YjFqwhkBkU2/iic=
|
||||
github.com/libdns/cloudflare v0.1.0/go.mod h1:a44IP6J1YH6nvcNl1PverfJviADgXUnsozR3a7vBKN8=
|
||||
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=
|
||||
github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU=
|
||||
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
|
||||
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
||||
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
||||
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss=
|
||||
@@ -98,39 +100,35 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
|
||||
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY=
|
||||
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k=
|
||||
github.com/sagernet/gomobile v0.1.1 h1:3vihRGyUfFTToHMeeak0UK6/ldt2MV2bcWKFi2VyECU=
|
||||
github.com/sagernet/gomobile v0.1.1/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E=
|
||||
github.com/sagernet/gvisor v0.0.0-20240214044702-a3d61928a32f h1:7hj/CcCkUiC6gfhX4D+QNyodmhfurW2L2Q4qzJ1bPnI=
|
||||
github.com/sagernet/gvisor v0.0.0-20240214044702-a3d61928a32f/go.mod h1:bLmnT/4M4+yKB6F7JtRsbUr+YJ64yXwFIygjyYDFQzQ=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/quic-go v0.41.0-beta.2 h1:NtFC1Ief+SYJkfRq68D1OEqZQTNh2jYSpyRLhjT+m6U=
|
||||
github.com/sagernet/quic-go v0.41.0-beta.2/go.mod h1:X10Mf9DVHuSEReOLov/XuflD13MVLH3WtppVVFnSItU=
|
||||
github.com/sagernet/gomobile v0.1.3 h1:ohjIb1Ou2+1558PnZour3od69suSuvkdSVOlO1tC4B8=
|
||||
github.com/sagernet/gomobile v0.1.3/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E=
|
||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I=
|
||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0=
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
||||
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||
github.com/sagernet/quic-go v0.45.0-beta.2 h1:nWq9KJTR+cGU8UU4E20XNjdM6QgbLkBgpq+NCExg5RY=
|
||||
github.com/sagernet/quic-go v0.45.0-beta.2/go.mod h1:rs3XCo3SQ2sB96NtaKnEyq+ZkyaKWL51BvIW3veaiWw=
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
||||
github.com/sagernet/sing v0.3.1-beta.1 h1:5s4hUokrd58xgmiT2baXQJmZDUvjf7qzXXIqVbPTa3o=
|
||||
github.com/sagernet/sing v0.3.1-beta.1/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g=
|
||||
github.com/sagernet/sing v0.3.2-beta.1 h1:5NY+aWvZldxeAxr9hE6vt3Y1aktQ4T79q2Lss46ZdBw=
|
||||
github.com/sagernet/sing v0.3.2-beta.1/go.mod h1:qHySJ7u8po9DABtMYEkNBcOumx7ZZJf/fbv2sfTkNHE=
|
||||
github.com/sagernet/sing-dns v0.2.0-beta.4.0.20240214131950-9a328d89cce6 h1:Gfq6FG3XlDHI1hs2f6vmKpjSNfGlkoZKdfitOpf1EgI=
|
||||
github.com/sagernet/sing-dns v0.2.0-beta.4.0.20240214131950-9a328d89cce6/go.mod h1:IxOqfSb6Zt6UVCy8fJpDxb2XxqzHUytNqeOuJfaiLu8=
|
||||
github.com/sagernet/sing-dns v0.2.0-beta.6 h1:e167d8lW4VEagPGh/fPCgdtGGAPFBgY8g/c4WqKRkQk=
|
||||
github.com/sagernet/sing-dns v0.2.0-beta.6/go.mod h1:IxOqfSb6Zt6UVCy8fJpDxb2XxqzHUytNqeOuJfaiLu8=
|
||||
github.com/sagernet/sing v0.5.0-alpha.11 h1:4nR9hv3Thxb16tdMY8eQ3xNqyvqGV2gLCwiTht6TkD8=
|
||||
github.com/sagernet/sing v0.5.0-alpha.11/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-dns v0.3.0-beta.5 h1:lX+wfnBVaOlSd7+GBgb431Tt/gmYwJXSHvS1HutfnD4=
|
||||
github.com/sagernet/sing-dns v0.3.0-beta.5/go.mod h1:qeO/lOUK/c3Zczp5a1VO13fbmolaM8xGKCUXtaX0/NQ=
|
||||
github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo=
|
||||
github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
|
||||
github.com/sagernet/sing-quic v0.1.9-beta.1 h1:rCmgLUq2d4EA643EAvjbfUYYVMPCss0GpmS4pJCT2Lw=
|
||||
github.com/sagernet/sing-quic v0.1.9-beta.1/go.mod h1:F4AXCZiwtRtYdLUTjVMO6elTpA/lLJe17sFlHhHmDVw=
|
||||
github.com/sagernet/sing-quic v0.2.0-beta.9 h1:gfqUwVgKA6APwFOPhwge9VrPZ0XQtmuXF8hbbIVZML8=
|
||||
github.com/sagernet/sing-quic v0.2.0-beta.9/go.mod h1:hb6RwYy9Js0gZi80zlTBWABMOIvJDv46K5yak/pbZ4w=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
|
||||
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
|
||||
github.com/sagernet/sing-tun v0.2.0-beta.3 h1:Qtkz2vLb0Nu7FYQanNxFGEpdb128vP9zimGbwldFWyU=
|
||||
github.com/sagernet/sing-tun v0.2.0-beta.3/go.mod h1:wHVVNUVFJVIWGTdPiSkrF0GVccwNWtY/glSCCqmCFkk=
|
||||
github.com/sagernet/sing-tun v0.2.2-beta.3 h1:LEzxRkn10iicWdZAlIKtc0/G9hgzrt/WMZSkRtsbzCw=
|
||||
github.com/sagernet/sing-tun v0.2.2-beta.3/go.mod h1:TaUYOzyBWK43Si6TadJ4gc7fj2iYZ7kxcp6CzzKey3E=
|
||||
github.com/sagernet/sing-tun v0.4.0-beta.11 h1:BRzcAFLhfysu3gd46CNJqIkgWw3rYDPKgUBbpbDvBfE=
|
||||
github.com/sagernet/sing-tun v0.4.0-beta.11/go.mod h1:YgaSM4cm+YIn6erBZN/eF+sW7I27BfBt91EWGo53MME=
|
||||
github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc=
|
||||
github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
||||
@@ -143,8 +141,6 @@ github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 h1:R0OMYASco
|
||||
github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc=
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
@@ -153,40 +149,40 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
||||
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 h1:/RIbNt/Zr7rVhIkQhooTxCxFcdWLGIKnZA4IXNFSrvo=
|
||||
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -194,31 +190,27 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY=
|
||||
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
|
||||
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
|
||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -11,43 +11,43 @@ import (
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.Inbound, platformInterface platform.Interface) (adapter.Inbound, error) {
|
||||
func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Inbound, platformInterface platform.Interface) (adapter.Inbound, error) {
|
||||
if options.Type == "" {
|
||||
return nil, E.New("missing inbound type")
|
||||
}
|
||||
switch options.Type {
|
||||
case C.TypeTun:
|
||||
return NewTun(ctx, router, logger, options.Tag, options.TunOptions, platformInterface)
|
||||
return NewTun(ctx, router, logger, tag, options.TunOptions, platformInterface)
|
||||
case C.TypeRedirect:
|
||||
return NewRedirect(ctx, router, logger, options.Tag, options.RedirectOptions), nil
|
||||
return NewRedirect(ctx, router, logger, tag, options.RedirectOptions), nil
|
||||
case C.TypeTProxy:
|
||||
return NewTProxy(ctx, router, logger, options.Tag, options.TProxyOptions), nil
|
||||
return NewTProxy(ctx, router, logger, tag, options.TProxyOptions), nil
|
||||
case C.TypeDirect:
|
||||
return NewDirect(ctx, router, logger, options.Tag, options.DirectOptions), nil
|
||||
return NewDirect(ctx, router, logger, tag, options.DirectOptions), nil
|
||||
case C.TypeSOCKS:
|
||||
return NewSocks(ctx, router, logger, options.Tag, options.SocksOptions), nil
|
||||
return NewSocks(ctx, router, logger, tag, options.SocksOptions), nil
|
||||
case C.TypeHTTP:
|
||||
return NewHTTP(ctx, router, logger, options.Tag, options.HTTPOptions)
|
||||
return NewHTTP(ctx, router, logger, tag, options.HTTPOptions)
|
||||
case C.TypeMixed:
|
||||
return NewMixed(ctx, router, logger, options.Tag, options.MixedOptions), nil
|
||||
return NewMixed(ctx, router, logger, tag, options.MixedOptions), nil
|
||||
case C.TypeShadowsocks:
|
||||
return NewShadowsocks(ctx, router, logger, options.Tag, options.ShadowsocksOptions)
|
||||
return NewShadowsocks(ctx, router, logger, tag, options.ShadowsocksOptions)
|
||||
case C.TypeVMess:
|
||||
return NewVMess(ctx, router, logger, options.Tag, options.VMessOptions)
|
||||
return NewVMess(ctx, router, logger, tag, options.VMessOptions)
|
||||
case C.TypeTrojan:
|
||||
return NewTrojan(ctx, router, logger, options.Tag, options.TrojanOptions)
|
||||
return NewTrojan(ctx, router, logger, tag, options.TrojanOptions)
|
||||
case C.TypeNaive:
|
||||
return NewNaive(ctx, router, logger, options.Tag, options.NaiveOptions)
|
||||
return NewNaive(ctx, router, logger, tag, options.NaiveOptions)
|
||||
case C.TypeHysteria:
|
||||
return NewHysteria(ctx, router, logger, options.Tag, options.HysteriaOptions)
|
||||
return NewHysteria(ctx, router, logger, tag, options.HysteriaOptions)
|
||||
case C.TypeShadowTLS:
|
||||
return NewShadowTLS(ctx, router, logger, options.Tag, options.ShadowTLSOptions)
|
||||
return NewShadowTLS(ctx, router, logger, tag, options.ShadowTLSOptions)
|
||||
case C.TypeVLESS:
|
||||
return NewVLESS(ctx, router, logger, options.Tag, options.VLESSOptions)
|
||||
return NewVLESS(ctx, router, logger, tag, options.VLESSOptions)
|
||||
case C.TypeTUIC:
|
||||
return NewTUIC(ctx, router, logger, options.Tag, options.TUICOptions)
|
||||
return NewTUIC(ctx, router, logger, tag, options.TUICOptions)
|
||||
case C.TypeHysteria2:
|
||||
return NewHysteria2(ctx, router, logger, options.Tag, options.Hysteria2Options)
|
||||
return NewHysteria2(ctx, router, logger, tag, options.Hysteria2Options)
|
||||
default:
|
||||
return nil, E.New("unknown inbound type: ", options.Type)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
@@ -16,6 +18,9 @@ func (a *myInboundAdapter) ListenTCP() (net.Listener, error) {
|
||||
bindAddr := M.SocksaddrFrom(a.listenOptions.Listen.Build(), a.listenOptions.ListenPort)
|
||||
var tcpListener net.Listener
|
||||
var listenConfig net.ListenConfig
|
||||
// TODO: Add an option to customize the keep alive period
|
||||
listenConfig.KeepAlive = C.TCPKeepAliveInitial
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.SetKeepAlivePeriod(C.TCPKeepAliveInitial, C.TCPKeepAliveInterval))
|
||||
if a.listenOptions.TCPMultiPath {
|
||||
if !go121Available {
|
||||
return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")
|
||||
|
||||
@@ -116,6 +116,7 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
func (h *Hysteria) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
ctx = log.ContextWithNewID(ctx)
|
||||
metadata = h.createMetadata(conn, metadata)
|
||||
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||
userID, _ := auth.UserFromContext[int](ctx)
|
||||
if userName := h.userNameList[userID]; userName != "" {
|
||||
metadata.User = userName
|
||||
@@ -129,6 +130,7 @@ func (h *Hysteria) newConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
func (h *Hysteria) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
ctx = log.ContextWithNewID(ctx)
|
||||
metadata = h.createPacketMetadata(conn, metadata)
|
||||
h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source)
|
||||
userID, _ := auth.UserFromContext[int](ctx)
|
||||
if userName := h.userNameList[userID]; userName != "" {
|
||||
metadata.User = userName
|
||||
|
||||
@@ -127,6 +127,7 @@ func NewHysteria2(ctx context.Context, router adapter.Router, logger log.Context
|
||||
func (h *Hysteria2) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
ctx = log.ContextWithNewID(ctx)
|
||||
metadata = h.createMetadata(conn, metadata)
|
||||
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||
userID, _ := auth.UserFromContext[int](ctx)
|
||||
if userName := h.userNameList[userID]; userName != "" {
|
||||
metadata.User = userName
|
||||
@@ -140,6 +141,7 @@ func (h *Hysteria2) newConnection(ctx context.Context, conn net.Conn, metadata a
|
||||
func (h *Hysteria2) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
ctx = log.ContextWithNewID(ctx)
|
||||
metadata = h.createPacketMetadata(conn, metadata)
|
||||
h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source)
|
||||
userID, _ := auth.UserFromContext[int](ctx)
|
||||
if userName := h.userNameList[userID]; userName != "" {
|
||||
metadata.User = userName
|
||||
|
||||
@@ -98,6 +98,7 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge
|
||||
func (h *TUIC) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
ctx = log.ContextWithNewID(ctx)
|
||||
metadata = h.createMetadata(conn, metadata)
|
||||
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||
userID, _ := auth.UserFromContext[int](ctx)
|
||||
if userName := h.userNameList[userID]; userName != "" {
|
||||
metadata.User = userName
|
||||
@@ -111,6 +112,7 @@ func (h *TUIC) newConnection(ctx context.Context, conn net.Conn, metadata adapte
|
||||
func (h *TUIC) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
ctx = log.ContextWithNewID(ctx)
|
||||
metadata = h.createPacketMetadata(conn, metadata)
|
||||
h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source)
|
||||
userID, _ := auth.UserFromContext[int](ctx)
|
||||
if userName := h.userNameList[userID]; userName != "" {
|
||||
metadata.User = userName
|
||||
|
||||
256
inbound/tun.go
256
inbound/tun.go
@@ -3,6 +3,9 @@ package inbound
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -19,27 +22,91 @@ import (
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/ranges"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
var _ adapter.Inbound = (*Tun)(nil)
|
||||
|
||||
type Tun struct {
|
||||
tag string
|
||||
ctx context.Context
|
||||
router adapter.Router
|
||||
logger log.ContextLogger
|
||||
inboundOptions option.InboundOptions
|
||||
tunOptions tun.Options
|
||||
endpointIndependentNat bool
|
||||
udpTimeout int64
|
||||
stack string
|
||||
tunIf tun.Tun
|
||||
tunStack tun.Stack
|
||||
platformInterface platform.Interface
|
||||
platformOptions option.TunPlatformOptions
|
||||
tag string
|
||||
ctx context.Context
|
||||
router adapter.Router
|
||||
logger log.ContextLogger
|
||||
inboundOptions option.InboundOptions
|
||||
tunOptions tun.Options
|
||||
endpointIndependentNat bool
|
||||
udpTimeout int64
|
||||
stack string
|
||||
tunIf tun.Tun
|
||||
tunStack tun.Stack
|
||||
platformInterface platform.Interface
|
||||
platformOptions option.TunPlatformOptions
|
||||
autoRedirect tun.AutoRedirect
|
||||
routeRuleSet []adapter.RuleSet
|
||||
routeRuleSetCallback []*list.Element[adapter.RuleSetUpdateCallback]
|
||||
routeExcludeRuleSet []adapter.RuleSet
|
||||
routeExcludeRuleSetCallback []*list.Element[adapter.RuleSetUpdateCallback]
|
||||
routeAddressSet []*netipx.IPSet
|
||||
routeExcludeAddressSet []*netipx.IPSet
|
||||
}
|
||||
|
||||
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
|
||||
address := options.Address
|
||||
//nolint:staticcheck
|
||||
//goland:noinspection GoDeprecation
|
||||
if len(options.Inet4Address) > 0 {
|
||||
address = append(address, options.Inet4Address...)
|
||||
}
|
||||
//nolint:staticcheck
|
||||
//goland:noinspection GoDeprecation
|
||||
if len(options.Inet6Address) > 0 {
|
||||
address = append(address, options.Inet6Address...)
|
||||
}
|
||||
inet4Address := common.Filter(address, func(it netip.Prefix) bool {
|
||||
return it.Addr().Is4()
|
||||
})
|
||||
inet6Address := common.Filter(address, func(it netip.Prefix) bool {
|
||||
return it.Addr().Is6()
|
||||
})
|
||||
|
||||
routeAddress := options.RouteAddress
|
||||
//nolint:staticcheck
|
||||
//goland:noinspection GoDeprecation
|
||||
if len(options.Inet4RouteAddress) > 0 {
|
||||
routeAddress = append(routeAddress, options.Inet4RouteAddress...)
|
||||
}
|
||||
//nolint:staticcheck
|
||||
//goland:noinspection GoDeprecation
|
||||
if len(options.Inet6RouteAddress) > 0 {
|
||||
routeAddress = append(routeAddress, options.Inet6RouteAddress...)
|
||||
}
|
||||
inet4RouteAddress := common.Filter(routeAddress, func(it netip.Prefix) bool {
|
||||
return it.Addr().Is4()
|
||||
})
|
||||
inet6RouteAddress := common.Filter(routeAddress, func(it netip.Prefix) bool {
|
||||
return it.Addr().Is6()
|
||||
})
|
||||
|
||||
routeExcludeAddress := options.RouteExcludeAddress
|
||||
//nolint:staticcheck
|
||||
//goland:noinspection GoDeprecation
|
||||
if len(options.Inet4RouteExcludeAddress) > 0 {
|
||||
routeExcludeAddress = append(routeExcludeAddress, options.Inet4RouteExcludeAddress...)
|
||||
}
|
||||
//nolint:staticcheck
|
||||
//goland:noinspection GoDeprecation
|
||||
if len(options.Inet6RouteExcludeAddress) > 0 {
|
||||
routeExcludeAddress = append(routeExcludeAddress, options.Inet6RouteExcludeAddress...)
|
||||
}
|
||||
inet4RouteExcludeAddress := common.Filter(routeExcludeAddress, func(it netip.Prefix) bool {
|
||||
return it.Addr().Is4()
|
||||
})
|
||||
inet6RouteExcludeAddress := common.Filter(routeExcludeAddress, func(it netip.Prefix) bool {
|
||||
return it.Addr().Is6()
|
||||
})
|
||||
|
||||
tunMTU := options.MTU
|
||||
if tunMTU == 0 {
|
||||
tunMTU = 9000
|
||||
@@ -50,9 +117,9 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
||||
} else {
|
||||
udpTimeout = C.UDPTimeout
|
||||
}
|
||||
var err error
|
||||
includeUID := uidToRange(options.IncludeUID)
|
||||
if len(options.IncludeUIDRange) > 0 {
|
||||
var err error
|
||||
includeUID, err = parseRange(includeUID, options.IncludeUIDRange)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse include_uid_range")
|
||||
@@ -60,13 +127,30 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
||||
}
|
||||
excludeUID := uidToRange(options.ExcludeUID)
|
||||
if len(options.ExcludeUIDRange) > 0 {
|
||||
var err error
|
||||
excludeUID, err = parseRange(excludeUID, options.ExcludeUIDRange)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse exclude_uid_range")
|
||||
}
|
||||
}
|
||||
return &Tun{
|
||||
|
||||
tableIndex := options.IPRoute2TableIndex
|
||||
if tableIndex == 0 {
|
||||
tableIndex = tun.DefaultIPRoute2TableIndex
|
||||
}
|
||||
ruleIndex := options.IPRoute2RuleIndex
|
||||
if ruleIndex == 0 {
|
||||
ruleIndex = tun.DefaultIPRoute2RuleIndex
|
||||
}
|
||||
inputMark := options.AutoRedirectInputMark
|
||||
if inputMark == 0 {
|
||||
inputMark = tun.DefaultAutoRedirectInputMark
|
||||
}
|
||||
outputMark := options.AutoRedirectOutputMark
|
||||
if outputMark == 0 {
|
||||
outputMark = tun.DefaultAutoRedirectOutputMark
|
||||
}
|
||||
|
||||
inbound := &Tun{
|
||||
tag: tag,
|
||||
ctx: ctx,
|
||||
router: router,
|
||||
@@ -76,30 +160,83 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
||||
Name: options.InterfaceName,
|
||||
MTU: tunMTU,
|
||||
GSO: options.GSO,
|
||||
Inet4Address: options.Inet4Address,
|
||||
Inet6Address: options.Inet6Address,
|
||||
Inet4Address: inet4Address,
|
||||
Inet6Address: inet6Address,
|
||||
AutoRoute: options.AutoRoute,
|
||||
IPRoute2TableIndex: tableIndex,
|
||||
IPRoute2RuleIndex: ruleIndex,
|
||||
AutoRedirectInputMark: inputMark,
|
||||
AutoRedirectOutputMark: outputMark,
|
||||
StrictRoute: options.StrictRoute,
|
||||
IncludeInterface: options.IncludeInterface,
|
||||
ExcludeInterface: options.ExcludeInterface,
|
||||
Inet4RouteAddress: options.Inet4RouteAddress,
|
||||
Inet6RouteAddress: options.Inet6RouteAddress,
|
||||
Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress,
|
||||
Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress,
|
||||
Inet4RouteAddress: inet4RouteAddress,
|
||||
Inet6RouteAddress: inet6RouteAddress,
|
||||
Inet4RouteExcludeAddress: inet4RouteExcludeAddress,
|
||||
Inet6RouteExcludeAddress: inet6RouteExcludeAddress,
|
||||
IncludeUID: includeUID,
|
||||
ExcludeUID: excludeUID,
|
||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||
IncludePackage: options.IncludePackage,
|
||||
ExcludePackage: options.ExcludePackage,
|
||||
InterfaceMonitor: router.InterfaceMonitor(),
|
||||
TableIndex: 2022,
|
||||
},
|
||||
endpointIndependentNat: options.EndpointIndependentNat,
|
||||
udpTimeout: int64(udpTimeout.Seconds()),
|
||||
stack: options.Stack,
|
||||
platformInterface: platformInterface,
|
||||
platformOptions: common.PtrValueOrDefault(options.Platform),
|
||||
}, nil
|
||||
}
|
||||
if options.AutoRedirect {
|
||||
if !options.AutoRoute {
|
||||
return nil, E.New("`auto_route` is required by `auto_redirect`")
|
||||
}
|
||||
disableNFTables, dErr := strconv.ParseBool(os.Getenv("DISABLE_NFTABLES"))
|
||||
inbound.autoRedirect, err = tun.NewAutoRedirect(tun.AutoRedirectOptions{
|
||||
TunOptions: &inbound.tunOptions,
|
||||
Context: ctx,
|
||||
Handler: inbound,
|
||||
Logger: logger,
|
||||
NetworkMonitor: router.NetworkMonitor(),
|
||||
InterfaceFinder: router.InterfaceFinder(),
|
||||
TableName: "sing-box",
|
||||
DisableNFTables: dErr == nil && disableNFTables,
|
||||
RouteAddressSet: &inbound.routeAddressSet,
|
||||
RouteExcludeAddressSet: &inbound.routeExcludeAddressSet,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize auto-redirect")
|
||||
}
|
||||
if runtime.GOOS != "android" {
|
||||
var markMode bool
|
||||
for _, routeAddressSet := range options.RouteAddressSet {
|
||||
ruleSet, loaded := router.RuleSet(routeAddressSet)
|
||||
if !loaded {
|
||||
return nil, E.New("parse route_address_set: rule-set not found: ", routeAddressSet)
|
||||
}
|
||||
ruleSet.IncRef()
|
||||
inbound.routeRuleSet = append(inbound.routeRuleSet, ruleSet)
|
||||
markMode = true
|
||||
}
|
||||
for _, routeExcludeAddressSet := range options.RouteExcludeAddressSet {
|
||||
ruleSet, loaded := router.RuleSet(routeExcludeAddressSet)
|
||||
if !loaded {
|
||||
return nil, E.New("parse route_exclude_address_set: rule-set not found: ", routeExcludeAddressSet)
|
||||
}
|
||||
ruleSet.IncRef()
|
||||
inbound.routeExcludeRuleSet = append(inbound.routeExcludeRuleSet, ruleSet)
|
||||
markMode = true
|
||||
}
|
||||
if markMode {
|
||||
inbound.tunOptions.AutoRedirectMarkMode = true
|
||||
err = router.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return inbound, nil
|
||||
}
|
||||
|
||||
func uidToRange(uidList option.Listable[uint32]) []ranges.Range[uint32] {
|
||||
@@ -121,11 +258,11 @@ func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.
|
||||
}
|
||||
var start, end uint64
|
||||
var err error
|
||||
start, err = strconv.ParseUint(uidRange[:subIndex], 10, 32)
|
||||
start, err = strconv.ParseUint(uidRange[:subIndex], 0, 32)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse range start")
|
||||
}
|
||||
end, err = strconv.ParseUint(uidRange[subIndex+1:], 10, 32)
|
||||
end, err = strconv.ParseUint(uidRange[subIndex+1:], 0, 32)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse range end")
|
||||
}
|
||||
@@ -153,7 +290,7 @@ func (t *Tun) Start() error {
|
||||
tunInterface tun.Tun
|
||||
err error
|
||||
)
|
||||
monitor := taskmonitor.New(t.logger, C.DefaultStartTimeout)
|
||||
monitor := taskmonitor.New(t.logger, C.StartTimeout)
|
||||
monitor.Start("open tun interface")
|
||||
if t.platformInterface != nil {
|
||||
tunInterface, err = t.platformInterface.OpenTun(&t.tunOptions, t.platformOptions)
|
||||
@@ -166,6 +303,14 @@ func (t *Tun) Start() error {
|
||||
}
|
||||
t.logger.Trace("creating stack")
|
||||
t.tunIf = tunInterface
|
||||
var (
|
||||
forwarderBindInterface bool
|
||||
includeAllNetworks bool
|
||||
)
|
||||
if t.platformInterface != nil {
|
||||
forwarderBindInterface = true
|
||||
includeAllNetworks = t.platformInterface.IncludeAllNetworks()
|
||||
}
|
||||
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
|
||||
Context: t.ctx,
|
||||
Tun: tunInterface,
|
||||
@@ -174,8 +319,9 @@ func (t *Tun) Start() error {
|
||||
UDPTimeout: t.udpTimeout,
|
||||
Handler: t,
|
||||
Logger: t.logger,
|
||||
ForwarderBindInterface: t.platformInterface != nil,
|
||||
ForwarderBindInterface: forwarderBindInterface,
|
||||
InterfaceFinder: t.router.InterfaceFinder(),
|
||||
IncludeAllNetworks: includeAllNetworks,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -190,10 +336,58 @@ func (t *Tun) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tun) PostStart() error {
|
||||
monitor := taskmonitor.New(t.logger, C.StartTimeout)
|
||||
if t.autoRedirect != nil {
|
||||
t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)
|
||||
for _, routeRuleSet := range t.routeRuleSet {
|
||||
ipSets := routeRuleSet.ExtractIPSet()
|
||||
if len(ipSets) == 0 {
|
||||
t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name())
|
||||
}
|
||||
t.routeAddressSet = append(t.routeAddressSet, ipSets...)
|
||||
}
|
||||
t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
|
||||
for _, routeExcludeRuleSet := range t.routeExcludeRuleSet {
|
||||
ipSets := routeExcludeRuleSet.ExtractIPSet()
|
||||
if len(ipSets) == 0 {
|
||||
t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name())
|
||||
}
|
||||
t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...)
|
||||
}
|
||||
monitor.Start("initiating auto-redirect")
|
||||
err := t.autoRedirect.Start()
|
||||
monitor.Finish()
|
||||
if err != nil {
|
||||
return E.Cause(err, "auto-redirect")
|
||||
}
|
||||
for _, routeRuleSet := range t.routeRuleSet {
|
||||
t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet))
|
||||
routeRuleSet.DecRef()
|
||||
}
|
||||
for _, routeExcludeRuleSet := range t.routeExcludeRuleSet {
|
||||
t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet))
|
||||
routeExcludeRuleSet.DecRef()
|
||||
}
|
||||
t.routeAddressSet = nil
|
||||
t.routeExcludeAddressSet = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tun) updateRouteAddressSet(it adapter.RuleSet) {
|
||||
t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)
|
||||
t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
|
||||
t.autoRedirect.UpdateRouteAddressSet()
|
||||
t.routeAddressSet = nil
|
||||
t.routeExcludeAddressSet = nil
|
||||
}
|
||||
|
||||
func (t *Tun) Close() error {
|
||||
return common.Close(
|
||||
t.tunStack,
|
||||
t.tunIf,
|
||||
t.autoRedirect,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -205,7 +399,11 @@ func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata
|
||||
metadata.Source = upstreamMetadata.Source
|
||||
metadata.Destination = upstreamMetadata.Destination
|
||||
metadata.InboundOptions = t.inboundOptions
|
||||
t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||
if upstreamMetadata.Protocol != "" {
|
||||
t.logger.InfoContext(ctx, "inbound ", upstreamMetadata.Protocol, " connection from ", metadata.Source)
|
||||
} else {
|
||||
t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||
}
|
||||
t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
||||
err := t.router.RouteConnection(ctx, conn, metadata)
|
||||
if err != nil {
|
||||
|
||||
@@ -43,7 +43,7 @@ func (f Formatter) Format(ctx context.Context, level Level, tag string, message
|
||||
id, hasId = IDFromContext(ctx)
|
||||
}
|
||||
if hasId {
|
||||
activeDuration := formatDuration(time.Since(id.CreatedAt))
|
||||
activeDuration := FormatDuration(time.Since(id.CreatedAt))
|
||||
if !f.DisableColors {
|
||||
var color aurora.Color
|
||||
color = aurora.Color(uint8(id.ID))
|
||||
@@ -113,7 +113,7 @@ func (f Formatter) FormatWithSimple(ctx context.Context, level Level, tag string
|
||||
id, hasId = IDFromContext(ctx)
|
||||
}
|
||||
if hasId {
|
||||
activeDuration := formatDuration(time.Since(id.CreatedAt))
|
||||
activeDuration := FormatDuration(time.Since(id.CreatedAt))
|
||||
if !f.DisableColors {
|
||||
var color aurora.Color
|
||||
color = aurora.Color(uint8(id.ID))
|
||||
@@ -163,7 +163,7 @@ func xd(value int, x int) string {
|
||||
return message
|
||||
}
|
||||
|
||||
func formatDuration(duration time.Duration) string {
|
||||
func FormatDuration(duration time.Duration) string {
|
||||
if duration < time.Second {
|
||||
return F.ToString(duration.Milliseconds(), "ms")
|
||||
} else if duration < time.Minute {
|
||||
|
||||
@@ -53,7 +53,9 @@ func NewDefaultFactory(
|
||||
if platformWriter != nil {
|
||||
factory.platformFormatter.DisableColors = platformWriter.DisableColors()
|
||||
}
|
||||
factory.observer = observable.NewObserver[Entry](factory.subscriber, 64)
|
||||
if needObservable {
|
||||
factory.observer = observable.NewObserver[Entry](factory.subscriber, 64)
|
||||
}
|
||||
return factory
|
||||
}
|
||||
|
||||
@@ -72,7 +74,7 @@ func (f *defaultFactory) Start() error {
|
||||
func (f *defaultFactory) Close() error {
|
||||
return common.Close(
|
||||
common.PtrOrNil(f.file),
|
||||
f.observer,
|
||||
f.subscriber,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -66,8 +66,9 @@ nav:
|
||||
- Proxy Protocol:
|
||||
- Shadowsocks: manual/proxy-protocol/shadowsocks.md
|
||||
- Trojan: manual/proxy-protocol/trojan.md
|
||||
- TUIC: manual/proxy-protocol/tuic.md
|
||||
- Hysteria 2: manual/proxy-protocol/hysteria2.md
|
||||
- Misc:
|
||||
- TunnelVision: manual/misc/tunnelvision.md
|
||||
- Configuration:
|
||||
- configuration/index.md
|
||||
- Log:
|
||||
@@ -133,7 +134,6 @@ nav:
|
||||
- WireGuard: configuration/outbound/wireguard.md
|
||||
- Hysteria: configuration/outbound/hysteria.md
|
||||
- ShadowTLS: configuration/outbound/shadowtls.md
|
||||
- ShadowsocksR: configuration/outbound/shadowsocksr.md
|
||||
- VLESS: configuration/outbound/vless.md
|
||||
- TUIC: configuration/outbound/tuic.md
|
||||
- Hysteria2: configuration/outbound/hysteria2.md
|
||||
@@ -236,4 +236,4 @@ plugins:
|
||||
|
||||
Manual: 手册
|
||||
reconfigure_material: true
|
||||
reconfigure_search: true
|
||||
reconfigure_search: true
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user