mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 01:57:18 +10:00
Compare commits
158 Commits
v1.1-beta1
...
v1.2-beta5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd5c2a7999 | ||
|
|
fbc94b9e3e | ||
|
|
e766f25d55 | ||
|
|
140ed9a4cb | ||
|
|
60094884cd | ||
|
|
0e8a4d141a | ||
|
|
17b78a6339 | ||
|
|
e99741159b | ||
|
|
6b9603227b | ||
|
|
23e8d282a3 | ||
|
|
611d6bbfc5 | ||
|
|
f26785c0ba | ||
|
|
5bcfb71737 | ||
|
|
4135c4974f | ||
|
|
222196b182 | ||
|
|
86e55c5c1c | ||
|
|
73c068b96f | ||
|
|
f516026540 | ||
|
|
3c5bc842ed | ||
|
|
d8270a66f4 | ||
|
|
123c383eae | ||
|
|
67814faf92 | ||
|
|
ec4a0c8497 | ||
|
|
21cb227bc2 | ||
|
|
1610bdc5dd | ||
|
|
3296a2f7b2 | ||
|
|
2bd91baad0 | ||
|
|
a624cd9b49 | ||
|
|
02afba132f | ||
|
|
99890a1af0 | ||
|
|
437f1f819c | ||
|
|
92a79e6158 | ||
|
|
c9efd0a74f | ||
|
|
9da349748a | ||
|
|
2423cbbbfe | ||
|
|
4833f6d5db | ||
|
|
9db3cb5cb7 | ||
|
|
c14b353a29 | ||
|
|
19d08b55c8 | ||
|
|
39514b3ca0 | ||
|
|
7ea9d48987 | ||
|
|
df3a982141 | ||
|
|
687b4509df | ||
|
|
41ec2e7944 | ||
|
|
1bd3a9144d | ||
|
|
6e852cc99b | ||
|
|
8320dd0b51 | ||
|
|
960d04d172 | ||
|
|
86ea035bdd | ||
|
|
9b6449dcf4 | ||
|
|
4e22ac1a35 | ||
|
|
8a779f6e94 | ||
|
|
d461768ffb | ||
|
|
5d41e328d4 | ||
|
|
fe492904e9 | ||
|
|
168253b851 | ||
|
|
05620a369e | ||
|
|
8e0fe55363 | ||
|
|
59e521c1db | ||
|
|
f32c149738 | ||
|
|
23a35b3c06 | ||
|
|
044f9c5d4f | ||
|
|
54f9625bdc | ||
|
|
ff0693be32 | ||
|
|
53d9ad93e3 | ||
|
|
f5c5570bec | ||
|
|
53f19a6ead | ||
|
|
cfaf15f429 | ||
|
|
9e67f3b4a5 | ||
|
|
4d2185a2d4 | ||
|
|
33f22263ca | ||
|
|
d09aa07d21 | ||
|
|
8afb8ca7eb | ||
|
|
80ed5bf8fb | ||
|
|
81e7b0b320 | ||
|
|
a828c3b5da | ||
|
|
c95e4a13a1 | ||
|
|
726a7e19eb | ||
|
|
8953ddc6e0 | ||
|
|
7ebbd58b00 | ||
|
|
d0095fd0f4 | ||
|
|
66d8d563eb | ||
|
|
4bf96c7eb5 | ||
|
|
f687c25fa9 | ||
|
|
a92412ecac | ||
|
|
8dcafa5b33 | ||
|
|
7a02cb83a7 | ||
|
|
51ce672076 | ||
|
|
7734afc40c | ||
|
|
ee3cd49aa5 | ||
|
|
bf20ff84b5 | ||
|
|
c58302554c | ||
|
|
05ed88aba8 | ||
|
|
9f5cc0442b | ||
|
|
2641a43ad8 | ||
|
|
4a6ab5e9fd | ||
|
|
d1fe17a4db | ||
|
|
7c910165ef | ||
|
|
8c1fddcf8d | ||
|
|
01b4769852 | ||
|
|
a401828ed5 | ||
|
|
ffd54eef6c | ||
|
|
c16e4316d6 | ||
|
|
8b7fe20b7f | ||
|
|
696c1065b6 | ||
|
|
5d690f4147 | ||
|
|
f906641a82 | ||
|
|
89913dfa8c | ||
|
|
468778f67f | ||
|
|
22a22aebe2 | ||
|
|
a2d2ec9b45 | ||
|
|
2695b3516e | ||
|
|
3a9ef8fac0 | ||
|
|
ebad363201 | ||
|
|
11076d52cd | ||
|
|
5eb132063e | ||
|
|
13ab5d3348 | ||
|
|
ce1ddc400f | ||
|
|
2c9d25e853 | ||
|
|
3d76777760 | ||
|
|
24f4dfea04 | ||
|
|
2fc1a0a9dd | ||
|
|
617aba84e4 | ||
|
|
5510c474c7 | ||
|
|
eb2e8a0b40 | ||
|
|
972491c19d | ||
|
|
7358ca4a52 | ||
|
|
61c274045a | ||
|
|
f205140b04 | ||
|
|
1db8e03c86 | ||
|
|
2ecf86c2bc | ||
|
|
999a847e86 | ||
|
|
1f63ce5dee | ||
|
|
0ad1bbea11 | ||
|
|
b2cd78d279 | ||
|
|
d5bb58a0b4 | ||
|
|
7f84936050 | ||
|
|
6adfea0a72 | ||
|
|
10f213bf3d | ||
|
|
6e8c4f6576 | ||
|
|
9779dc0154 | ||
|
|
a2abe31298 | ||
|
|
930d177dd0 | ||
|
|
f3d1b59173 | ||
|
|
14452f3049 | ||
|
|
4119c8647b | ||
|
|
90a94a8c63 | ||
|
|
b0c39ac7ff | ||
|
|
8703e1ff98 | ||
|
|
35886b88d7 | ||
|
|
d583b35717 | ||
|
|
217ffb2f95 | ||
|
|
22f06f582b | ||
|
|
f2b5098fa0 | ||
|
|
0ca3290364 | ||
|
|
43d5b8598b | ||
|
|
f3e1d1defc | ||
|
|
95c03c9373 |
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -12,7 +12,7 @@ body:
|
||||
required: true
|
||||
- label: Yes, I've searched similar issues on GitHub and didn't find any.
|
||||
required: true
|
||||
- label: Yes, I've included all information below (version, config, log, etc).
|
||||
- label: Yes, I've included all information below (version, **FULL** config, **FULL** log, etc).
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
|
||||
28
.github/renovate.json
vendored
Normal file
28
.github/renovate.json
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"commitMessagePrefix": "[dependencies]",
|
||||
"extends": [
|
||||
"config:base",
|
||||
":disableRateLimiting"
|
||||
],
|
||||
"baseBranches": [
|
||||
"dev-next"
|
||||
],
|
||||
"golang": {
|
||||
"enabled": false
|
||||
},
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": [
|
||||
"github-actions"
|
||||
],
|
||||
"groupName": "github-actions"
|
||||
},
|
||||
{
|
||||
"matchManagers": [
|
||||
"dockerfile"
|
||||
],
|
||||
"groupName": "Dockerfile"
|
||||
}
|
||||
]
|
||||
}
|
||||
53
.github/workflows/debug.yml
vendored
53
.github/workflows/debug.yml
vendored
@@ -3,8 +3,7 @@ name: Debug build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
- main-next
|
||||
- dev-next
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
@@ -12,8 +11,7 @@ on:
|
||||
- '!.github/workflows/debug.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
- main-next
|
||||
- dev-next
|
||||
|
||||
jobs:
|
||||
@@ -21,24 +19,20 @@ jobs:
|
||||
name: Debug build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cancel previous
|
||||
uses: styfle/cancel-workflow-action@0.7.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Get latest go version
|
||||
id: version
|
||||
run: |
|
||||
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
|
||||
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ steps.version.outputs.go_version }}
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
@@ -55,6 +49,26 @@ 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@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18.10
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
key: go118-${{ hashFiles('**/go.sum') }}
|
||||
- name: Run Test
|
||||
run: make
|
||||
cross:
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -128,6 +142,9 @@ jobs:
|
||||
- name: linux-mips64el
|
||||
goos: linux
|
||||
goarch: mips64le
|
||||
- name: linux-s390x
|
||||
goos: linux
|
||||
goarch: s390x
|
||||
# darwin
|
||||
- name: darwin-amd64
|
||||
goos: darwin
|
||||
@@ -168,19 +185,19 @@ jobs:
|
||||
TAGS: with_clash_api,with_quic
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Get latest go version
|
||||
id: version
|
||||
run: |
|
||||
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
|
||||
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ steps.version.outputs.go_version }}
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
@@ -189,7 +206,7 @@ jobs:
|
||||
id: build
|
||||
run: make
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sing-box-${{ matrix.name }}
|
||||
path: sing-box*
|
||||
path: sing-box*
|
||||
|
||||
28
.github/workflows/docker.yml
vendored
28
.github/workflows/docker.yml
vendored
@@ -1,8 +1,5 @@
|
||||
name: Build Docker Images
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
@@ -12,32 +9,37 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Setup QEMU for Docker Buildx
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Docker metadata
|
||||
id: metadata
|
||||
uses: docker/metadata-action@v3
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ghcr.io/sagernet/sing-box
|
||||
- name: Get tag to build
|
||||
id: tag
|
||||
run: |
|
||||
echo "latest=ghcr.io/sagernet/sing-box:latest" >> $GITHUB_OUTPUT
|
||||
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
|
||||
echo ::set-output name=tag::ghcr.io/sagernet/sing-box:${{ github.ref_name }}
|
||||
echo "versioned=ghcr.io/sagernet/sing-box:${{ github.ref_name }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo ::set-output name=tag::ghcr.io/sagernet/sing-box:${{ github.event.inputs.tag }}
|
||||
echo "versioned=ghcr.io/sagernet/sing-box:${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Build and release Docker images
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
platforms: linux/386,linux/amd64
|
||||
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
|
||||
target: dist
|
||||
tags: ${{ steps.tag.outputs.tag }}
|
||||
push: true
|
||||
tags: |
|
||||
${{ steps.tag.outputs.latest }}
|
||||
${{ steps.tag.outputs.versioned }}
|
||||
push: true
|
||||
|
||||
23
.github/workflows/lint.yml
vendored
23
.github/workflows/lint.yml
vendored
@@ -3,45 +3,40 @@ name: Lint
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- main-next
|
||||
- dev-next
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '.github/**'
|
||||
- '!.github/workflows/debug.yml'
|
||||
- '!.github/workflows/lint.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
- main-next
|
||||
- dev-next
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cancel previous
|
||||
uses: styfle/cancel-workflow-action@0.7.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Get latest go version
|
||||
id: version
|
||||
run: |
|
||||
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
|
||||
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ steps.version.outputs.go_version }}
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
key: go-${{ hashFiles('**/go.sum') }}
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
go mod download -x
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
|
||||
10
.github/workflows/mkdocs.yml
vendored
10
.github/workflows/mkdocs.yml
vendored
@@ -10,9 +10,11 @@ jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: pip install mkdocs-material mkdocs-static-i18n
|
||||
- run: mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history
|
||||
- run: |
|
||||
pip install mkdocs-material=="9.*" mkdocs-static-i18n=="0.53"
|
||||
- run: |
|
||||
mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
- uses: actions/stale@v7
|
||||
with:
|
||||
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
||||
days-before-stale: 60
|
||||
|
||||
34
.github/workflows/test.yml
vendored
34
.github/workflows/test.yml
vendored
@@ -1,34 +0,0 @@
|
||||
name: Test build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
- dev-next
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Debug build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Get latest go version
|
||||
id: version
|
||||
run: |
|
||||
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ steps.version.outputs.go_version }}
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
key: go-${{ hashFiles('**/go.sum') }}
|
||||
- name: Run Test
|
||||
run: make test
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -5,4 +5,9 @@
|
||||
/site/
|
||||
/bin/
|
||||
/dist/
|
||||
/sing-box
|
||||
/sing-box
|
||||
/build/
|
||||
/*.jar
|
||||
/*.aar
|
||||
/*.xcframework/
|
||||
.DS_Store
|
||||
|
||||
@@ -3,19 +3,24 @@ linters:
|
||||
enable:
|
||||
- gofumpt
|
||||
- govet
|
||||
# - gci
|
||||
- gci
|
||||
- staticcheck
|
||||
- paralleltest
|
||||
|
||||
run:
|
||||
skip-dirs:
|
||||
- transport/simple-obfs
|
||||
- transport/clashssr
|
||||
- transport/cloudflaretls
|
||||
- transport/shadowtls/tls
|
||||
- transport/shadowtls/tls_go119
|
||||
|
||||
linters-settings:
|
||||
# gci:
|
||||
# sections:
|
||||
# - standard
|
||||
# - prefix(github.com/sagernet/)
|
||||
# - default
|
||||
gci:
|
||||
custom-order: true
|
||||
sections:
|
||||
- standard
|
||||
- prefix(github.com/sagernet/)
|
||||
- default
|
||||
staticcheck:
|
||||
go: '1.19'
|
||||
go: '1.20'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
project_name: sing-box
|
||||
builds:
|
||||
- main: ./cmd/sing-box
|
||||
- id: main
|
||||
main: ./cmd/sing-box
|
||||
flags:
|
||||
- -v
|
||||
- -trimpath
|
||||
@@ -14,17 +15,16 @@ builds:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
- with_wireguard
|
||||
- with_utls
|
||||
- with_clash_api
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
targets:
|
||||
- android_arm64
|
||||
- android_amd64
|
||||
- android_amd64_v3
|
||||
- linux_amd64_v1
|
||||
- linux_amd64_v3
|
||||
- linux_arm64
|
||||
- linux_arm_7
|
||||
- linux_s390x
|
||||
- windows_amd64_v1
|
||||
- windows_amd64_v3
|
||||
- windows_386
|
||||
@@ -33,6 +33,54 @@ builds:
|
||||
- darwin_amd64_v3
|
||||
- darwin_arm64
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
- id: android
|
||||
main: ./cmd/sing-box
|
||||
flags:
|
||||
- -v
|
||||
- -trimpath
|
||||
asmflags:
|
||||
- all=-trimpath={{.Env.GOPATH}}
|
||||
gcflags:
|
||||
- all=-trimpath={{.Env.GOPATH}}
|
||||
ldflags:
|
||||
- -s -w -buildid=
|
||||
tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
- with_wireguard
|
||||
- with_utls
|
||||
- with_clash_api
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
overrides:
|
||||
- goos: android
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
env:
|
||||
- CC=armv7a-linux-androideabi19-clang
|
||||
- CXX=armv7a-linux-androideabi19-clang++
|
||||
- goos: android
|
||||
goarch: arm64
|
||||
env:
|
||||
- CC=aarch64-linux-android21-clang
|
||||
- CXX=aarch64-linux-android21-clang++
|
||||
- goos: android
|
||||
goarch: 386
|
||||
env:
|
||||
- CC=i686-linux-android19-clang
|
||||
- CXX=i686-linux-android19-clang++
|
||||
- goos: android
|
||||
goarch: amd64
|
||||
goamd64: v1
|
||||
env:
|
||||
- CC=x86_64-linux-android21-clang
|
||||
- CXX=x86_64-linux-android21-clang++
|
||||
targets:
|
||||
- android_arm_7
|
||||
- android_arm64
|
||||
- android_386
|
||||
- android_amd64
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
snapshot:
|
||||
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
||||
archives:
|
||||
@@ -68,6 +116,9 @@ nfpms:
|
||||
dst: /etc/systemd/system/sing-box@.service
|
||||
- src: LICENSE
|
||||
dst: /usr/share/licenses/sing-box/LICENSE
|
||||
scripts:
|
||||
postinstall: release/config/postinstall.sh
|
||||
postremove: release/config/postremove.sh
|
||||
source:
|
||||
enabled: false
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.19-alpine AS builder
|
||||
FROM golang:1.20-alpine AS builder
|
||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||
COPY . /go/src/github.com/sagernet/sing-box
|
||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||
@@ -14,7 +14,6 @@ RUN set -ex \
|
||||
./cmd/sing-box
|
||||
FROM alpine AS dist
|
||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||
RUN [ ! -e /etc/nsswitch.conf ] && echo 'hosts: files dns' > /etc/nsswitch.conf
|
||||
RUN set -ex \
|
||||
&& apk upgrade \
|
||||
&& apk add bash tzdata ca-certificates \
|
||||
|
||||
20
Makefile
20
Makefile
@@ -1,7 +1,7 @@
|
||||
NAME = sing-box
|
||||
COMMIT = $(shell git rev-parse --short HEAD)
|
||||
TAGS ?= with_gvisor,with_quic,with_wireguard,with_clash_api
|
||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_shadowsocksr
|
||||
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api
|
||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
|
||||
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid="
|
||||
MAIN = ./cmd/sing-box
|
||||
|
||||
@@ -16,11 +16,11 @@ install:
|
||||
fmt:
|
||||
@gofumpt -l -w .
|
||||
@gofmt -s -w .
|
||||
@gci write -s "standard,prefix(github.com/sagernet/),default" .
|
||||
@gci write --custom-order -s "standard,prefix(github.com/sagernet/),default" .
|
||||
|
||||
fmt_install:
|
||||
go install -v mvdan.cc/gofumpt@latest
|
||||
go install -v github.com/daixiang0/gci@v0.4.0
|
||||
go install -v github.com/daixiang0/gci@latest
|
||||
|
||||
lint:
|
||||
GOOS=linux golangci-lint run ./...
|
||||
@@ -42,14 +42,14 @@ proto_install:
|
||||
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||
|
||||
snapshot:
|
||||
goreleaser release --rm-dist --snapshot
|
||||
go run ./cmd/internal/build goreleaser release --rm-dist --snapshot || exit 1
|
||||
mkdir dist/release
|
||||
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
|
||||
ghr --delete --draft --prerelease -p 1 nightly dist/release
|
||||
rm -r dist
|
||||
|
||||
release:
|
||||
goreleaser release --rm-dist --skip-publish
|
||||
go run ./cmd/internal/build goreleaser release --rm-dist --skip-publish || exit 1
|
||||
mkdir dist/release
|
||||
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
|
||||
ghr --delete --draft --prerelease -p 3 $(shell git describe --tags) dist/release
|
||||
@@ -71,6 +71,14 @@ test_stdio:
|
||||
go mod tidy && \
|
||||
go test -v -tags "$(TAGS_TEST),force_stdio" .
|
||||
|
||||
lib:
|
||||
go run ./cmd/internal/build_libbox
|
||||
|
||||
lib_install:
|
||||
go get -v -d
|
||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20221130124640-349ebaa752ca
|
||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20221130124640-349ebaa752ca
|
||||
|
||||
clean:
|
||||
rm -rf bin dist sing-box
|
||||
rm -f $(shell go env GOPATH)/sing-box
|
||||
|
||||
@@ -45,6 +45,6 @@ type V2RayServer interface {
|
||||
}
|
||||
|
||||
type V2RayStatsService interface {
|
||||
RoutedConnection(inbound string, outbound string, conn net.Conn) net.Conn
|
||||
RoutedPacketConnection(inbound string, outbound string, conn N.PacketConn) N.PacketConn
|
||||
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
|
||||
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
|
||||
}
|
||||
|
||||
@@ -46,6 +46,10 @@ type InboundContext struct {
|
||||
SourceGeoIPCode string
|
||||
GeoIPCode string
|
||||
ProcessInfo *process.Info
|
||||
|
||||
// dns cache
|
||||
|
||||
QueryType uint16
|
||||
}
|
||||
|
||||
type inboundContextKey struct{}
|
||||
|
||||
@@ -34,12 +34,15 @@ type Router interface {
|
||||
InterfaceFinder() control.InterfaceFinder
|
||||
DefaultInterface() string
|
||||
AutoDetectInterface() bool
|
||||
AutoDetectInterfaceFunc() control.Func
|
||||
DefaultMark() int
|
||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||
PackageManager() tun.PackageManager
|
||||
Rules() []Rule
|
||||
|
||||
TimeService
|
||||
|
||||
ClashServer() ClashServer
|
||||
SetClashServer(server ClashServer)
|
||||
|
||||
@@ -47,6 +50,20 @@ type Router interface {
|
||||
SetV2RayServer(server V2RayServer)
|
||||
}
|
||||
|
||||
type routerContextKey struct{}
|
||||
|
||||
func ContextWithRouter(ctx context.Context, router Router) context.Context {
|
||||
return context.WithValue(ctx, (*routerContextKey)(nil), router)
|
||||
}
|
||||
|
||||
func RouterFromContext(ctx context.Context) Router {
|
||||
metadata := ctx.Value((*routerContextKey)(nil))
|
||||
if metadata == nil {
|
||||
return nil
|
||||
}
|
||||
return metadata.(Router)
|
||||
}
|
||||
|
||||
type Rule interface {
|
||||
Service
|
||||
Type() string
|
||||
@@ -60,3 +77,7 @@ type DNSRule interface {
|
||||
Rule
|
||||
DisableCache() bool
|
||||
}
|
||||
|
||||
type InterfaceUpdateListener interface {
|
||||
InterfaceUpdated() error
|
||||
}
|
||||
|
||||
8
adapter/time.go
Normal file
8
adapter/time.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package adapter
|
||||
|
||||
import "time"
|
||||
|
||||
type TimeService interface {
|
||||
Service
|
||||
TimeFunc() func() time.Time
|
||||
}
|
||||
@@ -3,6 +3,10 @@ package adapter
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type V2RayServerTransport interface {
|
||||
@@ -12,6 +16,12 @@ type V2RayServerTransport interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
type V2RayServerTransportHandler interface {
|
||||
N.TCPConnectionHandler
|
||||
E.Handler
|
||||
FallbackConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error
|
||||
}
|
||||
|
||||
type V2RayClientTransport interface {
|
||||
DialContext(ctx context.Context) (net.Conn, error)
|
||||
}
|
||||
|
||||
69
box.go
69
box.go
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental"
|
||||
"github.com/sagernet/sing-box/inbound"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
@@ -53,19 +54,25 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
||||
var logFactory log.Factory
|
||||
var observableLogFactory log.ObservableFactory
|
||||
var logFile *os.File
|
||||
var logWriter io.Writer
|
||||
if logOptions.Disabled {
|
||||
observableLogFactory = log.NewNOPFactory()
|
||||
logFactory = observableLogFactory
|
||||
} else {
|
||||
var logWriter io.Writer
|
||||
switch logOptions.Output {
|
||||
case "", "stderr":
|
||||
case "":
|
||||
if options.PlatformInterface != nil {
|
||||
logWriter = io.Discard
|
||||
} else {
|
||||
logWriter = os.Stdout
|
||||
}
|
||||
case "stderr":
|
||||
logWriter = os.Stderr
|
||||
case "stdout":
|
||||
logWriter = os.Stdout
|
||||
default:
|
||||
var err error
|
||||
logFile, err = os.OpenFile(logOptions.Output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
logFile, err = os.OpenFile(C.BasePath(logOptions.Output), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -79,10 +86,10 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
||||
TimestampFormat: "-0700 2006-01-02 15:04:05",
|
||||
}
|
||||
if needClashAPI {
|
||||
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter)
|
||||
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter, options.PlatformInterface)
|
||||
logFactory = observableLogFactory
|
||||
} else {
|
||||
logFactory = log.NewFactory(logFormatter, logWriter)
|
||||
logFactory = log.NewFactory(logFormatter, logWriter, options.PlatformInterface)
|
||||
}
|
||||
if logOptions.Level != "" {
|
||||
logLevel, err := log.ParseLevel(logOptions.Level)
|
||||
@@ -97,11 +104,12 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
||||
|
||||
router, err := route.NewRouter(
|
||||
ctx,
|
||||
logFactory.NewLogger("router"),
|
||||
logFactory.NewLogger("dns"),
|
||||
logFactory,
|
||||
common.PtrValueOrDefault(options.Route),
|
||||
common.PtrValueOrDefault(options.DNS),
|
||||
common.PtrValueOrDefault(options.NTP),
|
||||
options.Inbounds,
|
||||
options.PlatformInterface,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse route options")
|
||||
@@ -121,6 +129,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||
inboundOptions,
|
||||
options.PlatformInterface,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse inbound[", i, "]")
|
||||
@@ -256,19 +265,43 @@ func (s *Box) Close() error {
|
||||
default:
|
||||
close(s.done)
|
||||
}
|
||||
for _, in := range s.inbounds {
|
||||
in.Close()
|
||||
var errors error
|
||||
for i, in := range s.inbounds {
|
||||
errors = E.Append(errors, in.Close(), func(err error) error {
|
||||
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
|
||||
})
|
||||
}
|
||||
for _, out := range s.outbounds {
|
||||
common.Close(out)
|
||||
for i, out := range s.outbounds {
|
||||
errors = E.Append(errors, common.Close(out), func(err error) error {
|
||||
return E.Cause(err, "close inbound/", out.Type(), "[", i, "]")
|
||||
})
|
||||
}
|
||||
return common.Close(
|
||||
s.router,
|
||||
s.logFactory,
|
||||
s.clashServer,
|
||||
s.v2rayServer,
|
||||
common.PtrOrNil(s.logFile),
|
||||
)
|
||||
if err := common.Close(s.router); err != nil {
|
||||
errors = E.Append(errors, err, func(err error) error {
|
||||
return E.Cause(err, "close router")
|
||||
})
|
||||
}
|
||||
if err := common.Close(s.logFactory); err != nil {
|
||||
errors = E.Append(errors, err, func(err error) error {
|
||||
return E.Cause(err, "close log factory")
|
||||
})
|
||||
}
|
||||
if err := common.Close(s.clashServer); err != nil {
|
||||
errors = E.Append(errors, err, func(err error) error {
|
||||
return E.Cause(err, "close clash api server")
|
||||
})
|
||||
}
|
||||
if err := common.Close(s.v2rayServer); err != nil {
|
||||
errors = E.Append(errors, err, func(err error) error {
|
||||
return E.Cause(err, "close v2ray api server")
|
||||
})
|
||||
}
|
||||
if s.logFile != nil {
|
||||
errors = E.Append(errors, s.logFile.Close(), func(err error) error {
|
||||
return E.Cause(err, "close log file")
|
||||
})
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
func (s *Box) Router() adapter.Router {
|
||||
|
||||
21
cmd/internal/build/main.go
Normal file
21
cmd/internal/build/main.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
build_shared.FindSDK()
|
||||
|
||||
command := exec.Command(os.Args[1], os.Args[2:]...)
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
err := command.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
112
cmd/internal/build_libbox/main.go
Normal file
112
cmd/internal/build_libbox/main.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
_ "github.com/sagernet/gomobile/asset"
|
||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
)
|
||||
|
||||
var (
|
||||
debugEnabled bool
|
||||
target string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
||||
flag.StringVar(&target, "target", "android", "target platform")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
build_shared.FindMobile()
|
||||
|
||||
switch target {
|
||||
case "android":
|
||||
buildAndroid()
|
||||
case "ios":
|
||||
buildiOS()
|
||||
}
|
||||
}
|
||||
|
||||
func buildAndroid() {
|
||||
build_shared.FindSDK()
|
||||
|
||||
args := []string{
|
||||
"bind",
|
||||
"-v",
|
||||
"-androidapi", "21",
|
||||
"-javapkg=io.nekohasekai",
|
||||
"-libname=box",
|
||||
}
|
||||
if !debugEnabled {
|
||||
args = append(args,
|
||||
"-trimpath", "-ldflags=-s -w -buildid=",
|
||||
"-tags", "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api",
|
||||
)
|
||||
} else {
|
||||
args = append(args, "-tags", "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api,debug")
|
||||
}
|
||||
|
||||
args = append(args, "./experimental/libbox")
|
||||
|
||||
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
err := command.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
const name = "libbox.aar"
|
||||
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
|
||||
if rw.FileExists(copyPath) {
|
||||
copyPath, _ = filepath.Abs(copyPath)
|
||||
err = rw.CopyFile(name, filepath.Join(copyPath, name))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Info("copied to ", copyPath)
|
||||
}
|
||||
}
|
||||
|
||||
func buildiOS() {
|
||||
args := []string{
|
||||
"bind",
|
||||
"-v",
|
||||
"-target", "ios,iossimulator,macos",
|
||||
"-libname=box",
|
||||
}
|
||||
if !debugEnabled {
|
||||
args = append(args,
|
||||
"-trimpath", "-ldflags=-s -w -buildid=",
|
||||
)
|
||||
} else {
|
||||
args = append(args, "-tags", "debug")
|
||||
}
|
||||
|
||||
args = append(args, "./experimental/libbox")
|
||||
|
||||
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
err := command.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
copyPath := filepath.Join("..", "sfi")
|
||||
if rw.FileExists(copyPath) {
|
||||
targetDir := filepath.Join(copyPath, "Libbox.xcframework")
|
||||
targetDir, _ = filepath.Abs(targetDir)
|
||||
os.RemoveAll(targetDir)
|
||||
os.Rename("Libbox.xcframework", targetDir)
|
||||
log.Info("copied to ", targetDir)
|
||||
}
|
||||
}
|
||||
92
cmd/internal/build_shared/sdk.go
Normal file
92
cmd/internal/build_shared/sdk.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package build_shared
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
)
|
||||
|
||||
var (
|
||||
androidSDKPath string
|
||||
androidNDKPath string
|
||||
)
|
||||
|
||||
func FindSDK() {
|
||||
searchPath := []string{
|
||||
"$ANDROID_HOME",
|
||||
"$HOME/Android/Sdk",
|
||||
"$HOME/.local/lib/android/sdk",
|
||||
"$HOME/Library/Android/sdk",
|
||||
}
|
||||
for _, path := range searchPath {
|
||||
path = os.ExpandEnv(path)
|
||||
if rw.FileExists(path + "/licenses/android-sdk-license") {
|
||||
androidSDKPath = path
|
||||
break
|
||||
}
|
||||
}
|
||||
if androidSDKPath == "" {
|
||||
log.Fatal("android SDK not found")
|
||||
}
|
||||
if !findNDK() {
|
||||
log.Fatal("android NDK not found")
|
||||
}
|
||||
|
||||
os.Setenv("ANDROID_HOME", androidSDKPath)
|
||||
os.Setenv("ANDROID_SDK_HOME", androidSDKPath)
|
||||
os.Setenv("ANDROID_NDK_HOME", androidNDKPath)
|
||||
os.Setenv("NDK", androidNDKPath)
|
||||
os.Setenv("PATH", os.Getenv("PATH")+":"+filepath.Join(androidNDKPath, "toolchains", "llvm", "prebuilt", runtime.GOOS+"-x86_64", "bin"))
|
||||
}
|
||||
|
||||
func findNDK() bool {
|
||||
if rw.FileExists(androidSDKPath + "/ndk/25.1.8937393") {
|
||||
androidNDKPath = androidSDKPath + "/ndk/25.1.8937393"
|
||||
return true
|
||||
}
|
||||
ndkVersions, err := os.ReadDir(androidSDKPath + "/ndk")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
versionNames := common.Map(ndkVersions, os.DirEntry.Name)
|
||||
if len(versionNames) == 0 {
|
||||
return false
|
||||
}
|
||||
sort.Slice(versionNames, func(i, j int) bool {
|
||||
iVersions := strings.Split(versionNames[i], ".")
|
||||
jVersions := strings.Split(versionNames[j], ".")
|
||||
for k := 0; k < len(iVersions) && k < len(jVersions); k++ {
|
||||
iVersion, _ := strconv.Atoi(iVersions[k])
|
||||
jVersion, _ := strconv.Atoi(jVersions[k])
|
||||
if iVersion != jVersion {
|
||||
return iVersion > jVersion
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
for _, versionName := range versionNames {
|
||||
if rw.FileExists(androidSDKPath + "/ndk/" + versionName) {
|
||||
androidNDKPath = androidSDKPath + "/ndk/" + versionName
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var GoBinPath string
|
||||
|
||||
func FindMobile() {
|
||||
goBin := filepath.Join(build.Default.GOPATH, "bin")
|
||||
if !rw.FileExists(goBin + "/" + "gobind") {
|
||||
log.Fatal("missing gomobile installation")
|
||||
}
|
||||
GoBinPath = goBin
|
||||
}
|
||||
@@ -69,6 +69,20 @@ func create() (*box.Box, context.CancelFunc, error) {
|
||||
cancel()
|
||||
return nil, nil, E.Cause(err, "create service")
|
||||
}
|
||||
|
||||
osSignals := make(chan os.Signal, 1)
|
||||
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
|
||||
defer func() {
|
||||
signal.Stop(osSignals)
|
||||
close(osSignals)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, loaded := <-osSignals
|
||||
if loaded {
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
err = instance.Start()
|
||||
if err != nil {
|
||||
cancel()
|
||||
@@ -80,6 +94,7 @@ func create() (*box.Box, context.CancelFunc, error) {
|
||||
func run() error {
|
||||
osSignals := make(chan os.Signal, 1)
|
||||
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
|
||||
defer signal.Stop(osSignals)
|
||||
for {
|
||||
instance, cancel, err := create()
|
||||
if err != nil {
|
||||
|
||||
@@ -3,7 +3,6 @@ package dialer
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -14,8 +13,7 @@ import (
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"github.com/database64128/tfo-go/v2"
|
||||
"github.com/sagernet/tfo-go"
|
||||
)
|
||||
|
||||
var warnBindInterfaceOnUnsupportedPlatform = warning.New(
|
||||
@@ -54,10 +52,13 @@ var warnTFOOnUnsupportedPlatform = warning.New(
|
||||
)
|
||||
|
||||
type DefaultDialer struct {
|
||||
dialer tfo.Dialer
|
||||
udpDialer net.Dialer
|
||||
dialer4 tfo.Dialer
|
||||
dialer6 tfo.Dialer
|
||||
udpDialer4 net.Dialer
|
||||
udpDialer6 net.Dialer
|
||||
udpListener net.ListenConfig
|
||||
bindUDPAddr string
|
||||
udpAddr4 string
|
||||
udpAddr6 string
|
||||
}
|
||||
|
||||
func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDialer {
|
||||
@@ -69,15 +70,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if router.AutoDetectInterface() {
|
||||
const useInterfaceName = C.IsLinux
|
||||
bindFunc := control.BindToInterfaceFunc(router.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int) {
|
||||
remoteAddr := M.ParseSocksaddr(address).Addr
|
||||
if C.IsLinux {
|
||||
return router.InterfaceMonitor().DefaultInterfaceName(remoteAddr), -1
|
||||
} else {
|
||||
return "", router.InterfaceMonitor().DefaultInterfaceIndex(remoteAddr)
|
||||
}
|
||||
})
|
||||
bindFunc := router.AutoDetectInterfaceFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if router.DefaultInterface() != "" {
|
||||
@@ -120,22 +113,37 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
||||
dialer.Control = control.Append(dialer.Control, control.DisableUDPFragment())
|
||||
listener.Control = control.Append(listener.Control, control.DisableUDPFragment())
|
||||
}
|
||||
var bindUDPAddr string
|
||||
udpDialer := dialer
|
||||
var bindAddress netip.Addr
|
||||
if options.BindAddress != nil {
|
||||
bindAddress = options.BindAddress.Build()
|
||||
var (
|
||||
dialer4 = dialer
|
||||
udpDialer4 = dialer
|
||||
udpAddr4 string
|
||||
)
|
||||
if options.Inet4BindAddress != nil {
|
||||
bindAddr := options.Inet4BindAddress.Build()
|
||||
dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
|
||||
udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
|
||||
udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String()
|
||||
}
|
||||
if bindAddress.IsValid() {
|
||||
dialer.LocalAddr = &net.TCPAddr{
|
||||
IP: bindAddress.AsSlice(),
|
||||
}
|
||||
udpDialer.LocalAddr = &net.UDPAddr{
|
||||
IP: bindAddress.AsSlice(),
|
||||
}
|
||||
bindUDPAddr = M.SocksaddrFrom(bindAddress, 0).String()
|
||||
var (
|
||||
dialer6 = dialer
|
||||
udpDialer6 = dialer
|
||||
udpAddr6 string
|
||||
)
|
||||
if options.Inet6BindAddress != nil {
|
||||
bindAddr := options.Inet6BindAddress.Build()
|
||||
dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
|
||||
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
|
||||
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
|
||||
}
|
||||
return &DefaultDialer{
|
||||
tfo.Dialer{Dialer: dialer4, DisableTFO: !options.TCPFastOpen},
|
||||
tfo.Dialer{Dialer: dialer6, DisableTFO: !options.TCPFastOpen},
|
||||
udpDialer4,
|
||||
udpDialer6,
|
||||
listener,
|
||||
udpAddr4,
|
||||
udpAddr6,
|
||||
}
|
||||
return &DefaultDialer{tfo.Dialer{Dialer: dialer, DisableTFO: !options.TCPFastOpen}, udpDialer, listener, bindUDPAddr}
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
|
||||
@@ -144,11 +152,23 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
||||
}
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkUDP:
|
||||
return d.udpDialer.DialContext(ctx, network, address.String())
|
||||
if !address.IsIPv6() {
|
||||
return d.udpDialer4.DialContext(ctx, network, address.String())
|
||||
} else {
|
||||
return d.udpDialer6.DialContext(ctx, network, address.String())
|
||||
}
|
||||
}
|
||||
if !address.IsIPv6() {
|
||||
return DialSlowContext(&d.dialer4, ctx, network, address)
|
||||
} else {
|
||||
return DialSlowContext(&d.dialer6, ctx, network, address)
|
||||
}
|
||||
return DialSlowContext(&d.dialer, ctx, network, address)
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.bindUDPAddr)
|
||||
if !destination.IsIPv6() {
|
||||
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
|
||||
} else {
|
||||
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,7 @@ import (
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"github.com/database64128/tfo-go/v2"
|
||||
"github.com/sagernet/tfo-go"
|
||||
)
|
||||
|
||||
type slowOpenConn struct {
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"net/netip"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
)
|
||||
@@ -31,8 +30,5 @@ func (r *Reader) Lookup(addr netip.Addr) string {
|
||||
if code != "" {
|
||||
return code
|
||||
}
|
||||
if !N.IsPublicAddr(addr) {
|
||||
return "private"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
@@ -329,6 +329,23 @@ func (c *ClientPacketConn) Write(b []byte) (n int, err error) {
|
||||
return c.ExtendedConn.Write(b)
|
||||
}
|
||||
|
||||
func (c *ClientPacketConn) ReadBuffer(buffer *buf.Buffer) (err error) {
|
||||
if !c.responseRead {
|
||||
err = c.readResponse()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.responseRead = true
|
||||
}
|
||||
var length uint16
|
||||
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
||||
return
|
||||
}
|
||||
|
||||
func (c *ClientPacketConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
if !c.requestWrite {
|
||||
defer buffer.Release()
|
||||
@@ -343,6 +360,11 @@ func (c *ClientPacketConn) FrontHeadroom() int {
|
||||
return 2
|
||||
}
|
||||
|
||||
func (c *ClientPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||
err = c.ReadBuffer(buffer)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *ClientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
return c.WriteBuffer(buffer)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func ParseProtocol(name string) (Protocol, error) {
|
||||
func (p Protocol) newServer(conn net.Conn) (abstractSession, error) {
|
||||
switch p {
|
||||
case ProtocolSMux:
|
||||
session, err := smux.Server(conn, nil)
|
||||
session, err := smux.Server(conn, smuxConfig())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -58,7 +58,7 @@ func (p Protocol) newServer(conn net.Conn) (abstractSession, error) {
|
||||
func (p Protocol) newClient(conn net.Conn) (abstractSession, error) {
|
||||
switch p {
|
||||
case ProtocolSMux:
|
||||
session, err := smux.Client(conn, nil)
|
||||
session, err := smux.Client(conn, smuxConfig())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -70,6 +70,12 @@ func (p Protocol) newClient(conn net.Conn) (abstractSession, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func smuxConfig() *smux.Config {
|
||||
config := smux.DefaultConfig()
|
||||
config.KeepAliveDisabled = true
|
||||
return config
|
||||
}
|
||||
|
||||
func yaMuxConfig() *yamux.Config {
|
||||
config := yamux.DefaultConfig()
|
||||
config.LogOutput = io.Discard
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"encoding/binary"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
@@ -29,6 +31,22 @@ func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, so
|
||||
return &Info{ProcessPath: processName, UserId: -1}, nil
|
||||
}
|
||||
|
||||
var structSize = func() int {
|
||||
value, _ := syscall.Sysctl("kern.osrelease")
|
||||
major, _, _ := strings.Cut(value, ".")
|
||||
n, _ := strconv.ParseInt(major, 10, 64)
|
||||
switch true {
|
||||
case n >= 22:
|
||||
return 408
|
||||
default:
|
||||
// from darwin-xnu/bsd/netinet/in_pcblist.c:get_pcblist_n
|
||||
// size/offset are round up (aligned) to 8 bytes in darwin
|
||||
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
|
||||
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
|
||||
return 384
|
||||
}
|
||||
}()
|
||||
|
||||
func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
||||
var spath string
|
||||
switch network {
|
||||
@@ -53,7 +71,7 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
||||
// size/offset are round up (aligned) to 8 bytes in darwin
|
||||
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
|
||||
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
|
||||
itemSize := 384
|
||||
itemSize := structSize
|
||||
if network == N.NetworkTCP {
|
||||
// rup8(sizeof(xtcpcb_n))
|
||||
itemSize += 208
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package redir
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
@@ -29,7 +30,9 @@ func GetOriginalDestination(conn net.Conn) (destination netip.AddrPort, err erro
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
destination = netip.AddrPortFrom(M.AddrFromIP(raw.Addr.Addr[:]), raw.Addr.Port)
|
||||
var port [2]byte
|
||||
binary.BigEndian.PutUint16(port[:], raw.Addr.Port)
|
||||
destination = netip.AddrPortFrom(M.AddrFromIP(raw.Addr.Addr[:]), binary.LittleEndian.Uint16(port[:]))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
|
||||
err := wininet.SetSystemProxy(F.ToString("http://127.0.0.1:", port), "<local>")
|
||||
err := wininet.SetSystemProxy(F.ToString("http://127.0.0.1:", port), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -13,13 +13,13 @@ import (
|
||||
const (
|
||||
VersionDraft29 = 0xff00001d
|
||||
Version1 = 0x1
|
||||
Version2 = 0x709a50c4
|
||||
Version2 = 0x6b3343cf
|
||||
)
|
||||
|
||||
var (
|
||||
SaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
|
||||
SaltV1 = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
|
||||
SaltV2 = []byte{0xa7, 0x07, 0xc2, 0x03, 0xa5, 0x9b, 0x47, 0x18, 0x4a, 0x1d, 0x62, 0xca, 0x57, 0x04, 0x06, 0xea, 0x7a, 0xe3, 0xe5, 0xd3}
|
||||
SaltV2 = []byte{0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9}
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -5,6 +5,7 @@ package tls
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -13,6 +14,8 @@ import (
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
type acmeWrapper struct {
|
||||
@@ -54,6 +57,11 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
|
||||
config := &certmagic.Config{
|
||||
DefaultServerName: options.DefaultServerName,
|
||||
Storage: storage,
|
||||
Logger: zap.New(zapcore.NewCore(
|
||||
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
|
||||
os.Stderr,
|
||||
zap.InfoLevel,
|
||||
)),
|
||||
}
|
||||
acmeConfig := certmagic.ACMEIssuer{
|
||||
CA: acmeServer,
|
||||
@@ -63,8 +71,9 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
|
||||
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
|
||||
AltHTTPPort: int(options.AlternativeHTTPPort),
|
||||
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
||||
Logger: config.Logger,
|
||||
}
|
||||
if options.ExternalAccount != nil {
|
||||
if options.ExternalAccount != nil && options.ExternalAccount.KeyID != "" {
|
||||
acmeConfig.ExternalAccount = (*acme.EAB)(options.ExternalAccount)
|
||||
}
|
||||
config.Issuers = []certmagic.Issuer{certmagic.NewACMEIssuer(config, acmeConfig)}
|
||||
|
||||
@@ -15,6 +15,9 @@ import (
|
||||
)
|
||||
|
||||
func NewDialerFromOptions(router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
||||
if !options.Enabled {
|
||||
return dialer, nil
|
||||
}
|
||||
config, err := NewClient(router, serverAddress, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -23,20 +26,28 @@ func NewDialerFromOptions(router adapter.Router, dialer N.Dialer, serverAddress
|
||||
}
|
||||
|
||||
func NewClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||
if !options.Enabled {
|
||||
return nil, nil
|
||||
}
|
||||
if options.ECH != nil && options.ECH.Enabled {
|
||||
return newECHClient(router, serverAddress, options)
|
||||
return NewECHClient(router, serverAddress, options)
|
||||
} else if options.Reality != nil && options.Reality.Enabled {
|
||||
return NewRealityClient(router, serverAddress, options)
|
||||
} else if options.UTLS != nil && options.UTLS.Enabled {
|
||||
return newUTLSClient(router, serverAddress, options)
|
||||
return NewUTLSClient(router, serverAddress, options)
|
||||
} else {
|
||||
return newStdClient(serverAddress, options)
|
||||
return NewSTDClient(router, serverAddress, options)
|
||||
}
|
||||
}
|
||||
|
||||
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
|
||||
tlsConn := config.Client(conn)
|
||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
||||
defer cancel()
|
||||
err := tlsConn.HandshakeContext(ctx)
|
||||
tlsConn, err := config.Client(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tlsConn.HandshakeContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -10,27 +10,40 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
STDConfig = tls.Config
|
||||
STDConn = tls.Conn
|
||||
STDConfig = tls.Config
|
||||
STDConn = tls.Conn
|
||||
ConnectionState = tls.ConnectionState
|
||||
)
|
||||
|
||||
type Config interface {
|
||||
ServerName() string
|
||||
SetServerName(serverName string)
|
||||
NextProtos() []string
|
||||
SetNextProtos(nextProto []string)
|
||||
Config() (*STDConfig, error)
|
||||
Client(conn net.Conn) Conn
|
||||
Client(conn net.Conn) (Conn, error)
|
||||
Clone() Config
|
||||
}
|
||||
|
||||
type ConfigWithSessionIDGenerator interface {
|
||||
SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error)
|
||||
}
|
||||
|
||||
type ServerConfig interface {
|
||||
Config
|
||||
adapter.Service
|
||||
Server(conn net.Conn) Conn
|
||||
Server(conn net.Conn) (Conn, error)
|
||||
}
|
||||
|
||||
type ServerConfigCompat interface {
|
||||
ServerConfig
|
||||
ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error)
|
||||
}
|
||||
|
||||
type Conn interface {
|
||||
net.Conn
|
||||
HandshakeContext(ctx context.Context) error
|
||||
ConnectionState() tls.ConnectionState
|
||||
ConnectionState() ConnectionState
|
||||
}
|
||||
|
||||
func ParseTLSVersion(version string) (uint16, error) {
|
||||
|
||||
@@ -11,33 +11,47 @@ import (
|
||||
"net/netip"
|
||||
"os"
|
||||
|
||||
cftls "github.com/sagernet/cloudflare-tls"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
cftls "github.com/sagernet/sing-box/transport/cloudflaretls"
|
||||
"github.com/sagernet/sing-dns"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
mDNS "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type echClientConfig struct {
|
||||
type ECHClientConfig struct {
|
||||
config *cftls.Config
|
||||
}
|
||||
|
||||
func (e *echClientConfig) NextProtos() []string {
|
||||
func (e *ECHClientConfig) ServerName() string {
|
||||
return e.config.ServerName
|
||||
}
|
||||
|
||||
func (e *ECHClientConfig) SetServerName(serverName string) {
|
||||
e.config.ServerName = serverName
|
||||
}
|
||||
|
||||
func (e *ECHClientConfig) NextProtos() []string {
|
||||
return e.config.NextProtos
|
||||
}
|
||||
|
||||
func (e *echClientConfig) SetNextProtos(nextProto []string) {
|
||||
func (e *ECHClientConfig) SetNextProtos(nextProto []string) {
|
||||
e.config.NextProtos = nextProto
|
||||
}
|
||||
|
||||
func (e *echClientConfig) Config() (*STDConfig, error) {
|
||||
func (e *ECHClientConfig) Config() (*STDConfig, error) {
|
||||
return nil, E.New("unsupported usage for ECH")
|
||||
}
|
||||
|
||||
func (e *echClientConfig) Client(conn net.Conn) Conn {
|
||||
return &echConnWrapper{cftls.Client(conn, e.config)}
|
||||
func (e *ECHClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||
return &echConnWrapper{cftls.Client(conn, e.config)}, nil
|
||||
}
|
||||
|
||||
func (e *ECHClientConfig) Clone() Config {
|
||||
return &ECHClientConfig{
|
||||
config: e.config.Clone(),
|
||||
}
|
||||
}
|
||||
|
||||
type echConnWrapper struct {
|
||||
@@ -62,7 +76,11 @@ func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
|
||||
}
|
||||
}
|
||||
|
||||
func newECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||
func (c *echConnWrapper) Upstream() any {
|
||||
return c.Conn
|
||||
}
|
||||
|
||||
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||
var serverName string
|
||||
if options.ServerName != "" {
|
||||
serverName = options.ServerName
|
||||
@@ -76,6 +94,7 @@ func newECHClient(router adapter.Router, serverAddress string, options option.Ou
|
||||
}
|
||||
|
||||
var tlsConfig cftls.Config
|
||||
tlsConfig.Time = router.TimeFunc()
|
||||
if options.DisableSNI {
|
||||
tlsConfig.ServerName = "127.0.0.1"
|
||||
} else {
|
||||
@@ -162,7 +181,7 @@ func newECHClient(router adapter.Router, serverAddress string, options option.Ou
|
||||
} else {
|
||||
tlsConfig.GetClientECHConfigs = fetchECHClientConfig(router)
|
||||
}
|
||||
return &echClientConfig{&tlsConfig}, nil
|
||||
return &ECHClientConfig{&tlsConfig}, nil
|
||||
}
|
||||
|
||||
func fetchECHClientConfig(router adapter.Router) func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
||||
|
||||
@@ -8,6 +8,6 @@ import (
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func newECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||
return nil, E.New(`ECH is not included in this build, rebuild with -tags with_ech`)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func GenerateKeyPair(serverName string) (*tls.Certificate, error) {
|
||||
func GenerateKeyPair(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
||||
if timeFunc == nil {
|
||||
timeFunc = time.Now
|
||||
}
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -22,8 +25,8 @@ func GenerateKeyPair(serverName string) (*tls.Certificate, error) {
|
||||
}
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
NotBefore: time.Now().Add(time.Hour * -1),
|
||||
NotAfter: time.Now().Add(time.Hour),
|
||||
NotBefore: timeFunc().Add(time.Hour * -1),
|
||||
NotAfter: timeFunc().Add(time.Hour),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
|
||||
187
common/tls/reality_client.go
Normal file
187
common/tls/reality_client.go
Normal file
@@ -0,0 +1,187 @@
|
||||
//go:build with_utls
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ed25519"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/debug"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
utls "github.com/sagernet/utls"
|
||||
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
var _ Config = (*RealityClientConfig)(nil)
|
||||
|
||||
type RealityClientConfig struct {
|
||||
uClient *UTLSClientConfig
|
||||
publicKey []byte
|
||||
shortID []byte
|
||||
}
|
||||
|
||||
func NewRealityClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*RealityClientConfig, error) {
|
||||
if options.UTLS == nil || !options.UTLS.Enabled {
|
||||
return nil, E.New("uTLS is required by reality client")
|
||||
}
|
||||
|
||||
uClient, err := NewUTLSClient(router, serverAddress, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
publicKey, err := base64.RawURLEncoding.DecodeString(options.Reality.PublicKey)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode public_key")
|
||||
}
|
||||
if len(publicKey) != 32 {
|
||||
return nil, E.New("invalid public_key")
|
||||
}
|
||||
shortID, err := hex.DecodeString(options.Reality.ShortID)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode short_id")
|
||||
}
|
||||
if len(shortID) != 8 {
|
||||
return nil, E.New("invalid short_id")
|
||||
}
|
||||
return &RealityClientConfig{uClient, publicKey, shortID}, nil
|
||||
}
|
||||
|
||||
func (e *RealityClientConfig) ServerName() string {
|
||||
return e.uClient.ServerName()
|
||||
}
|
||||
|
||||
func (e *RealityClientConfig) SetServerName(serverName string) {
|
||||
e.uClient.SetServerName(serverName)
|
||||
}
|
||||
|
||||
func (e *RealityClientConfig) NextProtos() []string {
|
||||
return e.uClient.NextProtos()
|
||||
}
|
||||
|
||||
func (e *RealityClientConfig) SetNextProtos(nextProto []string) {
|
||||
e.uClient.SetNextProtos(nextProto)
|
||||
}
|
||||
|
||||
func (e *RealityClientConfig) Config() (*STDConfig, error) {
|
||||
return nil, E.New("unsupported usage for reality")
|
||||
}
|
||||
|
||||
func (e *RealityClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||
verifier := &realityVerifier{
|
||||
serverName: e.uClient.ServerName(),
|
||||
}
|
||||
uConfig := e.uClient.config.Clone()
|
||||
uConfig.InsecureSkipVerify = true
|
||||
uConfig.SessionTicketsDisabled = true
|
||||
uConfig.VerifyPeerCertificate = verifier.VerifyPeerCertificate
|
||||
uConn := utls.UClient(conn, uConfig, e.uClient.id)
|
||||
verifier.UConn = uConn
|
||||
err := uConn.BuildHandshakeState()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hello := uConn.HandshakeState.Hello
|
||||
hello.SessionId = make([]byte, 32)
|
||||
copy(hello.Raw[39:], hello.SessionId)
|
||||
|
||||
var nowTime time.Time
|
||||
if uConfig.Time != nil {
|
||||
nowTime = uConfig.Time()
|
||||
} else {
|
||||
nowTime = time.Now()
|
||||
}
|
||||
binary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))
|
||||
|
||||
hello.SessionId[0] = 1
|
||||
hello.SessionId[1] = 7
|
||||
hello.SessionId[2] = 5
|
||||
copy(hello.SessionId[8:], e.shortID)
|
||||
|
||||
if debug.Enabled {
|
||||
fmt.Printf("REALITY hello.sessionId[:16]: %v\n", hello.SessionId[:16])
|
||||
}
|
||||
|
||||
authKey := uConn.HandshakeState.State13.EcdheParams.SharedKey(e.publicKey)
|
||||
if authKey == nil {
|
||||
return nil, E.New("nil auth_key")
|
||||
}
|
||||
verifier.authKey = authKey
|
||||
_, err = hkdf.New(sha256.New, authKey, hello.Random[:20], []byte("REALITY")).Read(authKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aesBlock, _ := aes.NewCipher(authKey)
|
||||
aesGcmCipher, _ := cipher.NewGCM(aesBlock)
|
||||
aesGcmCipher.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
|
||||
copy(hello.Raw[39:], hello.SessionId)
|
||||
if debug.Enabled {
|
||||
fmt.Printf("REALITY hello.sessionId: %v\n", hello.SessionId)
|
||||
fmt.Printf("REALITY uConn.AuthKey: %v\n", authKey)
|
||||
}
|
||||
|
||||
return &utlsConnWrapper{uConn}, nil
|
||||
}
|
||||
|
||||
func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
||||
e.uClient.config.SessionIDGenerator = generator
|
||||
}
|
||||
|
||||
func (e *RealityClientConfig) Clone() Config {
|
||||
return &RealityClientConfig{
|
||||
e.uClient.Clone().(*UTLSClientConfig),
|
||||
e.publicKey,
|
||||
e.shortID,
|
||||
}
|
||||
}
|
||||
|
||||
type realityVerifier struct {
|
||||
*utls.UConn
|
||||
serverName string
|
||||
authKey []byte
|
||||
verified bool
|
||||
}
|
||||
|
||||
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
|
||||
certs := *(*([]*x509.Certificate))(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + p.Offset))
|
||||
if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
|
||||
h := hmac.New(sha512.New, c.authKey)
|
||||
h.Write(pub)
|
||||
if bytes.Equal(h.Sum(nil), certs[0].Signature) {
|
||||
c.verified = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
opts := x509.VerifyOptions{
|
||||
DNSName: c.serverName,
|
||||
Intermediates: x509.NewCertPool(),
|
||||
}
|
||||
for _, cert := range certs[1:] {
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
if _, err := certs[0].Verify(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
if !c.verified {
|
||||
return E.New("reality verification failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
194
common/tls/reality_server.go
Normal file
194
common/tls/reality_server.go
Normal file
@@ -0,0 +1,194 @@
|
||||
//go:build with_reality_server
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/debug"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"github.com/nekohasekai/reality"
|
||||
)
|
||||
|
||||
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
|
||||
|
||||
type RealityServerConfig struct {
|
||||
config *reality.Config
|
||||
}
|
||||
|
||||
func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
|
||||
var tlsConfig reality.Config
|
||||
|
||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||
return nil, E.New("acme is unavailable in reality")
|
||||
}
|
||||
tlsConfig.Time = router.TimeFunc()
|
||||
if options.ServerName != "" {
|
||||
tlsConfig.ServerName = options.ServerName
|
||||
}
|
||||
if len(options.ALPN) > 0 {
|
||||
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
|
||||
}
|
||||
if options.MinVersion != "" {
|
||||
minVersion, err := ParseTLSVersion(options.MinVersion)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse min_version")
|
||||
}
|
||||
tlsConfig.MinVersion = minVersion
|
||||
}
|
||||
if options.MaxVersion != "" {
|
||||
maxVersion, err := ParseTLSVersion(options.MaxVersion)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse max_version")
|
||||
}
|
||||
tlsConfig.MaxVersion = maxVersion
|
||||
}
|
||||
if options.CipherSuites != nil {
|
||||
find:
|
||||
for _, cipherSuite := range options.CipherSuites {
|
||||
for _, tlsCipherSuite := range tls.CipherSuites() {
|
||||
if cipherSuite == tlsCipherSuite.Name {
|
||||
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
|
||||
continue find
|
||||
}
|
||||
}
|
||||
return nil, E.New("unknown cipher_suite: ", cipherSuite)
|
||||
}
|
||||
}
|
||||
if options.Certificate != "" || options.CertificatePath != "" {
|
||||
return nil, E.New("certificate is unavailable in reality")
|
||||
}
|
||||
if options.Key != "" || options.KeyPath != "" {
|
||||
return nil, E.New("key is unavailable in reality")
|
||||
}
|
||||
|
||||
tlsConfig.SessionTicketsDisabled = true
|
||||
tlsConfig.Type = N.NetworkTCP
|
||||
tlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String()
|
||||
|
||||
tlsConfig.ServerNames = map[string]bool{options.ServerName: true}
|
||||
privateKey, err := base64.RawURLEncoding.DecodeString(options.Reality.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode private key")
|
||||
}
|
||||
if len(privateKey) != 32 {
|
||||
return nil, E.New("invalid private key")
|
||||
}
|
||||
tlsConfig.PrivateKey = privateKey
|
||||
tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)
|
||||
|
||||
tlsConfig.ShortIds = make(map[[8]byte]bool)
|
||||
for i, shortID := range options.Reality.ShortID {
|
||||
var shortIDBytesArray [8]byte
|
||||
decodedLen, err := hex.Decode(shortIDBytesArray[:], []byte(shortID))
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortID)
|
||||
}
|
||||
if decodedLen != 8 {
|
||||
return nil, E.New("invalid short_id[", i, "]: ", shortID)
|
||||
}
|
||||
tlsConfig.ShortIds[shortIDBytesArray] = true
|
||||
}
|
||||
|
||||
handshakeDialer := dialer.New(router, options.Reality.Handshake.DialerOptions)
|
||||
tlsConfig.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
}
|
||||
|
||||
if debug.Enabled {
|
||||
tlsConfig.Show = true
|
||||
}
|
||||
|
||||
return &RealityServerConfig{&tlsConfig}, nil
|
||||
}
|
||||
|
||||
func (c *RealityServerConfig) ServerName() string {
|
||||
return c.config.ServerName
|
||||
}
|
||||
|
||||
func (c *RealityServerConfig) SetServerName(serverName string) {
|
||||
c.config.ServerName = serverName
|
||||
}
|
||||
|
||||
func (c *RealityServerConfig) NextProtos() []string {
|
||||
return c.config.NextProtos
|
||||
}
|
||||
|
||||
func (c *RealityServerConfig) SetNextProtos(nextProto []string) {
|
||||
c.config.NextProtos = nextProto
|
||||
}
|
||||
|
||||
func (c *RealityServerConfig) Config() (*tls.Config, error) {
|
||||
return nil, E.New("unsupported usage for reality")
|
||||
}
|
||||
|
||||
func (c *RealityServerConfig) Client(conn net.Conn) (Conn, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (c *RealityServerConfig) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RealityServerConfig) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) {
|
||||
tlsConn, err := reality.Server(ctx, conn, c.config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &realityConnWrapper{Conn: tlsConn}, nil
|
||||
}
|
||||
|
||||
func (c *RealityServerConfig) Clone() Config {
|
||||
return &RealityServerConfig{
|
||||
config: c.config.Clone(),
|
||||
}
|
||||
}
|
||||
|
||||
var _ Conn = (*realityConnWrapper)(nil)
|
||||
|
||||
type realityConnWrapper struct {
|
||||
*reality.Conn
|
||||
}
|
||||
|
||||
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
||||
state := c.Conn.ConnectionState()
|
||||
return tls.ConnectionState{
|
||||
Version: state.Version,
|
||||
HandshakeComplete: state.HandshakeComplete,
|
||||
DidResume: state.DidResume,
|
||||
CipherSuite: state.CipherSuite,
|
||||
NegotiatedProtocol: state.NegotiatedProtocol,
|
||||
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
|
||||
ServerName: state.ServerName,
|
||||
PeerCertificates: state.PeerCertificates,
|
||||
VerifiedChains: state.VerifiedChains,
|
||||
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
|
||||
OCSPResponse: state.OCSPResponse,
|
||||
TLSUnique: state.TLSUnique,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *realityConnWrapper) Upstream() any {
|
||||
return c.Conn
|
||||
}
|
||||
16
common/tls/reality_stub.go
Normal file
16
common/tls/reality_stub.go
Normal file
@@ -0,0 +1,16 @@
|
||||
//go:build !with_reality_server
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||
return nil, E.New(`reality server is not included in this build, rebuild with -tags with_reality_server`)
|
||||
}
|
||||
@@ -5,21 +5,35 @@ import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/badtls"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||
return newSTDServer(ctx, logger, options)
|
||||
func NewServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||
if !options.Enabled {
|
||||
return nil, nil
|
||||
}
|
||||
if options.Reality != nil && options.Reality.Enabled {
|
||||
return NewRealityServer(ctx, router, logger, options)
|
||||
} else {
|
||||
return NewSTDServer(ctx, router, logger, options)
|
||||
}
|
||||
}
|
||||
|
||||
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
|
||||
tlsConn := config.Server(conn)
|
||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
||||
defer cancel()
|
||||
err := tlsConn.HandshakeContext(ctx)
|
||||
if compatServer, isCompat := config.(ServerConfigCompat); isCompat {
|
||||
return compatServer.ServerHandshake(ctx, conn)
|
||||
}
|
||||
tlsConn, err := config.Server(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tlsConn.HandshakeContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -7,15 +7,44 @@ import (
|
||||
"net/netip"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
type stdClientConfig struct {
|
||||
type STDClientConfig struct {
|
||||
config *tls.Config
|
||||
}
|
||||
|
||||
func newStdClient(serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||
func (s *STDClientConfig) ServerName() string {
|
||||
return s.config.ServerName
|
||||
}
|
||||
|
||||
func (s *STDClientConfig) SetServerName(serverName string) {
|
||||
s.config.ServerName = serverName
|
||||
}
|
||||
|
||||
func (s *STDClientConfig) NextProtos() []string {
|
||||
return s.config.NextProtos
|
||||
}
|
||||
|
||||
func (s *STDClientConfig) SetNextProtos(nextProto []string) {
|
||||
s.config.NextProtos = nextProto
|
||||
}
|
||||
|
||||
func (s *STDClientConfig) Config() (*STDConfig, error) {
|
||||
return s.config, nil
|
||||
}
|
||||
|
||||
func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||
return tls.Client(conn, s.config), nil
|
||||
}
|
||||
|
||||
func (s *STDClientConfig) Clone() Config {
|
||||
return &STDClientConfig{s.config.Clone()}
|
||||
}
|
||||
|
||||
func NewSTDClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||
var serverName string
|
||||
if options.ServerName != "" {
|
||||
serverName = options.ServerName
|
||||
@@ -29,6 +58,7 @@ func newStdClient(serverAddress string, options option.OutboundTLSOptions) (Conf
|
||||
}
|
||||
|
||||
var tlsConfig tls.Config
|
||||
tlsConfig.Time = router.TimeFunc()
|
||||
if options.DisableSNI {
|
||||
tlsConfig.ServerName = "127.0.0.1"
|
||||
} else {
|
||||
@@ -96,21 +126,5 @@ func newStdClient(serverAddress string, options option.OutboundTLSOptions) (Conf
|
||||
}
|
||||
tlsConfig.RootCAs = certPool
|
||||
}
|
||||
return &stdClientConfig{&tlsConfig}, nil
|
||||
}
|
||||
|
||||
func (s *stdClientConfig) NextProtos() []string {
|
||||
return s.config.NextProtos
|
||||
}
|
||||
|
||||
func (s *stdClientConfig) SetNextProtos(nextProto []string) {
|
||||
s.config.NextProtos = nextProto
|
||||
}
|
||||
|
||||
func (s *stdClientConfig) Config() (*STDConfig, error) {
|
||||
return s.config, nil
|
||||
}
|
||||
|
||||
func (s *stdClientConfig) Client(conn net.Conn) Conn {
|
||||
return tls.Client(conn, s.config)
|
||||
return &STDClientConfig{&tlsConfig}, nil
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
var errInsecureUnused = E.New("tls: insecure unused")
|
||||
|
||||
type STDServerConfig struct {
|
||||
config *tls.Config
|
||||
logger log.Logger
|
||||
@@ -26,6 +28,14 @@ type STDServerConfig struct {
|
||||
watcher *fsnotify.Watcher
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) ServerName() string {
|
||||
return c.config.ServerName
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) SetServerName(serverName string) {
|
||||
c.config.ServerName = serverName
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) NextProtos() []string {
|
||||
return c.config.NextProtos
|
||||
}
|
||||
@@ -34,119 +44,22 @@ func (c *STDServerConfig) SetNextProtos(nextProto []string) {
|
||||
c.config.NextProtos = nextProto
|
||||
}
|
||||
|
||||
var errInsecureUnused = E.New("tls: insecure unused")
|
||||
|
||||
func newSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||
if !options.Enabled {
|
||||
return nil, nil
|
||||
}
|
||||
var tlsConfig *tls.Config
|
||||
var acmeService adapter.Service
|
||||
var err error
|
||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
|
||||
//nolint:staticcheck
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options.Insecure {
|
||||
return nil, errInsecureUnused
|
||||
}
|
||||
} else {
|
||||
tlsConfig = &tls.Config{}
|
||||
}
|
||||
if options.ServerName != "" {
|
||||
tlsConfig.ServerName = options.ServerName
|
||||
}
|
||||
if len(options.ALPN) > 0 {
|
||||
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
|
||||
}
|
||||
if options.MinVersion != "" {
|
||||
minVersion, err := ParseTLSVersion(options.MinVersion)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse min_version")
|
||||
}
|
||||
tlsConfig.MinVersion = minVersion
|
||||
}
|
||||
if options.MaxVersion != "" {
|
||||
maxVersion, err := ParseTLSVersion(options.MaxVersion)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse max_version")
|
||||
}
|
||||
tlsConfig.MaxVersion = maxVersion
|
||||
}
|
||||
if options.CipherSuites != nil {
|
||||
find:
|
||||
for _, cipherSuite := range options.CipherSuites {
|
||||
for _, tlsCipherSuite := range tls.CipherSuites() {
|
||||
if cipherSuite == tlsCipherSuite.Name {
|
||||
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
|
||||
continue find
|
||||
}
|
||||
}
|
||||
return nil, E.New("unknown cipher_suite: ", cipherSuite)
|
||||
}
|
||||
}
|
||||
var certificate []byte
|
||||
var key []byte
|
||||
if acmeService == nil {
|
||||
if options.Certificate != "" {
|
||||
certificate = []byte(options.Certificate)
|
||||
} else if options.CertificatePath != "" {
|
||||
content, err := os.ReadFile(options.CertificatePath)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read certificate")
|
||||
}
|
||||
certificate = content
|
||||
}
|
||||
if options.Key != "" {
|
||||
key = []byte(options.Key)
|
||||
} else if options.KeyPath != "" {
|
||||
content, err := os.ReadFile(options.KeyPath)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read key")
|
||||
}
|
||||
key = content
|
||||
}
|
||||
if certificate == nil && key == nil && options.Insecure {
|
||||
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return GenerateKeyPair(info.ServerName)
|
||||
}
|
||||
} else {
|
||||
if certificate == nil {
|
||||
return nil, E.New("missing certificate")
|
||||
} else if key == nil {
|
||||
return nil, E.New("missing key")
|
||||
}
|
||||
|
||||
keyPair, err := tls.X509KeyPair(certificate, key)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse x509 key pair")
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{keyPair}
|
||||
}
|
||||
}
|
||||
return &STDServerConfig{
|
||||
config: tlsConfig,
|
||||
logger: logger,
|
||||
acmeService: acmeService,
|
||||
certificate: certificate,
|
||||
key: key,
|
||||
certificatePath: options.CertificatePath,
|
||||
keyPath: options.KeyPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) Config() (*STDConfig, error) {
|
||||
return c.config, nil
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) Client(conn net.Conn) Conn {
|
||||
return tls.Client(conn, c.config)
|
||||
func (c *STDServerConfig) Client(conn net.Conn) (Conn, error) {
|
||||
return tls.Client(conn, c.config), nil
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) Server(conn net.Conn) Conn {
|
||||
return tls.Server(conn, c.config)
|
||||
func (c *STDServerConfig) Server(conn net.Conn) (Conn, error) {
|
||||
return tls.Server(conn, c.config), nil
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) Clone() Config {
|
||||
return &STDServerConfig{
|
||||
config: c.config.Clone(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) Start() error {
|
||||
@@ -242,3 +155,105 @@ func (c *STDServerConfig) Close() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||
if !options.Enabled {
|
||||
return nil, nil
|
||||
}
|
||||
var tlsConfig *tls.Config
|
||||
var acmeService adapter.Service
|
||||
var err error
|
||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
|
||||
//nolint:staticcheck
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options.Insecure {
|
||||
return nil, errInsecureUnused
|
||||
}
|
||||
} else {
|
||||
tlsConfig = &tls.Config{}
|
||||
}
|
||||
tlsConfig.Time = router.TimeFunc()
|
||||
if options.ServerName != "" {
|
||||
tlsConfig.ServerName = options.ServerName
|
||||
}
|
||||
if len(options.ALPN) > 0 {
|
||||
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
|
||||
}
|
||||
if options.MinVersion != "" {
|
||||
minVersion, err := ParseTLSVersion(options.MinVersion)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse min_version")
|
||||
}
|
||||
tlsConfig.MinVersion = minVersion
|
||||
}
|
||||
if options.MaxVersion != "" {
|
||||
maxVersion, err := ParseTLSVersion(options.MaxVersion)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse max_version")
|
||||
}
|
||||
tlsConfig.MaxVersion = maxVersion
|
||||
}
|
||||
if options.CipherSuites != nil {
|
||||
find:
|
||||
for _, cipherSuite := range options.CipherSuites {
|
||||
for _, tlsCipherSuite := range tls.CipherSuites() {
|
||||
if cipherSuite == tlsCipherSuite.Name {
|
||||
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
|
||||
continue find
|
||||
}
|
||||
}
|
||||
return nil, E.New("unknown cipher_suite: ", cipherSuite)
|
||||
}
|
||||
}
|
||||
var certificate []byte
|
||||
var key []byte
|
||||
if acmeService == nil {
|
||||
if options.Certificate != "" {
|
||||
certificate = []byte(options.Certificate)
|
||||
} else if options.CertificatePath != "" {
|
||||
content, err := os.ReadFile(options.CertificatePath)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read certificate")
|
||||
}
|
||||
certificate = content
|
||||
}
|
||||
if options.Key != "" {
|
||||
key = []byte(options.Key)
|
||||
} else if options.KeyPath != "" {
|
||||
content, err := os.ReadFile(options.KeyPath)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read key")
|
||||
}
|
||||
key = content
|
||||
}
|
||||
if certificate == nil && key == nil && options.Insecure {
|
||||
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return GenerateKeyPair(router.TimeFunc(), info.ServerName)
|
||||
}
|
||||
} else {
|
||||
if certificate == nil {
|
||||
return nil, E.New("missing certificate")
|
||||
} else if key == nil {
|
||||
return nil, E.New("missing key")
|
||||
}
|
||||
|
||||
keyPair, err := tls.X509KeyPair(certificate, key)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse x509 key pair")
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{keyPair}
|
||||
}
|
||||
}
|
||||
return &STDServerConfig{
|
||||
config: tlsConfig,
|
||||
logger: logger,
|
||||
acmeService: acmeService,
|
||||
certificate: certificate,
|
||||
key: key,
|
||||
certificatePath: options.CertificatePath,
|
||||
keyPath: options.KeyPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"net"
|
||||
@@ -13,39 +12,53 @@ import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
utls "github.com/refraction-networking/utls"
|
||||
utls "github.com/sagernet/utls"
|
||||
)
|
||||
|
||||
type utlsClientConfig struct {
|
||||
type UTLSClientConfig struct {
|
||||
config *utls.Config
|
||||
id utls.ClientHelloID
|
||||
}
|
||||
|
||||
func (e *utlsClientConfig) NextProtos() []string {
|
||||
func (e *UTLSClientConfig) ServerName() string {
|
||||
return e.config.ServerName
|
||||
}
|
||||
|
||||
func (e *UTLSClientConfig) SetServerName(serverName string) {
|
||||
e.config.ServerName = serverName
|
||||
}
|
||||
|
||||
func (e *UTLSClientConfig) NextProtos() []string {
|
||||
return e.config.NextProtos
|
||||
}
|
||||
|
||||
func (e *utlsClientConfig) SetNextProtos(nextProto []string) {
|
||||
func (e *UTLSClientConfig) SetNextProtos(nextProto []string) {
|
||||
e.config.NextProtos = nextProto
|
||||
}
|
||||
|
||||
func (e *utlsClientConfig) Config() (*STDConfig, error) {
|
||||
func (e *UTLSClientConfig) Config() (*STDConfig, error) {
|
||||
return nil, E.New("unsupported usage for uTLS")
|
||||
}
|
||||
|
||||
func (e *utlsClientConfig) Client(conn net.Conn) Conn {
|
||||
return &utlsConnWrapper{utls.UClient(conn, e.config, e.id)}
|
||||
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, nil
|
||||
}
|
||||
|
||||
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
||||
e.config.SessionIDGenerator = generator
|
||||
}
|
||||
|
||||
func (e *UTLSClientConfig) Clone() Config {
|
||||
return &UTLSClientConfig{
|
||||
config: e.config.Clone(),
|
||||
id: e.id,
|
||||
}
|
||||
}
|
||||
|
||||
type utlsConnWrapper struct {
|
||||
*utls.UConn
|
||||
}
|
||||
|
||||
func (c *utlsConnWrapper) HandshakeContext(ctx context.Context) error {
|
||||
return c.UConn.Handshake()
|
||||
}
|
||||
|
||||
func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
|
||||
state := c.Conn.ConnectionState()
|
||||
return tls.ConnectionState{
|
||||
@@ -64,7 +77,11 @@ func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
|
||||
}
|
||||
}
|
||||
|
||||
func newUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||
func (c *utlsConnWrapper) Upstream() any {
|
||||
return c.UConn
|
||||
}
|
||||
|
||||
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
|
||||
var serverName string
|
||||
if options.ServerName != "" {
|
||||
serverName = options.ServerName
|
||||
@@ -78,6 +95,7 @@ func newUTLSClient(router adapter.Router, serverAddress string, options option.O
|
||||
}
|
||||
|
||||
var tlsConfig utls.Config
|
||||
tlsConfig.Time = router.TimeFunc()
|
||||
if options.DisableSNI {
|
||||
tlsConfig.ServerName = "127.0.0.1"
|
||||
} else {
|
||||
@@ -134,20 +152,34 @@ func newUTLSClient(router adapter.Router, serverAddress string, options option.O
|
||||
}
|
||||
tlsConfig.RootCAs = certPool
|
||||
}
|
||||
var id utls.ClientHelloID
|
||||
switch options.UTLS.Fingerprint {
|
||||
case "chrome", "":
|
||||
id = utls.HelloChrome_Auto
|
||||
case "firefox":
|
||||
id = utls.HelloFirefox_Auto
|
||||
case "ios":
|
||||
id = utls.HelloIOS_Auto
|
||||
case "android":
|
||||
id = utls.HelloAndroid_11_OkHttp
|
||||
case "random":
|
||||
id = utls.HelloRandomized
|
||||
default:
|
||||
return nil, E.New("unknown uTLS fingerprint: ", options.UTLS.Fingerprint)
|
||||
id, err := uTLSClientHelloID(options.UTLS.Fingerprint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &UTLSClientConfig{&tlsConfig, id}, nil
|
||||
}
|
||||
|
||||
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
||||
switch name {
|
||||
case "chrome", "":
|
||||
return utls.HelloChrome_Auto, nil
|
||||
case "firefox":
|
||||
return utls.HelloFirefox_Auto, nil
|
||||
case "edge":
|
||||
return utls.HelloEdge_Auto, nil
|
||||
case "safari":
|
||||
return utls.HelloSafari_Auto, nil
|
||||
case "360":
|
||||
return utls.Hello360_Auto, nil
|
||||
case "qq":
|
||||
return utls.HelloQQ_Auto, nil
|
||||
case "ios":
|
||||
return utls.HelloIOS_Auto, nil
|
||||
case "android":
|
||||
return utls.HelloAndroid_11_OkHttp, nil
|
||||
case "random":
|
||||
return utls.HelloRandomized, nil
|
||||
default:
|
||||
return utls.ClientHelloID{}, E.New("unknown uTLS fingerprint: ", name)
|
||||
}
|
||||
return &utlsClientConfig{&tlsConfig, id}, nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@ import (
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func newUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||
return nil, E.New(`uTLS is not included in this build, rebuild with -tags with_utls`)
|
||||
}
|
||||
|
||||
func NewRealityClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||
return nil, E.New(`uTLS, which is required by reality client is not included in this build, rebuild with -tags with_utls`)
|
||||
}
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
package trafficcontrol
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type Manager[U comparable] struct {
|
||||
access sync.Mutex
|
||||
users map[U]*Traffic
|
||||
}
|
||||
|
||||
type Traffic struct {
|
||||
Upload uint64
|
||||
Download uint64
|
||||
}
|
||||
|
||||
func NewManager[U comparable]() *Manager[U] {
|
||||
return &Manager[U]{
|
||||
users: make(map[U]*Traffic),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager[U]) Reset() {
|
||||
m.users = make(map[U]*Traffic)
|
||||
}
|
||||
|
||||
func (m *Manager[U]) TrackConnection(user U, conn net.Conn) net.Conn {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
var traffic *Traffic
|
||||
if t, loaded := m.users[user]; loaded {
|
||||
traffic = t
|
||||
} else {
|
||||
traffic = new(Traffic)
|
||||
m.users[user] = traffic
|
||||
}
|
||||
return &TrackConn{conn, traffic}
|
||||
}
|
||||
|
||||
func (m *Manager[U]) TrackPacketConnection(user U, conn N.PacketConn) N.PacketConn {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
var traffic *Traffic
|
||||
if t, loaded := m.users[user]; loaded {
|
||||
traffic = t
|
||||
} else {
|
||||
traffic = new(Traffic)
|
||||
m.users[user] = traffic
|
||||
}
|
||||
return &TrackPacketConn{conn, traffic}
|
||||
}
|
||||
|
||||
func (m *Manager[U]) ReadTraffics() map[U]Traffic {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
|
||||
trafficMap := make(map[U]Traffic)
|
||||
for user, traffic := range m.users {
|
||||
upload := atomic.SwapUint64(&traffic.Upload, 0)
|
||||
download := atomic.SwapUint64(&traffic.Download, 0)
|
||||
if upload == 0 && download == 0 {
|
||||
continue
|
||||
}
|
||||
trafficMap[user] = Traffic{
|
||||
Upload: upload,
|
||||
Download: download,
|
||||
}
|
||||
}
|
||||
return trafficMap
|
||||
}
|
||||
|
||||
type TrackConn struct {
|
||||
net.Conn
|
||||
*Traffic
|
||||
}
|
||||
|
||||
func (c *TrackConn) Read(p []byte) (n int, err error) {
|
||||
n, err = c.Conn.Read(p)
|
||||
if n > 0 {
|
||||
atomic.AddUint64(&c.Upload, uint64(n))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *TrackConn) Write(p []byte) (n int, err error) {
|
||||
n, err = c.Conn.Write(p)
|
||||
if n > 0 {
|
||||
atomic.AddUint64(&c.Download, uint64(n))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *TrackConn) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = bufio.Copy(w, c.Conn)
|
||||
if n > 0 {
|
||||
atomic.AddUint64(&c.Upload, uint64(n))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *TrackConn) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
n, err = bufio.Copy(c.Conn, r)
|
||||
if n > 0 {
|
||||
atomic.AddUint64(&c.Download, uint64(n))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *TrackConn) Upstream() any {
|
||||
return c.Conn
|
||||
}
|
||||
|
||||
type TrackPacketConn struct {
|
||||
N.PacketConn
|
||||
*Traffic
|
||||
}
|
||||
|
||||
func (c *TrackPacketConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {
|
||||
destination, err := c.PacketConn.ReadPacket(buffer)
|
||||
if err == nil {
|
||||
atomic.AddUint64(&c.Upload, uint64(buffer.Len()))
|
||||
}
|
||||
return destination, err
|
||||
}
|
||||
|
||||
func (c *TrackPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
n := buffer.Len()
|
||||
err := c.PacketConn.WritePacket(buffer, destination)
|
||||
if err == nil {
|
||||
atomic.AddUint64(&c.Download, uint64(n))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *TrackPacketConn) Upstream() any {
|
||||
return c.PacketConn
|
||||
}
|
||||
8
constant/dhcp.go
Normal file
8
constant/dhcp.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package constant
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
DHCPTTL = time.Hour
|
||||
DHCPTimeout = time.Minute
|
||||
)
|
||||
@@ -3,13 +3,28 @@ package constant
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
)
|
||||
|
||||
const dirName = "sing-box"
|
||||
|
||||
var resourcePaths []string
|
||||
var (
|
||||
basePath string
|
||||
resourcePaths []string
|
||||
)
|
||||
|
||||
func BasePath(name string) string {
|
||||
if basePath == "" || strings.HasPrefix(name, "/") {
|
||||
return name
|
||||
}
|
||||
return filepath.Join(basePath, name)
|
||||
}
|
||||
|
||||
func SetBasePath(path string) {
|
||||
basePath = path
|
||||
}
|
||||
|
||||
func FindPath(name string) (string, bool) {
|
||||
name = os.ExpandEnv(name)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package constant
|
||||
|
||||
var Version = "1.1-beta10"
|
||||
var Version = "1.2-beta5"
|
||||
|
||||
@@ -1,3 +1,200 @@
|
||||
#### 1.2-beta5
|
||||
|
||||
* Add [VLESS server](/configuration/inbound/vless) and [vision](/configuration/outbound/vless#flow) support
|
||||
* Add [reality TLS](/configuration/shared/tls) support
|
||||
* Fix match private address
|
||||
|
||||
#### 1.1.6
|
||||
|
||||
* Improve vmess request
|
||||
* Fix ipv6 redirect on Linux
|
||||
* Fix match geoip private
|
||||
* Fix parse hysteria UDP message
|
||||
* Fix socks connect response
|
||||
* Disable vmess header protection if transport enabled
|
||||
* Update QUIC v2 version number and initial salt
|
||||
|
||||
#### 1.2-beta4
|
||||
|
||||
* Add [NTP service](/configuration/ntp)
|
||||
* Add Add multiple server names and multi-user support for shadowtls
|
||||
* Add strict mode support for shadowtls v3
|
||||
* Add uTLS support for shadowtls v3
|
||||
|
||||
#### 1.2-beta3
|
||||
|
||||
* Update QUIC v2 version number and initial salt
|
||||
* Fix shadowtls v3 implementation
|
||||
|
||||
#### 1.2-beta2
|
||||
|
||||
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
|
||||
* Add fallback support for v2ray transport
|
||||
* Fix parse hysteria UDP message
|
||||
* Fix socks connect response
|
||||
* Disable vmess header protection if transport enabled
|
||||
|
||||
#### 1.2-beta1
|
||||
|
||||
* Add [DHCP DNS server](/configuration/dns/server) support
|
||||
* Add SSH [host key validation](/configuration/outbound/ssh) support
|
||||
* Add [query_type](/configuration/dns/rule) DNS rule item
|
||||
* Add v2ray [user stats](/configuration/experimental#statsusers) api
|
||||
* Add new clash DNS query api
|
||||
* Improve vmess request
|
||||
* Fix ipv6 redirect on Linux
|
||||
* Fix match geoip private
|
||||
|
||||
#### 1.1.5
|
||||
|
||||
* Add Go 1.20 support
|
||||
* Fix inbound default DF value
|
||||
* Fix auth_user route for naive inbound
|
||||
* Fix gRPC lite header
|
||||
* Ignore domain case in route rules
|
||||
|
||||
#### 1.1.4
|
||||
|
||||
* Fix DNS log
|
||||
* Fix write to h2 conn after closed
|
||||
* Fix create UDP DNS transport from plain IPv6 address
|
||||
|
||||
#### 1.1.2
|
||||
|
||||
* Fix http proxy auth
|
||||
* Fix user from stream packet conn
|
||||
* Fix DNS response TTL
|
||||
* Fix override packet conn
|
||||
* Skip override system proxy bypass list
|
||||
* Improve DNS log
|
||||
|
||||
#### 1.1.1
|
||||
|
||||
* Fix acme config
|
||||
* Fix vmess packet conn
|
||||
* Suppress quic-go set DF error
|
||||
|
||||
#### 1.1
|
||||
|
||||
* Fix close clash cache
|
||||
|
||||
Important changes since 1.0:
|
||||
|
||||
* Add support for use with android VPNService
|
||||
* Add tun support for WireGuard outbound
|
||||
* Add system tun stack
|
||||
* Add comment filter for config
|
||||
* Add option for allow optional proxy protocol header
|
||||
* Add Clash mode and persistence support
|
||||
* Add TLS ECH and uTLS support for outbound TLS options
|
||||
* Add internal simple-obfs and v2ray-plugin
|
||||
* Add ShadowsocksR outbound
|
||||
* Add VLESS outbound and XUDP
|
||||
* Skip wait for hysteria tcp handshake response
|
||||
* Add v2ray mux support for all inbound
|
||||
* Add XUDP support for VMess
|
||||
* Improve websocket writer
|
||||
* Refine tproxy write back
|
||||
* Fix DNS leak caused by
|
||||
Windows' ordinary multihomed DNS resolution behavior
|
||||
* Add sniff_timeout listen option
|
||||
* Add custom route support for tun
|
||||
* Add option for custom wireguard reserved bytes
|
||||
* Split bind_address into ipv4 and ipv6
|
||||
* Add ShadowTLS v1 and v2 support
|
||||
|
||||
#### 1.1-rc1
|
||||
|
||||
* Fix TLS config for h2 server
|
||||
* Fix crash when input bad method in shadowsocks multi-user inbound
|
||||
* Fix listen UDP
|
||||
* Fix check invalid packet on macOS
|
||||
|
||||
#### 1.1-beta18
|
||||
|
||||
* Enhance defense against active probe for shadowtls server **1**
|
||||
|
||||
**1**:
|
||||
|
||||
The `fallback_after` option has been removed.
|
||||
|
||||
#### 1.1-beta17
|
||||
|
||||
* Fix shadowtls server **1**
|
||||
|
||||
*1*:
|
||||
|
||||
Added [fallback_after](/configuration/inbound/shadowtls#fallback_after) option.
|
||||
|
||||
#### 1.0.7
|
||||
|
||||
* Add support for new x/h2 deadline
|
||||
* Fix copy pipe
|
||||
* Fix decrypt xplus packet
|
||||
* Fix macOS Ventura process name match
|
||||
* Fix smux keepalive
|
||||
* Fix vmess request buffer
|
||||
* Fix h2c transport
|
||||
* Fix tor geoip
|
||||
* Fix udp connect for mux client
|
||||
* Fix default dns transport strategy
|
||||
|
||||
#### 1.1-beta16
|
||||
|
||||
* Improve shadowtls server
|
||||
* Fix default dns transport strategy
|
||||
* Update uTLS to v1.2.0
|
||||
|
||||
#### 1.1-beta15
|
||||
|
||||
* Add support for new x/h2 deadline
|
||||
* Fix udp connect for mux client
|
||||
* Fix dns buffer
|
||||
* Fix quic dns retry
|
||||
* Fix create TLS config
|
||||
* Fix websocket alpn
|
||||
* Fix tor geoip
|
||||
|
||||
#### 1.1-beta14
|
||||
|
||||
* Add multi-user support for hysteria inbound **1**
|
||||
* Add custom tls client support for std grpc
|
||||
* Fix smux keep alive
|
||||
* Fix vmess request buffer
|
||||
* Fix default local DNS server behavior
|
||||
* Fix h2c transport
|
||||
|
||||
*1*:
|
||||
|
||||
The `auth` and `auth_str` fields have been replaced by the `users` field.
|
||||
|
||||
#### 1.1-beta13
|
||||
|
||||
* Add custom worker count option for WireGuard outbound
|
||||
* Split bind_address into ipv4 and ipv6
|
||||
* Move WFP manipulation to strict route
|
||||
* Fix WireGuard outbound panic when close
|
||||
* Fix macOS Ventura process name match
|
||||
* Fix QUIC connection migration by @HyNetwork
|
||||
* Fix handling QUIC client SNI by @HyNetwork
|
||||
|
||||
#### 1.1-beta12
|
||||
|
||||
* Fix uTLS config
|
||||
* Update quic-go to v0.30.0
|
||||
* Update cloudflare-tls to go1.18.7
|
||||
|
||||
#### 1.1-beta11
|
||||
|
||||
* Add option for custom wireguard reserved bytes
|
||||
* Fix shadowtls v2
|
||||
* Fix h3 dns transport
|
||||
* Fix copy pipe
|
||||
* Fix decrypt xplus packet
|
||||
* Fix v2ray api
|
||||
* Suppress no network error
|
||||
* Improve local dns transport
|
||||
|
||||
#### 1.1-beta10
|
||||
|
||||
* Add [sniff_timeout](/configuration/shared/listen#sniff_timeout) listen option
|
||||
@@ -41,7 +238,8 @@ The `strict_route` on windows is removed.
|
||||
|
||||
**2**:
|
||||
|
||||
See [ShadowTLS inbound](/configuration/inbound/shadowtls#version) and [ShadowTLS outbound](/configuration/outbound/shadowtls#version)
|
||||
See [ShadowTLS inbound](/configuration/inbound/shadowtls#version)
|
||||
and [ShadowTLS outbound](/configuration/outbound/shadowtls#version)
|
||||
|
||||
#### 1.1-beta8
|
||||
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
"mixed-in"
|
||||
],
|
||||
"ip_version": 6,
|
||||
"query_type": [
|
||||
"A",
|
||||
"HTTPS",
|
||||
32768
|
||||
],
|
||||
"network": "tcp",
|
||||
"auth_user": [
|
||||
"usera",
|
||||
@@ -119,6 +124,10 @@ Tags of [Inbound](/configuration/inbound).
|
||||
|
||||
Not limited if empty.
|
||||
|
||||
#### query_type
|
||||
|
||||
DNS query type. Values can be integers or type name strings.
|
||||
|
||||
#### network
|
||||
|
||||
`tcp` or `udp`.
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
"mixed-in"
|
||||
],
|
||||
"ip_version": 6,
|
||||
"query_type": [
|
||||
"A",
|
||||
"HTTPS",
|
||||
32768
|
||||
],
|
||||
"network": "tcp",
|
||||
"auth_user": [
|
||||
"usera",
|
||||
@@ -118,6 +123,10 @@
|
||||
|
||||
默认不限制。
|
||||
|
||||
#### query_type
|
||||
|
||||
DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
#### network
|
||||
|
||||
`tcp` 或 `udp`。
|
||||
|
||||
@@ -30,16 +30,17 @@ The tag of the dns server.
|
||||
|
||||
The address of the dns server.
|
||||
|
||||
| Protocol | Format |
|
||||
|----------|-----------------------------|
|
||||
| `System` | `local` |
|
||||
| `TCP` | `tcp://1.0.0.1` |
|
||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||
| `TLS` | `tls://dns.google` |
|
||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||
| `QUIC` | `quic://dns.adguard.com` |
|
||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||
| `RCode` | `rcode://refused` |
|
||||
| Protocol | Format |
|
||||
|----------|-------------------------------|
|
||||
| `System` | `local` |
|
||||
| `TCP` | `tcp://1.0.0.1` |
|
||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||
| `TLS` | `tls://dns.google` |
|
||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||
| `QUIC` | `quic://dns.adguard.com` |
|
||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||
| `RCode` | `rcode://refused` |
|
||||
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
|
||||
|
||||
!!! warning ""
|
||||
|
||||
@@ -53,6 +54,10 @@ The address of the dns server.
|
||||
|
||||
the RCode transport is often used to block queries. Use with rules and the `disable_cache` rule option.
|
||||
|
||||
!!! warning ""
|
||||
|
||||
DHCP transport is not included by default, see [Installation](/#installation).
|
||||
|
||||
| RCode | Description |
|
||||
|-------------------|-----------------------|
|
||||
| `success` | `No error` |
|
||||
|
||||
@@ -30,16 +30,17 @@ DNS 服务器的标签。
|
||||
|
||||
DNS 服务器的地址。
|
||||
|
||||
| 协议 | 格式 |
|
||||
|----------|-----------------------------|
|
||||
| `System` | `local` |
|
||||
| `TCP` | `tcp://1.0.0.1` |
|
||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||
| `TLS` | `tls://dns.google` |
|
||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||
| `QUIC` | `quic://dns.adguard.com` |
|
||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||
| `RCode` | `rcode://refused` |
|
||||
| 协议 | 格式 |
|
||||
|----------|------------------------------|
|
||||
| `System` | `local` |
|
||||
| `TCP` | `tcp://1.0.0.1` |
|
||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||
| `TLS` | `tls://dns.google` |
|
||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||
| `QUIC` | `quic://dns.adguard.com` |
|
||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||
| `RCode` | `rcode://refused` |
|
||||
| `DHCP` | `dhcp://auto` 或 `dhcp://en0` |
|
||||
|
||||
!!! warning ""
|
||||
|
||||
@@ -53,6 +54,10 @@ DNS 服务器的地址。
|
||||
|
||||
RCode 传输层传输层常用于屏蔽请求. 与 DNS 规则和 `disable_cache` 规则选项一起使用。
|
||||
|
||||
!!! warning ""
|
||||
|
||||
默认安装不包含 DHCP 传输层,请参阅 [安装](/zh/#_2)。
|
||||
|
||||
| RCode | 描述 |
|
||||
|-------------------|----------|
|
||||
| `success` | `无错误` |
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"external_controller": "127.0.0.1:9090",
|
||||
"external_ui": "folder",
|
||||
"secret": "",
|
||||
"direct_io": false,
|
||||
"default_mode": "rule",
|
||||
"store_selected": false,
|
||||
"cache_file": "cache.db"
|
||||
@@ -18,13 +17,15 @@
|
||||
"listen": "127.0.0.1:8080",
|
||||
"stats": {
|
||||
"enabled": true,
|
||||
"direct_io": false,
|
||||
"inbounds": [
|
||||
"socks-in"
|
||||
],
|
||||
"outbounds": [
|
||||
"proxy",
|
||||
"direct"
|
||||
],
|
||||
"users": [
|
||||
"sekai"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -58,10 +59,6 @@ Secret for the RESTful API (optional)
|
||||
Authenticate by spedifying HTTP header `Authorization: Bearer ${secret}`
|
||||
ALWAYS set a secret if RESTful API is listening on 0.0.0.0
|
||||
|
||||
#### direct_io
|
||||
|
||||
Allows lossless relays like splice without real-time traffic reporting.
|
||||
|
||||
#### default_mode
|
||||
|
||||
Default mode in clash, `rule` will be used if empty.
|
||||
@@ -98,10 +95,6 @@ Traffic statistics service settings.
|
||||
|
||||
Enable statistics service.
|
||||
|
||||
#### stats.direct_io
|
||||
|
||||
Allows lossless relays like splice without real-time traffic reporting.
|
||||
|
||||
#### stats.inbounds
|
||||
|
||||
Inbound list to count traffic.
|
||||
@@ -109,3 +102,7 @@ Inbound list to count traffic.
|
||||
#### stats.outbounds
|
||||
|
||||
Outbound list to count traffic.
|
||||
|
||||
#### stats.users
|
||||
|
||||
User list to count traffic.
|
||||
@@ -9,7 +9,6 @@
|
||||
"external_controller": "127.0.0.1:9090",
|
||||
"external_ui": "folder",
|
||||
"secret": "",
|
||||
"direct_io": false,
|
||||
"default_mode": "rule",
|
||||
"store_selected": false,
|
||||
"cache_file": "cache.db"
|
||||
@@ -18,13 +17,15 @@
|
||||
"listen": "127.0.0.1:8080",
|
||||
"stats": {
|
||||
"enabled": true,
|
||||
"direct_io": false,
|
||||
"inbounds": [
|
||||
"socks-in"
|
||||
],
|
||||
"outbounds": [
|
||||
"proxy",
|
||||
"direct"
|
||||
],
|
||||
"users": [
|
||||
"sekai"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -56,10 +57,6 @@ RESTful API 的密钥(可选)
|
||||
通过指定 HTTP 标头 `Authorization: Bearer ${secret}` 进行身份验证
|
||||
如果 RESTful API 正在监听 0.0.0.0,请始终设置一个密钥。
|
||||
|
||||
#### direct_io
|
||||
|
||||
允许像 splice 这样的没有实时流量报告的无损中继。
|
||||
|
||||
#### default_mode
|
||||
|
||||
Clash 中的默认模式,默认使用 `rule`。
|
||||
@@ -96,10 +93,6 @@ gRPC API 监听地址。如果为空,则禁用 V2Ray API。
|
||||
|
||||
启用统计服务。
|
||||
|
||||
#### stats.direct_io
|
||||
|
||||
允许像 splice 这样的没有实时流量报告的无损中继。
|
||||
|
||||
#### stats.inbounds
|
||||
|
||||
统计流量的入站列表。
|
||||
@@ -107,3 +100,7 @@ gRPC API 监听地址。如果为空,则禁用 V2Ray API。
|
||||
#### stats.outbounds
|
||||
|
||||
统计流量的出站列表。
|
||||
|
||||
#### stats.users
|
||||
|
||||
统计流量的用户列表。
|
||||
@@ -12,8 +12,15 @@
|
||||
"down": "100 Mbps",
|
||||
"down_mbps": 100,
|
||||
"obfs": "fuck me till the daylight",
|
||||
"auth": "",
|
||||
"auth_str": "password",
|
||||
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"auth": "",
|
||||
"auth_str": "password"
|
||||
}
|
||||
],
|
||||
|
||||
"recv_window_conn": 0,
|
||||
"recv_window_client": 0,
|
||||
"max_conn_client": 0,
|
||||
@@ -61,11 +68,19 @@ Supported units (case sensitive, b = bits, B = bytes, 8b=1B):
|
||||
|
||||
Obfuscated password.
|
||||
|
||||
#### auth
|
||||
#### users
|
||||
|
||||
Hysteria users
|
||||
|
||||
#### users.auth
|
||||
|
||||
==Required if `auth_str` is empty==
|
||||
|
||||
Authentication password, in base64.
|
||||
|
||||
#### auth_str
|
||||
#### users.auth_str
|
||||
|
||||
==Required if `auth` is empty==
|
||||
|
||||
Authentication password.
|
||||
|
||||
|
||||
@@ -12,8 +12,15 @@
|
||||
"down": "100 Mbps",
|
||||
"down_mbps": 100,
|
||||
"obfs": "fuck me till the daylight",
|
||||
"auth": "",
|
||||
"auth_str": "password",
|
||||
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"auth": "",
|
||||
"auth_str": "password"
|
||||
}
|
||||
],
|
||||
|
||||
"recv_window_conn": 0,
|
||||
"recv_window_client": 0,
|
||||
"max_conn_client": 0,
|
||||
@@ -61,11 +68,19 @@
|
||||
|
||||
混淆密码。
|
||||
|
||||
#### auth
|
||||
#### users
|
||||
|
||||
Hysteria 用户
|
||||
|
||||
#### users.auth
|
||||
|
||||
==与 auth_str 必填一个==
|
||||
|
||||
base64 编码的认证密码。
|
||||
|
||||
#### auth_str
|
||||
#### users.auth_str
|
||||
|
||||
==与 auth 必填一个==
|
||||
|
||||
认证密码。
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
| `trojan` | [Trojan](./trojan) | TCP |
|
||||
| `naive` | [Naive](./naive) | X |
|
||||
| `hysteria` | [Hysteria](./hysteria) | X |
|
||||
| `shadowtls` | [ShadowTLS](./shadowtls) | TCP |
|
||||
| `vless` | [VLESS](./vless) | TCP |
|
||||
| `tun` | [Tun](./tun) | X |
|
||||
| `redirect` | [Redirect](./redirect) | X |
|
||||
| `tproxy` | [TProxy](./tproxy) | X |
|
||||
|
||||
@@ -7,14 +7,29 @@
|
||||
|
||||
... // Listen Fields
|
||||
|
||||
"version": 2,
|
||||
"version": 3,
|
||||
"password": "fuck me till the daylight",
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
}
|
||||
],
|
||||
"handshake": {
|
||||
"server": "google.com",
|
||||
"server_port": 443,
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
},
|
||||
"handshake_for_server_name": {
|
||||
"example.com": {
|
||||
"server": "example.com",
|
||||
"server_port": 443,
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
},
|
||||
"strict_mode": false
|
||||
}
|
||||
```
|
||||
|
||||
@@ -32,15 +47,35 @@ ShadowTLS protocol version.
|
||||
|---------------|-----------------------------------------------------------------------------------------|
|
||||
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
|
||||
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
|
||||
| `3` | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |
|
||||
|
||||
#### password
|
||||
|
||||
Set password.
|
||||
ShadowTLS password.
|
||||
|
||||
Only available in the ShadowTLS v2 protocol.
|
||||
Only available in the ShadowTLS protocol 2.
|
||||
|
||||
|
||||
#### users
|
||||
|
||||
ShadowTLS users.
|
||||
|
||||
Only available in the ShadowTLS protocol 3.
|
||||
|
||||
#### handshake
|
||||
|
||||
==Required==
|
||||
|
||||
Handshake server address and [Dial options](/configuration/shared/dial).
|
||||
Handshake server address and [Dial options](/configuration/shared/dial).
|
||||
|
||||
#### handshake
|
||||
|
||||
Handshake server address and [Dial options](/configuration/shared/dial) for specific server name.
|
||||
|
||||
Only available in the ShadowTLS protocol 2/3.
|
||||
|
||||
#### strict_mode
|
||||
|
||||
ShadowTLS strict mode.
|
||||
|
||||
Only available in the ShadowTLS protocol 3.
|
||||
|
||||
@@ -7,14 +7,29 @@
|
||||
|
||||
... // 监听字段
|
||||
|
||||
"version": 2,
|
||||
"version": 3,
|
||||
"password": "fuck me till the daylight",
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
}
|
||||
],
|
||||
"handshake": {
|
||||
"server": "google.com",
|
||||
"server_port": 443,
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
},
|
||||
"handshake_for_server_name": {
|
||||
"example.com": {
|
||||
"server": "example.com",
|
||||
"server_port": 443,
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
},
|
||||
"strict_mode": false
|
||||
}
|
||||
```
|
||||
|
||||
@@ -32,15 +47,36 @@ ShadowTLS 协议版本。
|
||||
|---------------|-----------------------------------------------------------------------------------------|
|
||||
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
|
||||
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
|
||||
| `3` | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |
|
||||
|
||||
#### password
|
||||
|
||||
设置密码。
|
||||
ShadowTLS 密码。
|
||||
|
||||
仅在 ShadowTLS v2 协议中可用。
|
||||
仅在 ShadowTLS 协议版本 2 中可用。
|
||||
|
||||
#### users
|
||||
|
||||
ShadowTLS 用户。
|
||||
|
||||
仅在 ShadowTLS 协议版本 3 中可用。
|
||||
|
||||
#### handshake
|
||||
|
||||
==必填==
|
||||
|
||||
握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
|
||||
|
||||
#### handshake
|
||||
|
||||
==必填==
|
||||
|
||||
对于特定服务器名称的握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
|
||||
|
||||
仅在 ShadowTLS 协议版本 2/3 中可用。
|
||||
|
||||
#### strict_mode
|
||||
|
||||
ShadowTLS 严格模式。
|
||||
|
||||
仅在 ShadowTLS 协议版本 3 中可用。
|
||||
|
||||
@@ -93,16 +93,23 @@ Set the default route to the Tun.
|
||||
|
||||
#### strict_route
|
||||
|
||||
*In Linux*:
|
||||
|
||||
Enforce strict routing rules when `auto_route` is enabled:
|
||||
|
||||
*In Linux*:
|
||||
|
||||
* Let unsupported network unreachable
|
||||
* Route all connections to tun
|
||||
|
||||
It prevents address leaks and makes DNS hijacking work on Android and Linux with systemd-resolved, but your device will
|
||||
not be accessible by others.
|
||||
|
||||
*In Windows*:
|
||||
|
||||
* Add firewall rules to prevent DNS leak caused by
|
||||
Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
|
||||
|
||||
It may prevent some applications (such as VirtualBox) from working properly in certain situations.
|
||||
|
||||
#### inet4_route_address
|
||||
|
||||
Use custom routes instead of default when `auto_route` is enabled.
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
{
|
||||
"type": "tun",
|
||||
"tag": "tun-in",
|
||||
|
||||
"interface_name": "tun0",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||
@@ -47,8 +46,8 @@
|
||||
"exclude_package": [
|
||||
"com.android.captiveportallogin"
|
||||
],
|
||||
|
||||
... // 监听字段
|
||||
...
|
||||
// 监听字段
|
||||
}
|
||||
```
|
||||
|
||||
@@ -94,15 +93,23 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
#### strict_route
|
||||
|
||||
*在 Linux 中*:
|
||||
|
||||
启用 `auto_route` 时执行严格的路由规则。
|
||||
|
||||
*在 Linux 中*:
|
||||
|
||||
* 让不支持的网络无法到达
|
||||
* 将所有连接路由到 tun
|
||||
|
||||
它可以防止地址泄漏,并使 DNS 劫持在 Android 和使用 systemd-resolved 的 Linux 上工作,但你的设备将无法其他设备被访问。
|
||||
|
||||
*在 Windows 中*:
|
||||
|
||||
* 添加防火墙规则以阻止 Windows
|
||||
的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
|
||||
造成的 DNS 泄露
|
||||
|
||||
它可能会使某些应用程序(如 VirtualBox)在某些情况下无法正常工作。
|
||||
|
||||
#### inet4_route_address
|
||||
|
||||
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
||||
|
||||
39
docs/configuration/inbound/vless.md
Normal file
39
docs/configuration/inbound/vless.md
Normal file
@@ -0,0 +1,39 @@
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "vless",
|
||||
"tag": "vless-in",
|
||||
|
||||
... // Listen Fields
|
||||
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661"
|
||||
}
|
||||
],
|
||||
"tls": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Listen Fields
|
||||
|
||||
See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
### Fields
|
||||
|
||||
#### users
|
||||
|
||||
==Required==
|
||||
|
||||
VLESS users.
|
||||
|
||||
#### tls
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||
39
docs/configuration/inbound/vless.zh.md
Normal file
39
docs/configuration/inbound/vless.zh.md
Normal file
@@ -0,0 +1,39 @@
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "vless",
|
||||
"tag": "vless-in",
|
||||
|
||||
... // 监听字段
|
||||
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661"
|
||||
}
|
||||
],
|
||||
"tls": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 监听字段
|
||||
|
||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||
|
||||
### 字段
|
||||
|
||||
#### users
|
||||
|
||||
==必填==
|
||||
|
||||
VLESS 用户。
|
||||
|
||||
#### tls
|
||||
|
||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
||||
@@ -8,6 +8,7 @@ sing-box uses JSON for configuration files.
|
||||
{
|
||||
"log": {},
|
||||
"dns": {},
|
||||
"ntp": {},
|
||||
"inbounds": [],
|
||||
"outbounds": [],
|
||||
"route": {},
|
||||
@@ -21,6 +22,7 @@ sing-box uses JSON for configuration files.
|
||||
|----------------|--------------------------------|
|
||||
| `log` | [Log](./log) |
|
||||
| `dns` | [DNS](./dns) |
|
||||
| `ntp` | [NTP](./ntp) |
|
||||
| `inbounds` | [Inbound](./inbound) |
|
||||
| `outbounds` | [Outbound](./outbound) |
|
||||
| `route` | [Route](./route) |
|
||||
|
||||
50
docs/configuration/ntp/index.md
Normal file
50
docs/configuration/ntp/index.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# NTP
|
||||
|
||||
Built-in NTP client service.
|
||||
|
||||
If enabled, it will provide time for protocols like TLS/Shadowsocks/VMess, which is useful for environments where time
|
||||
synchronization is not possible.
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"ntp": {
|
||||
"enabled": false,
|
||||
"server": "time.apple.com",
|
||||
"server_port": 123,
|
||||
"interval": "30m",
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
#### enabled
|
||||
|
||||
Enable NTP service.
|
||||
|
||||
#### server
|
||||
|
||||
==Required==
|
||||
|
||||
NTP server address.
|
||||
|
||||
#### server_port
|
||||
|
||||
NTP server port.
|
||||
|
||||
123 is used by default.
|
||||
|
||||
#### interval
|
||||
|
||||
Time synchronization interval.
|
||||
|
||||
30 minutes is used by default.
|
||||
|
||||
### Dial Fields
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial) for details.
|
||||
49
docs/configuration/ntp/index.zh.md
Normal file
49
docs/configuration/ntp/index.zh.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# NTP
|
||||
|
||||
内建的 NTP 客户端服务。
|
||||
|
||||
如果启用,它将为像 TLS/Shadowsocks/VMess 这样的协议提供时间,这对于无法进行时间同步的环境很有用。
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"ntp": {
|
||||
"enabled": false,
|
||||
"server": "time.apple.com",
|
||||
"server_port": 123,
|
||||
"interval": "30m",
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 字段
|
||||
|
||||
#### enabled
|
||||
|
||||
启用 NTP 服务。
|
||||
|
||||
#### server
|
||||
|
||||
==必填==
|
||||
|
||||
NTP 服务器地址。
|
||||
|
||||
#### server_port
|
||||
|
||||
NTP 服务器端口。
|
||||
|
||||
默认使用 123。
|
||||
|
||||
#### interval
|
||||
|
||||
时间同步间隔。
|
||||
|
||||
默认使用 30 分钟。
|
||||
|
||||
### 拨号字段
|
||||
|
||||
参阅 [拨号字段](/zh/configuration/shared/dial/)。
|
||||
@@ -28,6 +28,7 @@
|
||||
| `hysteria` | [Hysteria](./hysteria) |
|
||||
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
|
||||
| `vless` | [VLESS](./vless) |
|
||||
| `shadowtls` | [ShadowTLS](./shadowtls) |
|
||||
| `tor` | [Tor](./tor) |
|
||||
| `ssh` | [SSH](./ssh) |
|
||||
| `dns` | [DNS](./dns) |
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"version": 2,
|
||||
"version": 3,
|
||||
"password": "fuck me till the daylight",
|
||||
"tls": {},
|
||||
|
||||
@@ -37,12 +37,13 @@ ShadowTLS protocol version.
|
||||
|---------------|-----------------------------------------------------------------------------------------|
|
||||
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
|
||||
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
|
||||
| `3` | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |
|
||||
|
||||
#### password
|
||||
|
||||
Set password.
|
||||
|
||||
Only available in the ShadowTLS v2 protocol.
|
||||
Only available in the ShadowTLS v2/v3 protocol.
|
||||
|
||||
#### tls
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"version": 2,
|
||||
"version": 3,
|
||||
"password": "fuck me till the daylight",
|
||||
"tls": {},
|
||||
|
||||
@@ -37,12 +37,13 @@ ShadowTLS 协议版本。
|
||||
|---------------|-----------------------------------------------------------------------------------------|
|
||||
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
|
||||
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
|
||||
| `3` | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |
|
||||
|
||||
#### password
|
||||
|
||||
设置密码。
|
||||
|
||||
仅在 ShadowTLS v2 协议中可用。
|
||||
仅在 ShadowTLS v2/v3 协议中可用。
|
||||
|
||||
#### tls
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
"private_key": "",
|
||||
"private_key_path": "$HOME/.ssh/id_rsa",
|
||||
"private_key_passphrase": "",
|
||||
"host_key": [
|
||||
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdH..."
|
||||
],
|
||||
"host_key_algorithms": [],
|
||||
"client_version": "SSH-2.0-OpenSSH_7.4p1",
|
||||
|
||||
@@ -51,6 +54,10 @@ Private key path.
|
||||
|
||||
Private key passphrase.
|
||||
|
||||
#### host_key
|
||||
|
||||
Host key. Accept any if empty.
|
||||
|
||||
#### host_key_algorithms
|
||||
|
||||
Host key algorithms.
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
"private_key": "",
|
||||
"private_key_path": "$HOME/.ssh/id_rsa",
|
||||
"private_key_passphrase": "",
|
||||
"host_key": [
|
||||
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdH..."
|
||||
],
|
||||
"host_key_algorithms": [],
|
||||
"client_version": "SSH-2.0-OpenSSH_7.4p1",
|
||||
|
||||
@@ -51,6 +54,10 @@ SSH 用户, 默认使用 root。
|
||||
|
||||
密钥密码。
|
||||
|
||||
#### host_key
|
||||
|
||||
主机密钥,留空接受所有。
|
||||
|
||||
#### host_key_algorithms
|
||||
|
||||
主机密钥算法。
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||
"flow": "xtls-rprx-vision",
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"packet_encoding": "",
|
||||
@@ -17,10 +18,6 @@
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning ""
|
||||
|
||||
The VLESS protocol is architecturally coupled to v2ray and is unmaintained. This outbound is provided for compatibility purposes only.
|
||||
|
||||
### Fields
|
||||
|
||||
#### server
|
||||
@@ -41,6 +38,14 @@ The server port.
|
||||
|
||||
The VLESS user id.
|
||||
|
||||
#### flow
|
||||
|
||||
VLESS Sub-protocol.
|
||||
|
||||
Available values:
|
||||
|
||||
* `xtls-rprx-vision`
|
||||
|
||||
#### network
|
||||
|
||||
Enabled network
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||
"flow": "xtls-rprx-vision",
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"packet_encoding": "",
|
||||
@@ -17,10 +18,6 @@
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning ""
|
||||
|
||||
VLESS 协议与 v2ray 架构耦合且无人维护。 提供此出站仅出于兼容性目的。
|
||||
|
||||
### 字段
|
||||
|
||||
#### server
|
||||
@@ -41,6 +38,14 @@
|
||||
|
||||
VLESS 用户 ID。
|
||||
|
||||
#### flow
|
||||
|
||||
VLESS 子协议。
|
||||
|
||||
可用值:
|
||||
|
||||
* `xtls-rprx-vision`
|
||||
|
||||
#### network
|
||||
|
||||
启用的网络协议。
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
|
||||
"peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=",
|
||||
"pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=",
|
||||
"reserved": [0, 0, 0],
|
||||
"workers": 4,
|
||||
"mtu": 1408,
|
||||
"network": "tcp",
|
||||
|
||||
@@ -83,9 +85,21 @@ WireGuard peer public key.
|
||||
|
||||
WireGuard pre-shared key.
|
||||
|
||||
#### reserved
|
||||
|
||||
WireGuard reserved field bytes.
|
||||
|
||||
#### workers
|
||||
|
||||
WireGuard worker count.
|
||||
|
||||
CPU count is used by default.
|
||||
|
||||
#### mtu
|
||||
|
||||
WireGuard MTU. 1408 will be used if empty.
|
||||
WireGuard MTU.
|
||||
|
||||
1408 will be used if empty.
|
||||
|
||||
#### network
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
|
||||
"peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=",
|
||||
"pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=",
|
||||
"reserved": [0, 0, 0],
|
||||
"workers": 4,
|
||||
"mtu": 1408,
|
||||
"network": "tcp",
|
||||
|
||||
@@ -85,9 +87,21 @@ WireGuard 对等公钥。
|
||||
|
||||
WireGuard 预共享密钥。
|
||||
|
||||
#### reserved
|
||||
|
||||
WireGuard 保留字段字节。
|
||||
|
||||
#### workers
|
||||
|
||||
WireGuard worker 数量。
|
||||
|
||||
默认使用 CPU 数量。
|
||||
|
||||
#### mtu
|
||||
|
||||
WireGuard MTU。 默认1408。
|
||||
WireGuard MTU。
|
||||
|
||||
默认使用 1408。
|
||||
|
||||
#### network
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
{
|
||||
"detour": "upstream-out",
|
||||
"bind_interface": "en0",
|
||||
"bind_address": "0.0.0.0",
|
||||
"inet4_bind_address": "0.0.0.0",
|
||||
"inet6_bind_address": "::",
|
||||
"routing_mark": 1234,
|
||||
"reuse_addr": false,
|
||||
"connect_timeout": "5s",
|
||||
@@ -17,9 +18,9 @@
|
||||
|
||||
### Fields
|
||||
|
||||
| Field | Available Context |
|
||||
|---------------------------------------------------------------------------------------------------------------------|-------------------|
|
||||
| `bind_interface` /`bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open`/ `udp_fragment` /`connect_timeout` | `detour` not set |
|
||||
| Field | Available Context |
|
||||
|----------------------------------------------------------------------------------------------------------------------|-------------------|
|
||||
| `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open`/ `udp_fragment` /`connect_timeout` | `detour` not set |
|
||||
|
||||
#### detour
|
||||
|
||||
@@ -29,9 +30,13 @@ The tag of the upstream outbound.
|
||||
|
||||
The network interface to bind to.
|
||||
|
||||
#### bind_address
|
||||
#### inet4_bind_address
|
||||
|
||||
The address to bind to.
|
||||
The IPv4 address to bind to.
|
||||
|
||||
#### inet6_bind_address
|
||||
|
||||
The IPv6 address to bind to.
|
||||
|
||||
#### routing_mark
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
{
|
||||
"detour": "upstream-out",
|
||||
"bind_interface": "en0",
|
||||
"bind_address": "0.0.0.0",
|
||||
"inet4_bind_address": "0.0.0.0",
|
||||
"inet6_bind_address": "::",
|
||||
"routing_mark": 1234,
|
||||
"reuse_addr": false,
|
||||
"connect_timeout": "5s",
|
||||
@@ -17,9 +18,9 @@
|
||||
|
||||
### 字段
|
||||
|
||||
| 字段 | 可用上下文 |
|
||||
|---------------------------------------------------------------------------------------------------------------------|--------------|
|
||||
| `bind_interface` /`bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open`/ `udp_fragment` /`connect_timeout` | `detour` 未设置 |
|
||||
| 字段 | 可用上下文 |
|
||||
|----------------------------------------------------------------------------------------------------------------------|--------------|
|
||||
| `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open`/ `udp_fragment` /`connect_timeout` | `detour` 未设置 |
|
||||
|
||||
|
||||
#### detour
|
||||
@@ -32,9 +33,13 @@
|
||||
|
||||
要绑定到的网络接口。
|
||||
|
||||
#### bind_address
|
||||
#### inet4_bind_address
|
||||
|
||||
要绑定的地址。
|
||||
要绑定的 IPv4 地址。
|
||||
|
||||
#### inet6_bind_address
|
||||
|
||||
要绑定的 IPv6 地址。
|
||||
|
||||
#### routing_mark
|
||||
|
||||
|
||||
@@ -26,6 +26,20 @@
|
||||
"key_id": "",
|
||||
"mac_key": ""
|
||||
}
|
||||
},
|
||||
"reality": {
|
||||
"enabled": false,
|
||||
"handshake": {
|
||||
"server": "google.com",
|
||||
"server_port": 443,
|
||||
|
||||
... // Dial Fields
|
||||
},
|
||||
"private_key": "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
|
||||
"short_id": [
|
||||
"0123456789abcdef"
|
||||
],
|
||||
"max_time_difference": "1m"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -53,6 +67,11 @@
|
||||
"utls": {
|
||||
"enabled": false,
|
||||
"fingerprint": ""
|
||||
},
|
||||
"reality": {
|
||||
"enabled": false,
|
||||
"public_key": "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
|
||||
"short_id": "0123456789abcdef"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -192,10 +211,15 @@ Available fingerprint values:
|
||||
|
||||
* chrome
|
||||
* firefox
|
||||
* edge
|
||||
* safari
|
||||
* 360
|
||||
* qq
|
||||
* ios
|
||||
* android
|
||||
* random
|
||||
|
||||
Chrome fingerprint will be used if empty.
|
||||
|
||||
### ACME Fields
|
||||
|
||||
@@ -270,6 +294,54 @@ The key identifier.
|
||||
|
||||
The MAC key.
|
||||
|
||||
### Reality Fields
|
||||
|
||||
!!! warning ""
|
||||
|
||||
reality server is not included by default, see [Installation](/#installation).
|
||||
|
||||
!!! warning ""
|
||||
|
||||
uTLS, which is required by reality client is not included by default, see [Installation](/#installation).
|
||||
|
||||
#### handshake
|
||||
|
||||
==Server only==
|
||||
|
||||
==Required==
|
||||
|
||||
Handshake server address and [Dial options](/configuration/shared/dial).
|
||||
|
||||
#### private_key
|
||||
|
||||
==Server only==
|
||||
|
||||
==Required==
|
||||
|
||||
Private key, generated by `./xray x25519`.
|
||||
|
||||
#### public_key
|
||||
|
||||
==Client only==
|
||||
|
||||
==Required==
|
||||
|
||||
Public key, generated by `./xray x25519`.
|
||||
|
||||
#### short_id
|
||||
|
||||
==Required==
|
||||
|
||||
A 8-bit hex string.
|
||||
|
||||
#### max_time_difference
|
||||
|
||||
==Server only==
|
||||
|
||||
The maximum time difference between the server and the client.
|
||||
|
||||
Check disabled if empty.
|
||||
|
||||
### Reload
|
||||
|
||||
For server configuration, certificate and key will be automatically reloaded if modified.
|
||||
@@ -26,6 +26,20 @@
|
||||
"key_id": "",
|
||||
"mac_key": ""
|
||||
}
|
||||
},
|
||||
"reality": {
|
||||
"enabled": false,
|
||||
"handshake": {
|
||||
"server": "google.com",
|
||||
"server_port": 443,
|
||||
|
||||
... // 拨号字段
|
||||
},
|
||||
"private_key": "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
|
||||
"short_id": [
|
||||
"0123456789abcdef"
|
||||
],
|
||||
"max_time_difference": "1m"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -53,6 +67,11 @@
|
||||
"utls": {
|
||||
"enabled": false,
|
||||
"fingerprint": ""
|
||||
},
|
||||
"reality": {
|
||||
"enabled": false,
|
||||
"public_key": "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
|
||||
"short_id": "0123456789abcdef"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -192,10 +211,16 @@ uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻
|
||||
|
||||
* chrome
|
||||
* firefox
|
||||
* edge
|
||||
* safari
|
||||
* 360
|
||||
* qq
|
||||
* ios
|
||||
* android
|
||||
* random
|
||||
|
||||
默认使用 chrome 指纹。
|
||||
|
||||
### ACME 字段
|
||||
|
||||
!!! warning ""
|
||||
@@ -265,6 +290,52 @@ EAB(外部帐户绑定)包含将 ACME 帐户绑定或映射到其他已知
|
||||
|
||||
MAC 密钥。
|
||||
|
||||
### Reality 字段
|
||||
|
||||
!!! warning ""
|
||||
|
||||
默认安装不包含 reality 服务器,参阅 [安装](/zh/#_2)。
|
||||
|
||||
!!! warning ""
|
||||
|
||||
默认安装不包含被 reality 客户端需要的 uTLS, 参阅 [安装](/zh/#_2)。
|
||||
|
||||
#### handshake
|
||||
|
||||
==仅服务器==
|
||||
|
||||
==必填==
|
||||
|
||||
握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
|
||||
|
||||
#### private_key
|
||||
|
||||
==仅服务器==
|
||||
|
||||
==必填==
|
||||
|
||||
私钥,由 `./xray x25519` 生成。
|
||||
|
||||
#### public_key
|
||||
|
||||
==仅客户端==
|
||||
|
||||
==必填==
|
||||
|
||||
公钥,由 `./xray x25519` 生成。
|
||||
|
||||
#### short_id
|
||||
|
||||
==必填==
|
||||
|
||||
一个八位十六进制的字符串。
|
||||
|
||||
#### max_time_difference
|
||||
|
||||
服务器与和客户端之间允许的最大时间差。
|
||||
|
||||
默认禁用检查。
|
||||
|
||||
### 重载
|
||||
|
||||
对于服务器配置,如果修改,证书和密钥将自动重新加载。
|
||||
52
docs/examples/clash-api.md
Normal file
52
docs/examples/clash-api.md
Normal file
@@ -0,0 +1,52 @@
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"rules": [
|
||||
{
|
||||
"domain": [
|
||||
"clash.razord.top",
|
||||
"yacd.haishan.me"
|
||||
],
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "direct",
|
||||
"server": "local"
|
||||
}
|
||||
]
|
||||
},
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "selector",
|
||||
"tag": "default",
|
||||
"outbounds": [
|
||||
"proxy-a",
|
||||
"proxy-b"
|
||||
]
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"clash_mode": "direct",
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"domain": [
|
||||
"clash.razord.top",
|
||||
"yacd.haishan.me"
|
||||
],
|
||||
"outbound": "direct"
|
||||
}
|
||||
],
|
||||
"final": "default"
|
||||
},
|
||||
"experimental": {
|
||||
"clash_api": {
|
||||
"external_controller": "127.0.0.1:9090",
|
||||
"store_selected": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
@@ -3,7 +3,8 @@
|
||||
Configuration examples for sing-box.
|
||||
|
||||
* [Linux Server Installation](./linux-server-installation)
|
||||
* [Shadowsocks Server](./ss-server)
|
||||
* [Shadowsocks Client](./ss-client)
|
||||
* [Shadowsocks Tun](./ss-tun)
|
||||
* [DNS Hijack](./dns-hijack.md)
|
||||
* [Tun](./tun)
|
||||
* [DNS Hijack](./dns-hijack.md)
|
||||
* [Shadowsocks](./shadowsocks)
|
||||
* [ShadowTLS](./shadowtls)
|
||||
* [Clash API](./clash-api)
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
sing-box 的配置示例。
|
||||
|
||||
* [Linux 服务器安装](./linux-server-installation)
|
||||
* [Shadowsocks 服务器](./ss-server)
|
||||
* [Shadowsocks 客户端](./ss-client)
|
||||
* [Shadowsocks Tun](./ss-tun)
|
||||
* [DNS 劫持](./dns-hijack.md)
|
||||
* [Tun](./tun)
|
||||
* [DNS 劫持](./dns-hijack.md)
|
||||
* [Shadowsocks](./shadowsocks)
|
||||
* [ShadowTLS](./shadowtls)
|
||||
* [Clash API](./clash-api)
|
||||
|
||||
157
docs/examples/shadowsocks.md
Normal file
157
docs/examples/shadowsocks.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# Shadowsocks
|
||||
|
||||
## Single User
|
||||
|
||||
#### Server
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "shadowsocks",
|
||||
"listen": "::",
|
||||
"listen_port": 8080,
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Client
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "mixed",
|
||||
"listen": "::",
|
||||
"listen_port": 2080
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "shadowsocks",
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8080,
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Multiple Users
|
||||
|
||||
#### Server
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "shadowsocks",
|
||||
"listen": "::",
|
||||
"listen_port": 8080,
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"password": "BXYxVUXJ9NgF7c7KPLQjkg=="
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Client
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "mixed",
|
||||
"listen": "::",
|
||||
"listen_port": 2080
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "shadowsocks",
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8080,
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==:BXYxVUXJ9NgF7c7KPLQjkg=="
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Relay
|
||||
|
||||
#### Server
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "shadowsocks",
|
||||
"listen": "::",
|
||||
"listen_port": 8080,
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Relay
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "shadowsocks",
|
||||
"listen": "::",
|
||||
"listen_port": 8081,
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "BXYxVUXJ9NgF7c7KPLQjkg==",
|
||||
"destinations": [
|
||||
{
|
||||
"name": "my_server",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8080
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Client
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "mixed",
|
||||
"listen": "::",
|
||||
"listen_port": 2080
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "shadowsocks",
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8081,
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==:BXYxVUXJ9NgF7c7KPLQjkg=="
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
@@ -7,6 +7,13 @@
|
||||
"type": "shadowtls",
|
||||
"listen": "::",
|
||||
"listen_port": 4443,
|
||||
"version": 3,
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
}
|
||||
],
|
||||
"handshake": {
|
||||
"server": "google.com",
|
||||
"server_port": 443
|
||||
@@ -45,9 +52,15 @@
|
||||
"tag": "shadowtls-out",
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 4443,
|
||||
"version": 3,
|
||||
"password": "fuck me till the daylight",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "google.com"
|
||||
"server_name": "google.com",
|
||||
"utls": {
|
||||
"enabled": true,
|
||||
"fingerprint": "chrome"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "mixed",
|
||||
"listen": "::",
|
||||
"listen_port": 2080
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "shadowsocks",
|
||||
"server": "::",
|
||||
"server_port": 8080,
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
@@ -1,13 +0,0 @@
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "shadowsocks",
|
||||
"listen": "::",
|
||||
"listen_port": 8080,
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -10,9 +10,18 @@
|
||||
"tag": "local",
|
||||
"address": "223.5.5.5",
|
||||
"detour": "direct"
|
||||
},
|
||||
{
|
||||
"tag": "block",
|
||||
"address": "rcode://success"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"geosite": "category-ads-all",
|
||||
"server": "block",
|
||||
"disable_cache": true
|
||||
},
|
||||
{
|
||||
"domain": "mydomain.com",
|
||||
"geosite": "cn",
|
||||
@@ -26,6 +35,7 @@
|
||||
"type": "tun",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"auto_route": true,
|
||||
"strict_route": false,
|
||||
"sniff": true
|
||||
}
|
||||
],
|
||||
@@ -58,13 +68,16 @@
|
||||
"outbound": "dns-out"
|
||||
},
|
||||
{
|
||||
"geosite": "category-ads-all",
|
||||
"outbound": "block"
|
||||
"geosite": "cn",
|
||||
"geoip": [
|
||||
"private",
|
||||
"cn"
|
||||
],
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"geosite": "cn",
|
||||
"geoip": "cn",
|
||||
"outbound": "direct"
|
||||
"geosite": "category-ads-all",
|
||||
"outbound": "block"
|
||||
}
|
||||
],
|
||||
"auto_detect_interface": true
|
||||
@@ -7,11 +7,11 @@ the public internet.
|
||||
|
||||
##### on Android
|
||||
|
||||
`auto-route` cannot automatically hijack DNS requests when Android's `Private DNS` is enabled.
|
||||
`auto-route` cannot automatically hijack DNS requests when Android's `Private DNS` enabled or `strict_route` disabled.
|
||||
|
||||
##### on Linux
|
||||
|
||||
`auto-route` cannot automatically hijack DNS requests with `systemd-resolved` enabled, you can switch to NetworkManager.
|
||||
`auto-route` cannot automatically hijack DNS requests with `systemd-resolved` enabled and `strict_route` disabled.
|
||||
|
||||
#### System proxy
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
##### Android
|
||||
|
||||
`auto-route` 无法自动劫持 DNS 请求如果 `私人 DNS` 开启.
|
||||
`auto-route` 无法自动劫持 DNS 请求如果 `私人 DNS` 开启或 `strict_route` 禁用。
|
||||
|
||||
##### Linux
|
||||
|
||||
`auto-route` 无法自动劫持 DNS 请求如果 `systemd-resolved` 开启, 您可以切换到 NetworkManager.
|
||||
`auto-route` 无法自动劫持 DNS 请求如果 `systemd-resolved` 开启且 `strict_route` 禁用。
|
||||
|
||||
#### 系统代理
|
||||
|
||||
|
||||
@@ -24,8 +24,9 @@ go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@lat
|
||||
|
||||
| Build Tag | Description |
|
||||
|------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `with_quic` | 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_quic` | 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` | Build with standard gRPC support, see [V2Ray Transport#gRPC](./configuration/shared/v2ray-transport#grpc). |
|
||||
| `with_dhcp` | Build with DHCP support, see [DHCP DNS transport](./configuration/dns/server). |
|
||||
| `with_wireguard` | Build with WireGuard support, see [WireGuard outbound](./configuration/outbound/wireguard). |
|
||||
| `with_shadowsocksr` | Build with ShadowsocksR support, see [ShadowsocksR outbound](./configuration/outbound/shadowsocksr). |
|
||||
| `with_ech` | Build with TLS ECH extension support for TLS outbound, see [TLS](./configuration/shared/tls#ech). |
|
||||
|
||||
@@ -26,6 +26,7 @@ go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@lat
|
||||
|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `with_quic` | 启用 QUIC 支持,参阅 [QUIC 和 HTTP3 DNS 传输层](./configuration/dns/server),[Naive 入站](./configuration/inbound/naive),[Hysteria 入站](./configuration/inbound/hysteria),[Hysteria 出站](./configuration/outbound/hysteria) 和 [V2Ray 传输层#QUIC](./configuration/shared/v2ray-transport#quic)。 |
|
||||
| `with_grpc` | 启用标准 gRPC 支持,参阅 [V2Ray 传输层#gRPC](./configuration/shared/v2ray-transport#grpc)。 |
|
||||
| `with_dhcp` | 启用 DHCP 支持,参阅 [DHCP DNS 传输层](./configuration/dns/server)。 |
|
||||
| `with_wireguard` | 启用 WireGuard 支持,参阅 [WireGuard 出站](./configuration/outbound/wireguard)。 |
|
||||
| `with_shadowsocksr` | 启用 ShadowsocksR 支持,参阅 [ShadowsocksR 出站](./configuration/outbound/shadowsocksr)。 |
|
||||
| `with_ech` | 启用 TLS ECH 扩展支持,参阅 [TLS](./configuration/shared/tls#ech)。 |
|
||||
|
||||
@@ -59,3 +59,7 @@ func (c *CacheFile) StoreSelected(group, selected string) error {
|
||||
return bucket.Put([]byte(group), []byte(selected))
|
||||
})
|
||||
}
|
||||
|
||||
func (c *CacheFile) Close() error {
|
||||
return c.DB.Close()
|
||||
}
|
||||
|
||||
82
experimental/clashapi/dns.go
Normal file
82
experimental/clashapi/dns.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package clashapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func dnsRouter(router adapter.Router) http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/query", queryDNS(router))
|
||||
return r
|
||||
}
|
||||
|
||||
func queryDNS(router adapter.Router) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.URL.Query().Get("name")
|
||||
qTypeStr := r.URL.Query().Get("type")
|
||||
if qTypeStr == "" {
|
||||
qTypeStr = "A"
|
||||
}
|
||||
|
||||
qType, exist := dns.StringToType[qTypeStr]
|
||||
if !exist {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, newError("invalid query type"))
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.DNSTimeout)
|
||||
defer cancel()
|
||||
|
||||
msg := dns.Msg{}
|
||||
msg.SetQuestion(dns.Fqdn(name), qType)
|
||||
resp, err := router.Exchange(ctx, &msg)
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
render.JSON(w, r, newError(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
responseData := render.M{
|
||||
"Status": resp.Rcode,
|
||||
"Question": resp.Question,
|
||||
"Server": "internal",
|
||||
"TC": resp.Truncated,
|
||||
"RD": resp.RecursionDesired,
|
||||
"RA": resp.RecursionAvailable,
|
||||
"AD": resp.AuthenticatedData,
|
||||
"CD": resp.CheckingDisabled,
|
||||
}
|
||||
|
||||
rr2Json := func(rr dns.RR) render.M {
|
||||
header := rr.Header()
|
||||
return render.M{
|
||||
"name": header.Name,
|
||||
"type": header.Rrtype,
|
||||
"TTL": header.Ttl,
|
||||
"data": rr.String()[len(header.String()):],
|
||||
}
|
||||
}
|
||||
|
||||
if len(resp.Answer) > 0 {
|
||||
responseData["Answer"] = common.Map(resp.Answer, rr2Json)
|
||||
}
|
||||
if len(resp.Ns) > 0 {
|
||||
responseData["Authority"] = common.Map(resp.Ns, rr2Json)
|
||||
}
|
||||
if len(resp.Extra) > 0 {
|
||||
responseData["Additional"] = common.Map(resp.Extra, rr2Json)
|
||||
}
|
||||
|
||||
render.JSON(w, r, responseData)
|
||||
}
|
||||
}
|
||||
@@ -42,8 +42,6 @@ type Server struct {
|
||||
httpServer *http.Server
|
||||
trafficManager *trafficontrol.Manager
|
||||
urlTestHistory *urltest.HistoryStorage
|
||||
tcpListener net.Listener
|
||||
directIO bool
|
||||
mode string
|
||||
storeSelected bool
|
||||
cacheFile adapter.ClashCacheFile
|
||||
@@ -61,7 +59,6 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
||||
},
|
||||
trafficManager: trafficManager,
|
||||
urlTestHistory: urltest.NewHistoryStorage(),
|
||||
directIO: options.DirectIO,
|
||||
mode: strings.ToLower(options.DefaultMode),
|
||||
}
|
||||
if server.mode == "" {
|
||||
@@ -73,6 +70,11 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
||||
if cachePath == "" {
|
||||
cachePath = "cache.db"
|
||||
}
|
||||
if foundPath, loaded := C.FindPath(cachePath); loaded {
|
||||
cachePath = foundPath
|
||||
} else {
|
||||
cachePath = C.BasePath(cachePath)
|
||||
}
|
||||
cacheFile, err := cachefile.Open(cachePath)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "open cache file")
|
||||
@@ -101,10 +103,11 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
||||
r.Mount("/script", scriptRouter())
|
||||
r.Mount("/profile", profileRouter())
|
||||
r.Mount("/cache", cacheRouter())
|
||||
r.Mount("/dns", dnsRouter(router))
|
||||
})
|
||||
if options.ExternalUI != "" {
|
||||
chiRouter.Group(func(r chi.Router) {
|
||||
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(os.ExpandEnv(options.ExternalUI))))
|
||||
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(C.BasePath(os.ExpandEnv(options.ExternalUI)))))
|
||||
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
|
||||
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
|
||||
fs.ServeHTTP(w, r)
|
||||
@@ -120,7 +123,6 @@ func (s *Server) Start() error {
|
||||
return E.Cause(err, "external controller listen error")
|
||||
}
|
||||
s.logger.Info("restful api listening at ", listener.Addr())
|
||||
s.tcpListener = listener
|
||||
go func() {
|
||||
err = s.httpServer.Serve(listener)
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
@@ -133,7 +135,6 @@ func (s *Server) Start() error {
|
||||
func (s *Server) Close() error {
|
||||
return common.Close(
|
||||
common.PtrOrNil(s.httpServer),
|
||||
s.tcpListener,
|
||||
s.trafficManager,
|
||||
s.cacheFile,
|
||||
)
|
||||
@@ -156,7 +157,7 @@ func (s *Server) HistoryStorage() *urltest.HistoryStorage {
|
||||
}
|
||||
|
||||
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, s.directIO)
|
||||
tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule)
|
||||
return tracker, tracker
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ func (tt *tcpTracker) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule, directIO bool) *tcpTracker {
|
||||
func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule) *tcpTracker {
|
||||
uuid, _ := uuid.NewV4()
|
||||
|
||||
var chain []string
|
||||
@@ -107,7 +107,7 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router ad
|
||||
}, func(n int64) {
|
||||
download.Add(n)
|
||||
manager.PushDownloaded(n)
|
||||
}, directIO),
|
||||
}),
|
||||
manager: manager,
|
||||
trackerInfo: &trackerInfo{
|
||||
UUID: uuid,
|
||||
|
||||
15
experimental/libbox/config.go
Normal file
15
experimental/libbox/config.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func parseConfig(configContent string) (option.Options, error) {
|
||||
var options option.Options
|
||||
err := options.UnmarshalJSON([]byte(configContent))
|
||||
if err != nil {
|
||||
return option.Options{}, E.Cause(err, "decode config")
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
148
experimental/libbox/internal/procfs/procfs.go
Normal file
148
experimental/libbox/internal/procfs/procfs.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package procfs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
var (
|
||||
netIndexOfLocal = -1
|
||||
netIndexOfUid = -1
|
||||
nativeEndian binary.ByteOrder
|
||||
)
|
||||
|
||||
func init() {
|
||||
var x uint32 = 0x01020304
|
||||
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
|
||||
nativeEndian = binary.BigEndian
|
||||
} else {
|
||||
nativeEndian = binary.LittleEndian
|
||||
}
|
||||
}
|
||||
|
||||
func ResolveSocketByProcSearch(network string, source, _ netip.AddrPort) int32 {
|
||||
if netIndexOfLocal < 0 || netIndexOfUid < 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
path := "/proc/net/"
|
||||
|
||||
if network == N.NetworkTCP {
|
||||
path += "tcp"
|
||||
} else {
|
||||
path += "udp"
|
||||
}
|
||||
|
||||
if source.Addr().Is6() {
|
||||
path += "6"
|
||||
}
|
||||
|
||||
sIP := source.Addr().AsSlice()
|
||||
if len(sIP) == 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
var bytes [2]byte
|
||||
binary.BigEndian.PutUint16(bytes[:], source.Port())
|
||||
local := fmt.Sprintf("%s:%s", hex.EncodeToString(nativeEndianIP(sIP)), hex.EncodeToString(bytes[:]))
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
reader := bufio.NewReader(file)
|
||||
|
||||
for {
|
||||
row, _, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
fields := strings.Fields(string(row))
|
||||
|
||||
if len(fields) <= netIndexOfLocal || len(fields) <= netIndexOfUid {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.EqualFold(local, fields[netIndexOfLocal]) {
|
||||
uid, err := strconv.Atoi(fields[netIndexOfUid])
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
return int32(uid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func nativeEndianIP(ip net.IP) []byte {
|
||||
result := make([]byte, len(ip))
|
||||
|
||||
for i := 0; i < len(ip); i += 4 {
|
||||
value := binary.BigEndian.Uint32(ip[i:])
|
||||
|
||||
nativeEndian.PutUint32(result[i:], value)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func init() {
|
||||
file, err := os.Open("/proc/net/tcp")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
reader := bufio.NewReader(file)
|
||||
|
||||
header, _, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
columns := strings.Fields(string(header))
|
||||
|
||||
var txQueue, rxQueue, tr, tmWhen bool
|
||||
|
||||
for idx, col := range columns {
|
||||
offset := 0
|
||||
|
||||
if txQueue && rxQueue {
|
||||
offset--
|
||||
}
|
||||
|
||||
if tr && tmWhen {
|
||||
offset--
|
||||
}
|
||||
|
||||
switch col {
|
||||
case "tx_queue":
|
||||
txQueue = true
|
||||
case "rx_queue":
|
||||
rxQueue = true
|
||||
case "tr":
|
||||
tr = true
|
||||
case "tm->when":
|
||||
tmWhen = true
|
||||
case "local_address":
|
||||
netIndexOfLocal = idx + offset
|
||||
case "uid":
|
||||
netIndexOfUid = idx + offset
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user