mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
126 Commits
v1.1
...
v1.2-beta7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87dd328700 | ||
|
|
0cec92dd0f | ||
|
|
e88afa9665 | ||
|
|
3883a81315 | ||
|
|
c919ad079a | ||
|
|
83593aee70 | ||
|
|
ac7cc09694 | ||
|
|
d032e3568b | ||
|
|
c24df037ac | ||
|
|
a2d43b3746 | ||
|
|
5b3b74bd0f | ||
|
|
d24d3b26dc | ||
|
|
5db3cd7781 | ||
|
|
c88af8b081 | ||
|
|
45852ca3e7 | ||
|
|
03ce555104 | ||
|
|
dd0a07624e | ||
|
|
b9b2b77814 | ||
|
|
2366835121 | ||
|
|
42e1dea7d2 | ||
|
|
13d7716b02 | ||
|
|
7ecb9fc738 | ||
|
|
19b15e0d10 | ||
|
|
0b15de461b | ||
|
|
27aba99e6c | ||
|
|
8151bcfd6b | ||
|
|
e8802357e1 | ||
|
|
6e22c004f6 | ||
|
|
20e1caa531 | ||
|
|
32ad3c3db3 | ||
|
|
1f5f8a7dde | ||
|
|
6da1460795 | ||
|
|
b14ae51f71 | ||
|
|
5af8d001ae | ||
|
|
0ca344df5f | ||
|
|
49f568abbd | ||
|
|
3b4e811907 | ||
|
|
d0e9443031 | ||
|
|
f7e9d9ab1f | ||
|
|
7834d6bca7 | ||
|
|
ed50257735 | ||
|
|
f15f525c5c | ||
|
|
e4bff0460d | ||
|
|
5ce3ddee9b | ||
|
|
22bf7a9509 | ||
|
|
842730707c | ||
|
|
a8f13bd956 | ||
|
|
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 |
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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
39
.github/workflows/debug.yml
vendored
39
.github/workflows/debug.yml
vendored
@@ -3,8 +3,7 @@ name: Debug build
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main-next
|
||||||
- dev
|
|
||||||
- dev-next
|
- dev-next
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.md'
|
- '**.md'
|
||||||
@@ -12,8 +11,7 @@ on:
|
|||||||
- '!.github/workflows/debug.yml'
|
- '!.github/workflows/debug.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main-next
|
||||||
- dev
|
|
||||||
- dev-next
|
- dev-next
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -21,24 +19,20 @@ jobs:
|
|||||||
name: Debug build
|
name: Debug build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Cancel previous
|
|
||||||
uses: styfle/cancel-workflow-action@0.7.0
|
|
||||||
with:
|
|
||||||
access_token: ${{ github.token }}
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Get latest go version
|
- name: Get latest go version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
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
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.version.outputs.go_version }}
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
- name: Cache go module
|
- name: Cache go module
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/go/pkg/mod
|
~/go/pkg/mod
|
||||||
@@ -60,22 +54,21 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.18.7
|
go-version: 1.18.10
|
||||||
- name: Cache go module
|
- name: Cache go module
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/go/pkg/mod
|
~/go/pkg/mod
|
||||||
key: go118-${{ hashFiles('**/go.sum') }}
|
key: go118-${{ hashFiles('**/go.sum') }}
|
||||||
- name: Run Test
|
- name: Run Test
|
||||||
run: |
|
run: make
|
||||||
go test -v ./...
|
|
||||||
cross:
|
cross:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -192,19 +185,19 @@ jobs:
|
|||||||
TAGS: with_clash_api,with_quic
|
TAGS: with_clash_api,with_quic
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Get latest go version
|
- name: Get latest go version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
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
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.version.outputs.go_version }}
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
- name: Cache go module
|
- name: Cache go module
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/go/pkg/mod
|
~/go/pkg/mod
|
||||||
@@ -213,7 +206,7 @@ jobs:
|
|||||||
id: build
|
id: build
|
||||||
run: make
|
run: make
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: sing-box-${{ matrix.name }}
|
name: sing-box-${{ matrix.name }}
|
||||||
path: sing-box*
|
path: sing-box*
|
||||||
|
|||||||
13
.github/workflows/docker.yml
vendored
13
.github/workflows/docker.yml
vendored
@@ -1,8 +1,5 @@
|
|||||||
name: Build Docker Images
|
name: Build Docker Images
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- v*
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
@@ -12,20 +9,20 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v2
|
||||||
- name: Setup QEMU for Docker Buildx
|
- name: Setup QEMU for Docker Buildx
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Docker metadata
|
- name: Docker metadata
|
||||||
id: metadata
|
id: metadata
|
||||||
uses: docker/metadata-action@v3
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/sagernet/sing-box
|
images: ghcr.io/sagernet/sing-box
|
||||||
- name: Get tag to build
|
- name: Get tag to build
|
||||||
@@ -38,7 +35,7 @@ jobs:
|
|||||||
echo "versioned=ghcr.io/sagernet/sing-box:${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
|
echo "versioned=ghcr.io/sagernet/sing-box:${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
- name: Build and release Docker images
|
- name: Build and release Docker images
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
|
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
|
||||||
target: dist
|
target: dist
|
||||||
|
|||||||
23
.github/workflows/lint.yml
vendored
23
.github/workflows/lint.yml
vendored
@@ -3,45 +3,40 @@ name: Lint
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- main-next
|
||||||
|
- dev-next
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.md'
|
- '**.md'
|
||||||
- '.github/**'
|
- '.github/**'
|
||||||
- '!.github/workflows/debug.yml'
|
- '!.github/workflows/lint.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- main-next
|
||||||
|
- dev-next
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Cancel previous
|
|
||||||
uses: styfle/cancel-workflow-action@0.7.0
|
|
||||||
with:
|
|
||||||
access_token: ${{ github.token }}
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Get latest go version
|
- name: Get latest go version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
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
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.version.outputs.go_version }}
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
- name: Cache go module
|
- name: Cache go module
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/go/pkg/mod
|
~/go/pkg/mod
|
||||||
key: go-${{ hashFiles('**/go.sum') }}
|
key: go-${{ hashFiles('**/go.sum') }}
|
||||||
- name: Get dependencies
|
|
||||||
run: |
|
|
||||||
go mod download -x
|
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
10
.github/workflows/mkdocs.yml
vendored
10
.github/workflows/mkdocs.yml
vendored
@@ -10,9 +10,11 @@ jobs:
|
|||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
- run: pip install mkdocs-material mkdocs-static-i18n
|
- run: |
|
||||||
- run: mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history
|
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:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v5
|
- uses: actions/stale@v7
|
||||||
with:
|
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'
|
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
|
days-before-stale: 60
|
||||||
|
|||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -5,4 +5,10 @@
|
|||||||
/site/
|
/site/
|
||||||
/bin/
|
/bin/
|
||||||
/dist/
|
/dist/
|
||||||
/sing-box
|
/sing-box
|
||||||
|
/sing-box.exe
|
||||||
|
/build/
|
||||||
|
/*.jar
|
||||||
|
/*.aar
|
||||||
|
/*.xcframework/
|
||||||
|
.DS_Store
|
||||||
|
|||||||
@@ -3,19 +3,24 @@ linters:
|
|||||||
enable:
|
enable:
|
||||||
- gofumpt
|
- gofumpt
|
||||||
- govet
|
- govet
|
||||||
# - gci
|
- gci
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- paralleltest
|
- paralleltest
|
||||||
|
|
||||||
run:
|
run:
|
||||||
skip-dirs:
|
skip-dirs:
|
||||||
|
- transport/simple-obfs
|
||||||
|
- transport/clashssr
|
||||||
- transport/cloudflaretls
|
- transport/cloudflaretls
|
||||||
|
- transport/shadowtls/tls
|
||||||
|
- transport/shadowtls/tls_go119
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
# gci:
|
gci:
|
||||||
# sections:
|
custom-order: true
|
||||||
# - standard
|
sections:
|
||||||
# - prefix(github.com/sagernet/)
|
- standard
|
||||||
# - default
|
- prefix(github.com/sagernet/)
|
||||||
|
- default
|
||||||
staticcheck:
|
staticcheck:
|
||||||
go: '1.19'
|
go: '1.20'
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
project_name: sing-box
|
project_name: sing-box
|
||||||
builds:
|
builds:
|
||||||
- main: ./cmd/sing-box
|
- id: main
|
||||||
|
main: ./cmd/sing-box
|
||||||
flags:
|
flags:
|
||||||
- -v
|
- -v
|
||||||
- -trimpath
|
- -trimpath
|
||||||
@@ -19,9 +20,6 @@ builds:
|
|||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
targets:
|
targets:
|
||||||
- android_arm64
|
|
||||||
- android_amd64
|
|
||||||
- android_amd64_v3
|
|
||||||
- linux_amd64_v1
|
- linux_amd64_v1
|
||||||
- linux_amd64_v3
|
- linux_amd64_v3
|
||||||
- linux_arm64
|
- linux_arm64
|
||||||
@@ -35,6 +33,54 @@ builds:
|
|||||||
- darwin_amd64_v3
|
- darwin_amd64_v3
|
||||||
- darwin_arm64
|
- darwin_arm64
|
||||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
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:
|
snapshot:
|
||||||
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
||||||
archives:
|
archives:
|
||||||
@@ -70,6 +116,9 @@ nfpms:
|
|||||||
dst: /etc/systemd/system/sing-box@.service
|
dst: /etc/systemd/system/sing-box@.service
|
||||||
- src: LICENSE
|
- src: LICENSE
|
||||||
dst: /usr/share/licenses/sing-box/LICENSE
|
dst: /usr/share/licenses/sing-box/LICENSE
|
||||||
|
scripts:
|
||||||
|
postinstall: release/config/postinstall.sh
|
||||||
|
postremove: release/config/postremove.sh
|
||||||
source:
|
source:
|
||||||
enabled: false
|
enabled: false
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
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>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
COPY . /go/src/github.com/sagernet/sing-box
|
COPY . /go/src/github.com/sagernet/sing-box
|
||||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||||
|
|||||||
20
Makefile
20
Makefile
@@ -1,7 +1,7 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api
|
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_shadowsocksr
|
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="
|
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid="
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
|
|
||||||
@@ -16,11 +16,11 @@ install:
|
|||||||
fmt:
|
fmt:
|
||||||
@gofumpt -l -w .
|
@gofumpt -l -w .
|
||||||
@gofmt -s -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:
|
fmt_install:
|
||||||
go install -v mvdan.cc/gofumpt@latest
|
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:
|
lint:
|
||||||
GOOS=linux golangci-lint run ./...
|
GOOS=linux golangci-lint run ./...
|
||||||
@@ -42,14 +42,14 @@ proto_install:
|
|||||||
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||||
|
|
||||||
snapshot:
|
snapshot:
|
||||||
goreleaser release --rm-dist --snapshot
|
go run ./cmd/internal/build goreleaser release --rm-dist --snapshot || exit 1
|
||||||
mkdir dist/release
|
mkdir dist/release
|
||||||
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
|
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
|
||||||
ghr --delete --draft --prerelease -p 1 nightly dist/release
|
ghr --delete --draft --prerelease -p 1 nightly dist/release
|
||||||
rm -r dist
|
rm -r dist
|
||||||
|
|
||||||
release:
|
release:
|
||||||
goreleaser release --rm-dist --skip-publish
|
go run ./cmd/internal/build goreleaser release --rm-dist --skip-publish || exit 1
|
||||||
mkdir dist/release
|
mkdir dist/release
|
||||||
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm 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
|
ghr --delete --draft --prerelease -p 3 $(shell git describe --tags) dist/release
|
||||||
@@ -71,6 +71,14 @@ test_stdio:
|
|||||||
go mod tidy && \
|
go mod tidy && \
|
||||||
go test -v -tags "$(TAGS_TEST),force_stdio" .
|
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:
|
clean:
|
||||||
rm -rf bin dist sing-box
|
rm -rf bin dist sing-box
|
||||||
rm -f $(shell go env GOPATH)/sing-box
|
rm -f $(shell go env GOPATH)/sing-box
|
||||||
|
|||||||
@@ -45,6 +45,6 @@ type V2RayServer interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type V2RayStatsService interface {
|
type V2RayStatsService interface {
|
||||||
RoutedConnection(inbound string, outbound string, conn net.Conn) net.Conn
|
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
|
||||||
RoutedPacketConnection(inbound string, outbound string, conn N.PacketConn) N.PacketConn
|
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ type InboundContext struct {
|
|||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
ProcessInfo *process.Info
|
ProcessInfo *process.Info
|
||||||
|
|
||||||
|
// dns cache
|
||||||
|
|
||||||
|
QueryType uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
type inboundContextKey struct{}
|
type inboundContextKey struct{}
|
||||||
|
|||||||
@@ -34,12 +34,15 @@ type Router interface {
|
|||||||
InterfaceFinder() control.InterfaceFinder
|
InterfaceFinder() control.InterfaceFinder
|
||||||
DefaultInterface() string
|
DefaultInterface() string
|
||||||
AutoDetectInterface() bool
|
AutoDetectInterface() bool
|
||||||
|
AutoDetectInterfaceFunc() control.Func
|
||||||
DefaultMark() int
|
DefaultMark() int
|
||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
|
|
||||||
|
TimeService
|
||||||
|
|
||||||
ClashServer() ClashServer
|
ClashServer() ClashServer
|
||||||
SetClashServer(server ClashServer)
|
SetClashServer(server ClashServer)
|
||||||
|
|
||||||
@@ -47,6 +50,20 @@ type Router interface {
|
|||||||
SetV2RayServer(server V2RayServer)
|
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 {
|
type Rule interface {
|
||||||
Service
|
Service
|
||||||
Type() string
|
Type() string
|
||||||
|
|||||||
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"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 {
|
type V2RayServerTransport interface {
|
||||||
@@ -12,6 +16,12 @@ type V2RayServerTransport interface {
|
|||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type V2RayServerTransportHandler interface {
|
||||||
|
N.TCPConnectionHandler
|
||||||
|
E.Handler
|
||||||
|
FallbackConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error
|
||||||
|
}
|
||||||
|
|
||||||
type V2RayClientTransport interface {
|
type V2RayClientTransport interface {
|
||||||
DialContext(ctx context.Context) (net.Conn, error)
|
DialContext(ctx context.Context) (net.Conn, error)
|
||||||
}
|
}
|
||||||
|
|||||||
94
box.go
94
box.go
@@ -9,7 +9,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/inbound"
|
"github.com/sagernet/sing-box/inbound"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@@ -35,7 +37,7 @@ type Box struct {
|
|||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ctx context.Context, options option.Options) (*Box, error) {
|
func New(ctx context.Context, options option.Options, platformInterface platform.Interface) (*Box, error) {
|
||||||
createdAt := time.Now()
|
createdAt := time.Now()
|
||||||
logOptions := common.PtrValueOrDefault(options.Log)
|
logOptions := common.PtrValueOrDefault(options.Log)
|
||||||
|
|
||||||
@@ -53,19 +55,25 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
var logFactory log.Factory
|
var logFactory log.Factory
|
||||||
var observableLogFactory log.ObservableFactory
|
var observableLogFactory log.ObservableFactory
|
||||||
var logFile *os.File
|
var logFile *os.File
|
||||||
|
var logWriter io.Writer
|
||||||
if logOptions.Disabled {
|
if logOptions.Disabled {
|
||||||
observableLogFactory = log.NewNOPFactory()
|
observableLogFactory = log.NewNOPFactory()
|
||||||
logFactory = observableLogFactory
|
logFactory = observableLogFactory
|
||||||
} else {
|
} else {
|
||||||
var logWriter io.Writer
|
|
||||||
switch logOptions.Output {
|
switch logOptions.Output {
|
||||||
case "", "stderr":
|
case "":
|
||||||
|
if platformInterface != nil {
|
||||||
|
logWriter = io.Discard
|
||||||
|
} else {
|
||||||
|
logWriter = os.Stdout
|
||||||
|
}
|
||||||
|
case "stderr":
|
||||||
logWriter = os.Stderr
|
logWriter = os.Stderr
|
||||||
case "stdout":
|
case "stdout":
|
||||||
logWriter = os.Stdout
|
logWriter = os.Stdout
|
||||||
default:
|
default:
|
||||||
var err error
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -79,10 +87,10 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
TimestampFormat: "-0700 2006-01-02 15:04:05",
|
TimestampFormat: "-0700 2006-01-02 15:04:05",
|
||||||
}
|
}
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter)
|
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter, platformInterface)
|
||||||
logFactory = observableLogFactory
|
logFactory = observableLogFactory
|
||||||
} else {
|
} else {
|
||||||
logFactory = log.NewFactory(logFormatter, logWriter)
|
logFactory = log.NewFactory(logFormatter, logWriter, platformInterface)
|
||||||
}
|
}
|
||||||
if logOptions.Level != "" {
|
if logOptions.Level != "" {
|
||||||
logLevel, err := log.ParseLevel(logOptions.Level)
|
logLevel, err := log.ParseLevel(logOptions.Level)
|
||||||
@@ -100,7 +108,9 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
logFactory,
|
logFactory,
|
||||||
common.PtrValueOrDefault(options.Route),
|
common.PtrValueOrDefault(options.Route),
|
||||||
common.PtrValueOrDefault(options.DNS),
|
common.PtrValueOrDefault(options.DNS),
|
||||||
|
common.PtrValueOrDefault(options.NTP),
|
||||||
options.Inbounds,
|
options.Inbounds,
|
||||||
|
platformInterface,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse route options")
|
return nil, E.Cause(err, "parse route options")
|
||||||
@@ -120,6 +130,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||||
inboundOptions,
|
inboundOptions,
|
||||||
|
platformInterface,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse inbound[", i, "]")
|
return nil, E.Cause(err, "parse inbound[", i, "]")
|
||||||
@@ -202,6 +213,18 @@ func (s *Box) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) start() error {
|
func (s *Box) start() error {
|
||||||
|
if s.clashServer != nil {
|
||||||
|
err := s.clashServer.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "start clash api server")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.v2rayServer != nil {
|
||||||
|
err := s.v2rayServer.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "start v2ray api server")
|
||||||
|
}
|
||||||
|
}
|
||||||
for i, out := range s.outbounds {
|
for i, out := range s.outbounds {
|
||||||
if starter, isStarter := out.(common.Starter); isStarter {
|
if starter, isStarter := out.(common.Starter); isStarter {
|
||||||
err := starter.Start()
|
err := starter.Start()
|
||||||
@@ -232,18 +255,7 @@ func (s *Box) start() error {
|
|||||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.clashServer != nil {
|
|
||||||
err = s.clashServer.Start()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "start clash api server")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if s.v2rayServer != nil {
|
|
||||||
err = s.v2rayServer.Start()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "start v2ray api server")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -255,19 +267,43 @@ func (s *Box) Close() error {
|
|||||||
default:
|
default:
|
||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
for _, in := range s.inbounds {
|
var errors error
|
||||||
in.Close()
|
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 {
|
for i, out := range s.outbounds {
|
||||||
common.Close(out)
|
errors = E.Append(errors, common.Close(out), func(err error) error {
|
||||||
|
return E.Cause(err, "close inbound/", out.Type(), "[", i, "]")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return common.Close(
|
if err := common.Close(s.router); err != nil {
|
||||||
s.router,
|
errors = E.Append(errors, err, func(err error) error {
|
||||||
s.logFactory,
|
return E.Cause(err, "close router")
|
||||||
s.clashServer,
|
})
|
||||||
s.v2rayServer,
|
}
|
||||||
common.PtrOrNil(s.logFile),
|
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 {
|
func (s *Box) Router() adapter.Router {
|
||||||
|
|||||||
35
cmd/internal/build/main.go
Normal file
35
cmd/internal/build/main.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
build_shared.FindSDK()
|
||||||
|
|
||||||
|
currentTag, err := common.Exec("git", "describe", "--tags", "--abbrev=0").Read()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTag = strings.TrimSpace(currentTag)
|
||||||
|
|
||||||
|
if "v"+C.Version != currentTag {
|
||||||
|
log.Fatal("version mismatch, update constant.Version (", C.Version, ")", " to ", currentTag[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
113
cmd/internal/build_libbox/main.go
Normal file
113
cmd/internal/build_libbox/main.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
_ "github.com/sagernet/gomobile/event/key"
|
||||||
|
"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=",
|
||||||
|
"-tags", "with_gvisor,with_utls,with_clash_api,with_conntrack",
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
args = append(args, "-tags", "with_gvisor,with_utls,with_clash_api,with_conntrack,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("..", "sing-box-for-ios")
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -31,7 +31,10 @@ func check() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
_, err = box.New(ctx, options)
|
instance, err := box.New(ctx, options, nil)
|
||||||
|
if err == nil {
|
||||||
|
instance.Close()
|
||||||
|
}
|
||||||
cancel()
|
cancel()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
139
cmd/sing-box/cmd_generate.go
Normal file
139
cmd/sing-box/cmd_generate.go
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandGenerate = &cobra.Command{
|
||||||
|
Use: "generate",
|
||||||
|
Short: "Generate things",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandGenerate.AddCommand(commandGenerateUUID)
|
||||||
|
commandGenerate.AddCommand(commandGenerateRandom)
|
||||||
|
commandGenerate.AddCommand(commandGenerateWireGuardKeyPair)
|
||||||
|
commandGenerate.AddCommand(commandGenerateRealityKeyPair)
|
||||||
|
mainCommand.AddCommand(commandGenerate)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
outputBase64 bool
|
||||||
|
outputHex bool
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandGenerateRandom = &cobra.Command{
|
||||||
|
Use: "rand <length>",
|
||||||
|
Short: "Generate random bytes",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := generateRandom(args)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandGenerateRandom.Flags().BoolVar(&outputBase64, "base64", false, "Generate base64 string")
|
||||||
|
commandGenerateRandom.Flags().BoolVar(&outputHex, "hex", false, "Generate hex string")
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateRandom(args []string) error {
|
||||||
|
length, err := strconv.Atoi(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
randomBytes := make([]byte, length)
|
||||||
|
_, err = rand.Read(randomBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if outputBase64 {
|
||||||
|
_, err = os.Stdout.WriteString(base64.StdEncoding.EncodeToString(randomBytes) + "\n")
|
||||||
|
} else if outputHex {
|
||||||
|
_, err = os.Stdout.WriteString(hex.EncodeToString(randomBytes) + "\n")
|
||||||
|
} else {
|
||||||
|
_, err = os.Stdout.Write(randomBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandGenerateUUID = &cobra.Command{
|
||||||
|
Use: "uuid",
|
||||||
|
Short: "Generate UUID string",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := generateUUID()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateUUID() error {
|
||||||
|
newUUID, err := uuid.NewV4()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = os.Stdout.WriteString(newUUID.String() + "\n")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandGenerateWireGuardKeyPair = &cobra.Command{
|
||||||
|
Use: "wg-keypair",
|
||||||
|
Short: "Generate WireGuard key pair",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := generateWireGuardKey()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateWireGuardKey() error {
|
||||||
|
privateKey, err := wgtypes.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
os.Stdout.WriteString("PrivateKey: " + privateKey.String() + "\n")
|
||||||
|
os.Stdout.WriteString("PublicKey: " + privateKey.PublicKey().String() + "\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandGenerateRealityKeyPair = &cobra.Command{
|
||||||
|
Use: "reality-keypair",
|
||||||
|
Short: "Generate reality key pair",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := generateRealityKey()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateRealityKey() error {
|
||||||
|
privateKey, err := wgtypes.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
publicKey := privateKey.PublicKey()
|
||||||
|
os.Stdout.WriteString("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:]) + "\n")
|
||||||
|
os.Stdout.WriteString("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:]) + "\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -64,7 +64,7 @@ func create() (*box.Box, context.CancelFunc, error) {
|
|||||||
options.Log.DisableColor = true
|
options.Log.DisableColor = true
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
instance, err := box.New(ctx, options)
|
instance, err := box.New(ctx, options, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
return nil, nil, E.Cause(err, "create service")
|
return nil, nil, E.Cause(err, "create service")
|
||||||
|
|||||||
51
common/dialer/conntrack/conn.go
Normal file
51
common/dialer/conntrack/conn.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package conntrack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Conn struct {
|
||||||
|
net.Conn
|
||||||
|
element *list.Element[*ConnEntry]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConn(conn net.Conn) *Conn {
|
||||||
|
entry := &ConnEntry{
|
||||||
|
Conn: conn,
|
||||||
|
Stack: debug.Stack(),
|
||||||
|
}
|
||||||
|
connAccess.Lock()
|
||||||
|
element := openConnection.PushBack(entry)
|
||||||
|
connAccess.Unlock()
|
||||||
|
return &Conn{
|
||||||
|
Conn: conn,
|
||||||
|
element: element,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Close() error {
|
||||||
|
if c.element.Value != nil {
|
||||||
|
connAccess.Lock()
|
||||||
|
if c.element.Value != nil {
|
||||||
|
openConnection.Remove(c.element)
|
||||||
|
c.element.Value = nil
|
||||||
|
}
|
||||||
|
connAccess.Unlock()
|
||||||
|
}
|
||||||
|
return c.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Upstream() any {
|
||||||
|
return c.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) ReaderReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) WriterReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
51
common/dialer/conntrack/packet_conn.go
Normal file
51
common/dialer/conntrack/packet_conn.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package conntrack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PacketConn struct {
|
||||||
|
net.PacketConn
|
||||||
|
element *list.Element[*ConnEntry]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPacketConn(conn net.PacketConn) *PacketConn {
|
||||||
|
entry := &ConnEntry{
|
||||||
|
Conn: conn,
|
||||||
|
Stack: debug.Stack(),
|
||||||
|
}
|
||||||
|
connAccess.Lock()
|
||||||
|
element := openConnection.PushBack(entry)
|
||||||
|
connAccess.Unlock()
|
||||||
|
return &PacketConn{
|
||||||
|
PacketConn: conn,
|
||||||
|
element: element,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) Close() error {
|
||||||
|
if c.element.Value != nil {
|
||||||
|
connAccess.Lock()
|
||||||
|
if c.element.Value != nil {
|
||||||
|
openConnection.Remove(c.element)
|
||||||
|
c.element.Value = nil
|
||||||
|
}
|
||||||
|
connAccess.Unlock()
|
||||||
|
}
|
||||||
|
return c.PacketConn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) Upstream() any {
|
||||||
|
return c.PacketConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) ReaderReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) WriterReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
43
common/dialer/conntrack/track.go
Normal file
43
common/dialer/conntrack/track.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package conntrack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
connAccess sync.RWMutex
|
||||||
|
openConnection list.List[*ConnEntry]
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConnEntry struct {
|
||||||
|
Conn io.Closer
|
||||||
|
Stack []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func Count() int {
|
||||||
|
return openConnection.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
func List() []*ConnEntry {
|
||||||
|
connAccess.RLock()
|
||||||
|
defer connAccess.RUnlock()
|
||||||
|
connList := make([]*ConnEntry, 0, openConnection.Len())
|
||||||
|
for element := openConnection.Front(); element != nil; element = element.Next() {
|
||||||
|
connList = append(connList, element.Value)
|
||||||
|
}
|
||||||
|
return connList
|
||||||
|
}
|
||||||
|
|
||||||
|
func Close() {
|
||||||
|
connAccess.Lock()
|
||||||
|
defer connAccess.Unlock()
|
||||||
|
for element := openConnection.Front(); element != nil; element = element.Next() {
|
||||||
|
common.Close(element.Value.Conn)
|
||||||
|
element.Value = nil
|
||||||
|
}
|
||||||
|
openConnection = list.List[*ConnEntry]{}
|
||||||
|
}
|
||||||
5
common/dialer/conntrack/track_disable.go
Normal file
5
common/dialer/conntrack/track_disable.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
//go:build !with_conntrack
|
||||||
|
|
||||||
|
package conntrack
|
||||||
|
|
||||||
|
const Enabled = false
|
||||||
5
common/dialer/conntrack/track_enable.go
Normal file
5
common/dialer/conntrack/track_enable.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
//go:build with_conntrack
|
||||||
|
|
||||||
|
package conntrack
|
||||||
|
|
||||||
|
const Enabled = true
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||||
"github.com/sagernet/sing-box/common/warning"
|
"github.com/sagernet/sing-box/common/warning"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@@ -13,8 +14,7 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/tfo-go"
|
||||||
"github.com/database64128/tfo-go/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var warnBindInterfaceOnUnsupportedPlatform = warning.New(
|
var warnBindInterfaceOnUnsupportedPlatform = warning.New(
|
||||||
@@ -71,15 +71,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
|||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
} else if router.AutoDetectInterface() {
|
} else if router.AutoDetectInterface() {
|
||||||
const useInterfaceName = C.IsLinux
|
bindFunc := router.AutoDetectInterfaceFunc()
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
} else if router.DefaultInterface() != "" {
|
} else if router.DefaultInterface() != "" {
|
||||||
@@ -168,16 +160,30 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !address.IsIPv6() {
|
if !address.IsIPv6() {
|
||||||
return DialSlowContext(&d.dialer4, ctx, network, address)
|
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
||||||
} else {
|
} else {
|
||||||
return DialSlowContext(&d.dialer6, ctx, network, address)
|
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
if !destination.IsIPv6() {
|
if !destination.IsIPv6() {
|
||||||
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
|
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
||||||
} else {
|
} else {
|
||||||
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
|
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||||
|
if !conntrack.Enabled || err != nil {
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
return conntrack.NewConn(conn), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
|
||||||
|
if !conntrack.Enabled || err != nil {
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
return conntrack.NewPacketConn(conn), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/tfo-go"
|
||||||
"github.com/database64128/tfo-go/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type slowOpenConn struct {
|
type slowOpenConn struct {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
|
|
||||||
"github.com/oschwald/maxminddb-golang"
|
"github.com/oschwald/maxminddb-golang"
|
||||||
)
|
)
|
||||||
@@ -31,8 +30,5 @@ func (r *Reader) Lookup(addr netip.Addr) string {
|
|||||||
if code != "" {
|
if code != "" {
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
if !N.IsPublicAddr(addr) {
|
|
||||||
return "private"
|
|
||||||
}
|
|
||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ func (l *Listener) Accept() (net.Conn, error) {
|
|||||||
bufReader := std_bufio.NewReader(conn)
|
bufReader := std_bufio.NewReader(conn)
|
||||||
header, err := proxyproto.Read(bufReader)
|
header, err := proxyproto.Read(bufReader)
|
||||||
if err != nil && !(l.AcceptNoHeader && err == proxyproto.ErrNoProxyProtocol) {
|
if err != nil && !(l.AcceptNoHeader && err == proxyproto.ErrNoProxyProtocol) {
|
||||||
return nil, err
|
return nil, &Error{err}
|
||||||
}
|
}
|
||||||
if bufReader.Buffered() > 0 {
|
if bufReader.Buffered() > 0 {
|
||||||
cache := buf.NewSize(bufReader.Buffered())
|
cache := buf.NewSize(bufReader.Buffered())
|
||||||
_, err = cache.ReadFullFrom(bufReader, cache.FreeLen())
|
_, err = cache.ReadFullFrom(bufReader, cache.FreeLen())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, &Error{err}
|
||||||
}
|
}
|
||||||
conn = bufio.NewCachedConn(conn, cache)
|
conn = bufio.NewCachedConn(conn, cache)
|
||||||
}
|
}
|
||||||
@@ -42,3 +42,21 @@ func (l *Listener) Accept() (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ net.Error = (*Error)(nil)
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Unwrap() error {
|
||||||
|
return e.error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Timeout() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Temporary() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package redir
|
package redir
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -29,7 +30,9 @@ func GetOriginalDestination(conn net.Conn) (destination netip.AddrPort, err erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
return nil
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ import (
|
|||||||
const (
|
const (
|
||||||
VersionDraft29 = 0xff00001d
|
VersionDraft29 = 0xff00001d
|
||||||
Version1 = 0x1
|
Version1 = 0x1
|
||||||
Version2 = 0x709a50c4
|
Version2 = 0x6b3343cf
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
SaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
|
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}
|
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 (
|
const (
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package tls
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -13,6 +14,8 @@ import (
|
|||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/mholt/acmez/acme"
|
"github.com/mholt/acmez/acme"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
)
|
)
|
||||||
|
|
||||||
type acmeWrapper struct {
|
type acmeWrapper struct {
|
||||||
@@ -54,6 +57,11 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
|
|||||||
config := &certmagic.Config{
|
config := &certmagic.Config{
|
||||||
DefaultServerName: options.DefaultServerName,
|
DefaultServerName: options.DefaultServerName,
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
|
Logger: zap.New(zapcore.NewCore(
|
||||||
|
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
|
||||||
|
os.Stderr,
|
||||||
|
zap.InfoLevel,
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
acmeConfig := certmagic.ACMEIssuer{
|
acmeConfig := certmagic.ACMEIssuer{
|
||||||
CA: acmeServer,
|
CA: acmeServer,
|
||||||
@@ -63,8 +71,9 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
|
|||||||
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
|
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
|
||||||
AltHTTPPort: int(options.AlternativeHTTPPort),
|
AltHTTPPort: int(options.AlternativeHTTPPort),
|
||||||
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
||||||
|
Logger: config.Logger,
|
||||||
}
|
}
|
||||||
if options.ExternalAccount != nil {
|
if options.ExternalAccount != nil && options.ExternalAccount.KeyID != "" {
|
||||||
acmeConfig.ExternalAccount = (*acme.EAB)(options.ExternalAccount)
|
acmeConfig.ExternalAccount = (*acme.EAB)(options.ExternalAccount)
|
||||||
}
|
}
|
||||||
config.Issuers = []certmagic.Issuer{certmagic.NewACMEIssuer(config, acmeConfig)}
|
config.Issuers = []certmagic.Issuer{certmagic.NewACMEIssuer(config, acmeConfig)}
|
||||||
|
|||||||
@@ -2,16 +2,15 @@ package tls
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/badtls"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDialerFromOptions(router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
func NewDialerFromOptions(router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
||||||
@@ -31,29 +30,19 @@ func NewClient(router adapter.Router, serverAddress string, options option.Outbo
|
|||||||
}
|
}
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
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 {
|
} else if options.UTLS != nil && options.UTLS.Enabled {
|
||||||
return NewUTLSClient(router, serverAddress, options)
|
return NewUTLSClient(router, serverAddress, options)
|
||||||
} else {
|
} else {
|
||||||
return NewSTDClient(serverAddress, options)
|
return NewSTDClient(router, serverAddress, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
|
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
|
||||||
tlsConn := config.Client(conn)
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
err := tlsConn.HandshakeContext(ctx)
|
return aTLS.ClientHandshake(ctx, conn, config)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if stdConn, isSTD := tlsConn.(*tls.Conn); isSTD {
|
|
||||||
var badConn badtls.TLSConn
|
|
||||||
badConn, err = badtls.Create(stdConn)
|
|
||||||
if err == nil {
|
|
||||||
return badConn, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tlsConn, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Dialer struct {
|
type Dialer struct {
|
||||||
|
|||||||
@@ -1,41 +1,25 @@
|
|||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
STDConfig = tls.Config
|
Config = aTLS.Config
|
||||||
STDConn = tls.Conn
|
ConfigCompat = aTLS.ConfigCompat
|
||||||
|
ServerConfig = aTLS.ServerConfig
|
||||||
|
ServerConfigCompat = aTLS.ServerConfigCompat
|
||||||
|
WithSessionIDGenerator = aTLS.WithSessionIDGenerator
|
||||||
|
Conn = aTLS.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
|
|
||||||
Clone() Config
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServerConfig interface {
|
|
||||||
Config
|
|
||||||
adapter.Service
|
|
||||||
Server(conn net.Conn) Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
type Conn interface {
|
|
||||||
net.Conn
|
|
||||||
HandshakeContext(ctx context.Context) error
|
|
||||||
ConnectionState() tls.ConnectionState
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseTLSVersion(version string) (uint16, error) {
|
func ParseTLSVersion(version string) (uint16, error) {
|
||||||
switch version {
|
switch version {
|
||||||
case "1.0":
|
case "1.0":
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ func (e *ECHClientConfig) Config() (*STDConfig, error) {
|
|||||||
return nil, E.New("unsupported usage for ECH")
|
return nil, E.New("unsupported usage for ECH")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) Client(conn net.Conn) Conn {
|
func (e *ECHClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
return &echConnWrapper{cftls.Client(conn, e.config)}
|
return &echConnWrapper{cftls.Client(conn, e.config)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) Clone() Config {
|
func (e *ECHClientConfig) Clone() Config {
|
||||||
@@ -76,6 +76,10 @@ func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *echConnWrapper) Upstream() any {
|
||||||
|
return c.Conn
|
||||||
|
}
|
||||||
|
|
||||||
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
@@ -90,6 +94,7 @@ func NewECHClient(router adapter.Router, serverAddress string, options option.Ou
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tlsConfig cftls.Config
|
var tlsConfig cftls.Config
|
||||||
|
tlsConfig.Time = router.TimeFunc()
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ import (
|
|||||||
"time"
|
"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)
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -22,8 +25,8 @@ func GenerateKeyPair(serverName string) (*tls.Certificate, error) {
|
|||||||
}
|
}
|
||||||
template := &x509.Certificate{
|
template := &x509.Certificate{
|
||||||
SerialNumber: serialNumber,
|
SerialNumber: serialNumber,
|
||||||
NotBefore: time.Now().Add(time.Hour * -1),
|
NotBefore: timeFunc().Add(time.Hour * -1),
|
||||||
NotAfter: time.Now().Add(time.Hour),
|
NotAfter: timeFunc().Add(time.Hour),
|
||||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
|
|||||||
230
common/tls/reality_client.go
Normal file
230
common/tls/reality_client.go
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
//go:build with_utls
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
mRand "math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"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"
|
||||||
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
|
utls "github.com/sagernet/utls"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/hkdf"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ ConfigCompat = (*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) {
|
||||||
|
return ClientHandshake(context.Background(), conn, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = uConn.HandshakeContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug.Enabled {
|
||||||
|
fmt.Printf("REALITY Conn.Verified: %v\n", verifier.verified)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !verifier.verified {
|
||||||
|
go realityClientFallback(uConn, e.uClient.ServerName(), e.uClient.id)
|
||||||
|
return nil, E.New("reality verification failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &utlsConnWrapper{uConn}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
||||||
|
defer uConn.Close()
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: &http2.Transport{
|
||||||
|
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
|
||||||
|
return uConn, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
|
||||||
|
request.Header.Set("User-Agent", fingerprint.Client)
|
||||||
|
request.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", mRand.Intn(32)+30)})
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, _ = io.Copy(io.Discard, response.Body)
|
||||||
|
response.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
192
common/tls/reality_server.go
Normal file
192
common/tls/reality_server.go
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
//go:build with_reality_server
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/reality"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 ClientHandshake(context.Background(), conn, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RealityServerConfig) Start() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RealityServerConfig) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) {
|
||||||
|
return ServerHandshake(context.Background(), conn, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
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`)
|
||||||
|
}
|
||||||
@@ -2,36 +2,28 @@ package tls
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/badtls"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
func NewServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return NewSTDServer(ctx, logger, options)
|
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) {
|
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
|
||||||
tlsConn := config.Server(conn)
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
err := tlsConn.HandshakeContext(ctx)
|
return aTLS.ServerHandshake(ctx, conn, config)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if stdConn, isSTD := tlsConn.(*tls.Conn); isSTD {
|
|
||||||
var badConn badtls.TLSConn
|
|
||||||
badConn, err = badtls.Create(stdConn)
|
|
||||||
if err == nil {
|
|
||||||
return badConn, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tlsConn, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
@@ -35,15 +36,15 @@ func (s *STDClientConfig) Config() (*STDConfig, error) {
|
|||||||
return s.config, nil
|
return s.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *STDClientConfig) Client(conn net.Conn) Conn {
|
func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
return tls.Client(conn, s.config)
|
return tls.Client(conn, s.config), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *STDClientConfig) Clone() Config {
|
func (s *STDClientConfig) Clone() Config {
|
||||||
return &STDClientConfig{s.config.Clone()}
|
return &STDClientConfig{s.config.Clone()}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDClient(serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewSTDClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
@@ -57,6 +58,7 @@ func NewSTDClient(serverAddress string, options option.OutboundTLSOptions) (Conf
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tlsConfig tls.Config
|
var tlsConfig tls.Config
|
||||||
|
tlsConfig.Time = router.TimeFunc()
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -48,12 +48,12 @@ func (c *STDServerConfig) Config() (*STDConfig, error) {
|
|||||||
return c.config, nil
|
return c.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Client(conn net.Conn) Conn {
|
func (c *STDServerConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
return tls.Client(conn, c.config)
|
return tls.Client(conn, c.config), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Server(conn net.Conn) Conn {
|
func (c *STDServerConfig) Server(conn net.Conn) (Conn, error) {
|
||||||
return tls.Server(conn, c.config)
|
return tls.Server(conn, c.config), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Clone() Config {
|
func (c *STDServerConfig) Clone() Config {
|
||||||
@@ -156,7 +156,7 @@ func (c *STDServerConfig) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -175,6 +175,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
} else {
|
} else {
|
||||||
tlsConfig = &tls.Config{}
|
tlsConfig = &tls.Config{}
|
||||||
}
|
}
|
||||||
|
tlsConfig.Time = router.TimeFunc()
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
tlsConfig.ServerName = options.ServerName
|
tlsConfig.ServerName = options.ServerName
|
||||||
}
|
}
|
||||||
@@ -230,7 +231,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
}
|
}
|
||||||
if certificate == nil && key == nil && options.Insecure {
|
if certificate == nil && key == nil && options.Insecure {
|
||||||
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
return GenerateKeyPair(info.ServerName)
|
return GenerateKeyPair(router.TimeFunc(), info.ServerName)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if certificate == nil {
|
if certificate == nil {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package tls
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -12,8 +13,9 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
utls "github.com/sagernet/utls"
|
||||||
|
|
||||||
utls "github.com/refraction-networking/utls"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UTLSClientConfig struct {
|
type UTLSClientConfig struct {
|
||||||
@@ -34,6 +36,9 @@ func (e *UTLSClientConfig) NextProtos() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *UTLSClientConfig) SetNextProtos(nextProto []string) {
|
func (e *UTLSClientConfig) SetNextProtos(nextProto []string) {
|
||||||
|
if len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS {
|
||||||
|
nextProto = append(nextProto, "http/1.1")
|
||||||
|
}
|
||||||
e.config.NextProtos = nextProto
|
e.config.NextProtos = nextProto
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,8 +46,19 @@ func (e *UTLSClientConfig) Config() (*STDConfig, error) {
|
|||||||
return nil, E.New("unsupported usage for uTLS")
|
return nil, E.New("unsupported usage for uTLS")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *UTLSClientConfig) Client(conn net.Conn) Conn {
|
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}
|
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 {
|
type utlsConnWrapper struct {
|
||||||
@@ -67,14 +83,11 @@ func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *UTLSClientConfig) Clone() Config {
|
func (c *utlsConnWrapper) Upstream() any {
|
||||||
return &UTLSClientConfig{
|
return c.UConn
|
||||||
config: e.config.Clone(),
|
|
||||||
id: e.id,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
@@ -88,6 +101,7 @@ func NewUTLSClient(router adapter.Router, serverAddress string, options option.O
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tlsConfig utls.Config
|
var tlsConfig utls.Config
|
||||||
|
tlsConfig.Time = router.TimeFunc()
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
@@ -144,28 +158,59 @@ func NewUTLSClient(router adapter.Router, serverAddress string, options option.O
|
|||||||
}
|
}
|
||||||
tlsConfig.RootCAs = certPool
|
tlsConfig.RootCAs = certPool
|
||||||
}
|
}
|
||||||
var id utls.ClientHelloID
|
id, err := uTLSClientHelloID(options.UTLS.Fingerprint)
|
||||||
switch options.UTLS.Fingerprint {
|
if err != nil {
|
||||||
case "chrome", "":
|
return nil, err
|
||||||
id = utls.HelloChrome_Auto
|
|
||||||
case "firefox":
|
|
||||||
id = utls.HelloFirefox_Auto
|
|
||||||
case "edge":
|
|
||||||
id = utls.HelloEdge_Auto
|
|
||||||
case "safari":
|
|
||||||
id = utls.HelloSafari_Auto
|
|
||||||
case "360":
|
|
||||||
id = utls.Hello360_Auto
|
|
||||||
case "qq":
|
|
||||||
id = utls.HelloQQ_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)
|
|
||||||
}
|
}
|
||||||
return &UTLSClientConfig{&tlsConfig, id}, nil
|
return &UTLSClientConfig{&tlsConfig, id}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
randomFingerprint utls.ClientHelloID
|
||||||
|
randomizedFingerprint utls.ClientHelloID
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
modernFingerprints := []utls.ClientHelloID{
|
||||||
|
utls.HelloChrome_Auto,
|
||||||
|
utls.HelloFirefox_Auto,
|
||||||
|
utls.HelloEdge_Auto,
|
||||||
|
utls.HelloSafari_Auto,
|
||||||
|
utls.HelloIOS_Auto,
|
||||||
|
}
|
||||||
|
randomFingerprint = modernFingerprints[rand.Intn(len(modernFingerprints))]
|
||||||
|
|
||||||
|
weights := utls.DefaultWeights
|
||||||
|
weights.TLSVersMax_Set_VersionTLS13 = 1
|
||||||
|
weights.FirstKeyShare_Set_CurveP256 = 0
|
||||||
|
randomizedFingerprint = utls.HelloRandomized
|
||||||
|
randomizedFingerprint.Seed, _ = utls.NewPRNGSeed()
|
||||||
|
randomizedFingerprint.Weights = &weights
|
||||||
|
}
|
||||||
|
|
||||||
|
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 randomFingerprint, nil
|
||||||
|
case "randomized":
|
||||||
|
return randomizedFingerprint, nil
|
||||||
|
default:
|
||||||
|
return utls.ClientHelloID{}, E.New("unknown uTLS fingerprint: ", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,3 +11,7 @@ import (
|
|||||||
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`)
|
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`)
|
||||||
|
}
|
||||||
|
|||||||
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 (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dirName = "sing-box"
|
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) {
|
func FindPath(name string) (string, bool) {
|
||||||
name = os.ExpandEnv(name)
|
name = os.ExpandEnv(name)
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
var Version = "1.1"
|
var Version = "1.2-beta7"
|
||||||
|
|||||||
37
docs/assets/icon.svg
Normal file
37
docs/assets/icon.svg
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<svg width="1027" height="1109" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" overflow="hidden">
|
||||||
|
<defs>
|
||||||
|
<filter id="fx0" x="-10%" y="-10%" width="120%" height="120%" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
|
||||||
|
<feComponentTransfer color-interpolation-filters="sRGB">
|
||||||
|
<feFuncR type="discrete" tableValues="0 0" />
|
||||||
|
<feFuncG type="discrete" tableValues="0 0" />
|
||||||
|
<feFuncB type="discrete" tableValues="0 0" />
|
||||||
|
<feFuncA type="linear" slope="0.4" intercept="0" />
|
||||||
|
</feComponentTransfer>
|
||||||
|
<feGaussianBlur stdDeviation="4.58333 4.58333" />
|
||||||
|
</filter>
|
||||||
|
<clipPath id="clip1">
|
||||||
|
<rect x="692" y="855" width="1027" height="1109" />
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip2">
|
||||||
|
<rect x="-2" y="-2" width="541" height="786" />
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip3">
|
||||||
|
<rect x="0" y="0" width="535" height="782" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g clip-path="url(#clip1)" transform="translate(-692 -855)">
|
||||||
|
<path d="M692 1191 692 1575.69C692 1640.41 731.499 1651.19 731.499 1651.19L1148.03 1931.62C1212.66 1974.77 1194.71 1881.29 1194.71 1881.29L1194.71 1528.96 692 1191Z" fill="#37474F" fill-rule="evenodd" />
|
||||||
|
<g clip-path="url(#clip2)" filter="url(#fx0)" transform="translate(1184 1182)">
|
||||||
|
<g clip-path="url(#clip3)">
|
||||||
|
<path d="M520.482 15.4819 520.482 400.176C520.482 464.89 480.983 475.676 480.983 475.676 480.983 475.676 129.086 712.963 64.4523 756.106-0.181814 799.25 17.7721 705.773 17.7721 705.773L17.7721 353.437 520.482 15.4819Z" fill="#455A64" fill-rule="evenodd" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<path d="M1698 1191 1698 1575.69C1698 1640.41 1658.5 1651.19 1658.5 1651.19 1658.5 1651.19 1306.6 1888.48 1241.97 1931.62 1177.34 1974.77 1195.29 1881.29 1195.29 1881.29L1195.29 1528.96 1698 1191Z" fill="#455A64" fill-rule="evenodd" />
|
||||||
|
<path d="M1241.71 868.473C1212.96 850.509 1169.85 850.509 1144.7 868.473L713.557 1163.07C684.814 1181.04 684.814 1213.37 713.557 1231.33L1144.7 1529.53C1173.44 1547.49 1216.56 1547.49 1241.71 1529.53L1676.44 1227.74C1705.19 1209.78 1705.19 1177.44 1676.44 1159.48L1241.71 868.473Z" fill="#546E7A" fill-rule="evenodd" />
|
||||||
|
<path d="M1195 1949C1173.4 1949 1159 1935.19 1159 1917.92L1159 1531.08C1159 1513.82 1173.4 1500 1195 1500 1216.6 1500 1231 1513.82 1231 1531.08L1231 1914.46C1231 1935.19 1216.6 1949 1195 1949Z" fill="#546E7A" fill-rule="evenodd" />
|
||||||
|
<path d="M1553.92 1435.92C1553.92 1471.89 1557.5 1486.27 1518.03 1511.45L1428.32 1568.99C1388.85 1594.17 1374.5 1572.59 1374.5 1540.22L1374.5 1446.71C1374.5 1439.52 1374.5 1435.92 1363.73 1428.73 1270.43 1363.99 911.591 1115.84 847 1069.09L1012.07 954C1058.72 982.772 1399.61 1209.35 1539.56 1306.45 1546.74 1310.05 1550.33 1317.24 1550.33 1320.84L1550.33 1435.92Z" fill="#99AAB5" fill-rule="evenodd" />
|
||||||
|
<path d="M1543.41 1310.21C1399.82 1213.17 1058.79 986.752 1015.72 958L951.103 997.534 847 1069.41C911.615 1116.14 1270.59 1360.53 1363.92 1425.22 1371.1 1428.81 1371.1 1432.41 1371.1 1436L1547 1313.8C1547 1313.8 1547 1310.21 1543.41 1310.21Z" fill="#CCD6DD" fill-rule="evenodd" />
|
||||||
|
<path d="M1554.9 1435.48 1554.9 1324.19C1554.9 1317.01 1551.3 1313.42 1544.11 1309.83 1400.28 1212.89 1058.67 986.721 1015.51 958L940 1008.26C1062.26 1090.83 1389.49 1306.24 1475.79 1367.27 1486.58 1374.45 1486.58 1381.63 1486.58 1385.22L1486.58 1536 1522.54 1510.87C1558.5 1485.74 1554.9 1467.79 1554.9 1435.48Z" fill="#CCD6DD" fill-rule="evenodd" />
|
||||||
|
<path d="M1543.23 1309.95C1399.6 1212.98 1058.49 986.731 1015.4 958L940 1008.28C1062.08 1090.88 1388.83 1306.36 1475.01 1367.41 1475.01 1367.41 1478.6 1371 1478.6 1371L1554 1317.13C1546.82 1313.54 1546.82 1309.95 1543.23 1309.95Z" fill="#E1E8ED" fill-rule="evenodd" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.7 KiB |
@@ -1,3 +1,97 @@
|
|||||||
|
#### 1.2-beta7
|
||||||
|
|
||||||
|
* Fix the compatibility issue between VLESS's vision sub-protocol and the Xray-core client
|
||||||
|
* Improve the stability of the VMESS server
|
||||||
|
|
||||||
|
#### 1.2-beta6
|
||||||
|
|
||||||
|
* Introducing our [new iOS client application](/installation/clients/sfi)
|
||||||
|
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound
|
||||||
|
* Add custom TLS server support for http based v2ray transports
|
||||||
|
* Add generate commands
|
||||||
|
* Enable XUDP by default in VLESS
|
||||||
|
* Update reality server
|
||||||
|
* Update vision protocol
|
||||||
|
* Fixed [user flow in vless server](/configuration/inbound/vless#usersflow)
|
||||||
|
* Bug fixes
|
||||||
|
* Update dependencies
|
||||||
|
|
||||||
|
#### 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
|
#### 1.1
|
||||||
|
|
||||||
* Fix close clash cache
|
* Fix close clash cache
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
"mixed-in"
|
"mixed-in"
|
||||||
],
|
],
|
||||||
"ip_version": 6,
|
"ip_version": 6,
|
||||||
|
"query_type": [
|
||||||
|
"A",
|
||||||
|
"HTTPS",
|
||||||
|
32768
|
||||||
|
],
|
||||||
"network": "tcp",
|
"network": "tcp",
|
||||||
"auth_user": [
|
"auth_user": [
|
||||||
"usera",
|
"usera",
|
||||||
@@ -119,6 +124,10 @@ Tags of [Inbound](/configuration/inbound).
|
|||||||
|
|
||||||
Not limited if empty.
|
Not limited if empty.
|
||||||
|
|
||||||
|
#### query_type
|
||||||
|
|
||||||
|
DNS query type. Values can be integers or type name strings.
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
|
|
||||||
`tcp` or `udp`.
|
`tcp` or `udp`.
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
"mixed-in"
|
"mixed-in"
|
||||||
],
|
],
|
||||||
"ip_version": 6,
|
"ip_version": 6,
|
||||||
|
"query_type": [
|
||||||
|
"A",
|
||||||
|
"HTTPS",
|
||||||
|
32768
|
||||||
|
],
|
||||||
"network": "tcp",
|
"network": "tcp",
|
||||||
"auth_user": [
|
"auth_user": [
|
||||||
"usera",
|
"usera",
|
||||||
@@ -118,6 +123,10 @@
|
|||||||
|
|
||||||
默认不限制。
|
默认不限制。
|
||||||
|
|
||||||
|
#### query_type
|
||||||
|
|
||||||
|
DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
|
|
||||||
`tcp` 或 `udp`。
|
`tcp` 或 `udp`。
|
||||||
|
|||||||
@@ -30,16 +30,17 @@ The tag of the dns server.
|
|||||||
|
|
||||||
The address of the dns server.
|
The address of the dns server.
|
||||||
|
|
||||||
| Protocol | Format |
|
| Protocol | Format |
|
||||||
|----------|-----------------------------|
|
|----------|-------------------------------|
|
||||||
| `System` | `local` |
|
| `System` | `local` |
|
||||||
| `TCP` | `tcp://1.0.0.1` |
|
| `TCP` | `tcp://1.0.0.1` |
|
||||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||||
| `TLS` | `tls://dns.google` |
|
| `TLS` | `tls://dns.google` |
|
||||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||||
| `QUIC` | `quic://dns.adguard.com` |
|
| `QUIC` | `quic://dns.adguard.com` |
|
||||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||||
| `RCode` | `rcode://refused` |
|
| `RCode` | `rcode://refused` |
|
||||||
|
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
|
||||||
|
|
||||||
!!! warning ""
|
!!! 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.
|
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 |
|
| RCode | Description |
|
||||||
|-------------------|-----------------------|
|
|-------------------|-----------------------|
|
||||||
| `success` | `No error` |
|
| `success` | `No error` |
|
||||||
|
|||||||
@@ -30,16 +30,17 @@ DNS 服务器的标签。
|
|||||||
|
|
||||||
DNS 服务器的地址。
|
DNS 服务器的地址。
|
||||||
|
|
||||||
| 协议 | 格式 |
|
| 协议 | 格式 |
|
||||||
|----------|-----------------------------|
|
|----------|------------------------------|
|
||||||
| `System` | `local` |
|
| `System` | `local` |
|
||||||
| `TCP` | `tcp://1.0.0.1` |
|
| `TCP` | `tcp://1.0.0.1` |
|
||||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||||
| `TLS` | `tls://dns.google` |
|
| `TLS` | `tls://dns.google` |
|
||||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||||
| `QUIC` | `quic://dns.adguard.com` |
|
| `QUIC` | `quic://dns.adguard.com` |
|
||||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||||
| `RCode` | `rcode://refused` |
|
| `RCode` | `rcode://refused` |
|
||||||
|
| `DHCP` | `dhcp://auto` 或 `dhcp://en0` |
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
@@ -53,6 +54,10 @@ DNS 服务器的地址。
|
|||||||
|
|
||||||
RCode 传输层传输层常用于屏蔽请求. 与 DNS 规则和 `disable_cache` 规则选项一起使用。
|
RCode 传输层传输层常用于屏蔽请求. 与 DNS 规则和 `disable_cache` 规则选项一起使用。
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
默认安装不包含 DHCP 传输层,请参阅 [安装](/zh/#_2)。
|
||||||
|
|
||||||
| RCode | 描述 |
|
| RCode | 描述 |
|
||||||
|-------------------|----------|
|
|-------------------|----------|
|
||||||
| `success` | `无错误` |
|
| `success` | `无错误` |
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
"external_controller": "127.0.0.1:9090",
|
"external_controller": "127.0.0.1:9090",
|
||||||
"external_ui": "folder",
|
"external_ui": "folder",
|
||||||
"secret": "",
|
"secret": "",
|
||||||
"direct_io": false,
|
|
||||||
"default_mode": "rule",
|
"default_mode": "rule",
|
||||||
"store_selected": false,
|
"store_selected": false,
|
||||||
"cache_file": "cache.db"
|
"cache_file": "cache.db"
|
||||||
@@ -18,13 +17,15 @@
|
|||||||
"listen": "127.0.0.1:8080",
|
"listen": "127.0.0.1:8080",
|
||||||
"stats": {
|
"stats": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"direct_io": false,
|
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
"socks-in"
|
"socks-in"
|
||||||
],
|
],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
"proxy",
|
"proxy",
|
||||||
"direct"
|
"direct"
|
||||||
|
],
|
||||||
|
"users": [
|
||||||
|
"sekai"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,10 +59,6 @@ Secret for the RESTful API (optional)
|
|||||||
Authenticate by spedifying HTTP header `Authorization: Bearer ${secret}`
|
Authenticate by spedifying HTTP header `Authorization: Bearer ${secret}`
|
||||||
ALWAYS set a secret if RESTful API is listening on 0.0.0.0
|
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
|
||||||
|
|
||||||
Default mode in clash, `rule` will be used if empty.
|
Default mode in clash, `rule` will be used if empty.
|
||||||
@@ -98,10 +95,6 @@ Traffic statistics service settings.
|
|||||||
|
|
||||||
Enable statistics service.
|
Enable statistics service.
|
||||||
|
|
||||||
#### stats.direct_io
|
|
||||||
|
|
||||||
Allows lossless relays like splice without real-time traffic reporting.
|
|
||||||
|
|
||||||
#### stats.inbounds
|
#### stats.inbounds
|
||||||
|
|
||||||
Inbound list to count traffic.
|
Inbound list to count traffic.
|
||||||
@@ -109,3 +102,7 @@ Inbound list to count traffic.
|
|||||||
#### stats.outbounds
|
#### stats.outbounds
|
||||||
|
|
||||||
Outbound list to count traffic.
|
Outbound list to count traffic.
|
||||||
|
|
||||||
|
#### stats.users
|
||||||
|
|
||||||
|
User list to count traffic.
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
"external_controller": "127.0.0.1:9090",
|
"external_controller": "127.0.0.1:9090",
|
||||||
"external_ui": "folder",
|
"external_ui": "folder",
|
||||||
"secret": "",
|
"secret": "",
|
||||||
"direct_io": false,
|
|
||||||
"default_mode": "rule",
|
"default_mode": "rule",
|
||||||
"store_selected": false,
|
"store_selected": false,
|
||||||
"cache_file": "cache.db"
|
"cache_file": "cache.db"
|
||||||
@@ -18,13 +17,15 @@
|
|||||||
"listen": "127.0.0.1:8080",
|
"listen": "127.0.0.1:8080",
|
||||||
"stats": {
|
"stats": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"direct_io": false,
|
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
"socks-in"
|
"socks-in"
|
||||||
],
|
],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
"proxy",
|
"proxy",
|
||||||
"direct"
|
"direct"
|
||||||
|
],
|
||||||
|
"users": [
|
||||||
|
"sekai"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,10 +57,6 @@ RESTful API 的密钥(可选)
|
|||||||
通过指定 HTTP 标头 `Authorization: Bearer ${secret}` 进行身份验证
|
通过指定 HTTP 标头 `Authorization: Bearer ${secret}` 进行身份验证
|
||||||
如果 RESTful API 正在监听 0.0.0.0,请始终设置一个密钥。
|
如果 RESTful API 正在监听 0.0.0.0,请始终设置一个密钥。
|
||||||
|
|
||||||
#### direct_io
|
|
||||||
|
|
||||||
允许像 splice 这样的没有实时流量报告的无损中继。
|
|
||||||
|
|
||||||
#### default_mode
|
#### default_mode
|
||||||
|
|
||||||
Clash 中的默认模式,默认使用 `rule`。
|
Clash 中的默认模式,默认使用 `rule`。
|
||||||
@@ -96,10 +93,6 @@ gRPC API 监听地址。如果为空,则禁用 V2Ray API。
|
|||||||
|
|
||||||
启用统计服务。
|
启用统计服务。
|
||||||
|
|
||||||
#### stats.direct_io
|
|
||||||
|
|
||||||
允许像 splice 这样的没有实时流量报告的无损中继。
|
|
||||||
|
|
||||||
#### stats.inbounds
|
#### stats.inbounds
|
||||||
|
|
||||||
统计流量的入站列表。
|
统计流量的入站列表。
|
||||||
@@ -107,3 +100,7 @@ gRPC API 监听地址。如果为空,则禁用 V2Ray API。
|
|||||||
#### stats.outbounds
|
#### stats.outbounds
|
||||||
|
|
||||||
统计流量的出站列表。
|
统计流量的出站列表。
|
||||||
|
|
||||||
|
#### stats.users
|
||||||
|
|
||||||
|
统计流量的用户列表。
|
||||||
@@ -26,6 +26,8 @@
|
|||||||
| `trojan` | [Trojan](./trojan) | TCP |
|
| `trojan` | [Trojan](./trojan) | TCP |
|
||||||
| `naive` | [Naive](./naive) | X |
|
| `naive` | [Naive](./naive) | X |
|
||||||
| `hysteria` | [Hysteria](./hysteria) | X |
|
| `hysteria` | [Hysteria](./hysteria) | X |
|
||||||
|
| `shadowtls` | [ShadowTLS](./shadowtls) | TCP |
|
||||||
|
| `vless` | [VLESS](./vless) | TCP |
|
||||||
| `tun` | [Tun](./tun) | X |
|
| `tun` | [Tun](./tun) | X |
|
||||||
| `redirect` | [Redirect](./redirect) | X |
|
| `redirect` | [Redirect](./redirect) | X |
|
||||||
| `tproxy` | [TProxy](./tproxy) | X |
|
| `tproxy` | [TProxy](./tproxy) | X |
|
||||||
|
|||||||
@@ -77,11 +77,11 @@ Both if empty.
|
|||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
| Method | Password Format |
|
| Method | Password Format |
|
||||||
|---------------|-------------------------------------|
|
|---------------|------------------------------------------------|
|
||||||
| none | / |
|
| none | / |
|
||||||
| 2022 methods | `openssl rand -base64 <Key Length>` |
|
| 2022 methods | `sing-box generate rand --base64 <Key Length>` |
|
||||||
| other methods | any string |
|
| other methods | any string |
|
||||||
|
|
||||||
### Listen Fields
|
### Listen Fields
|
||||||
|
|
||||||
|
|||||||
@@ -77,8 +77,8 @@ See [Listen Fields](/configuration/shared/listen) for details.
|
|||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
| 方法 | 密码格式 |
|
| 方法 | 密码格式 |
|
||||||
|---------------|-------------------------------|
|
|---------------|------------------------------------------|
|
||||||
| none | / |
|
| none | / |
|
||||||
| 2022 methods | `openssl rand -base64 <密钥长度>` |
|
| 2022 methods | `sing-box generate rand --base64 <密钥长度>` |
|
||||||
| other methods | 任意字符串 |
|
| other methods | 任意字符串 |
|
||||||
@@ -7,14 +7,29 @@
|
|||||||
|
|
||||||
... // Listen Fields
|
... // Listen Fields
|
||||||
|
|
||||||
"version": 2,
|
"version": 3,
|
||||||
"password": "fuck me till the daylight",
|
"password": "fuck me till the daylight",
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "sekai",
|
||||||
|
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||||
|
}
|
||||||
|
],
|
||||||
"handshake": {
|
"handshake": {
|
||||||
"server": "google.com",
|
"server": "google.com",
|
||||||
"server_port": 443,
|
"server_port": 443,
|
||||||
|
|
||||||
... // Dial Fields
|
... // 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) |
|
| `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) |
|
| `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
|
#### 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
|
#### handshake
|
||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
Handshake server address and [Dial options](/configuration/shared/dial).
|
Handshake server address and [Dial options](/configuration/shared/dial).
|
||||||
|
|
||||||
|
#### handshake_for_server_name
|
||||||
|
|
||||||
|
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",
|
"password": "fuck me till the daylight",
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "sekai",
|
||||||
|
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||||
|
}
|
||||||
|
],
|
||||||
"handshake": {
|
"handshake": {
|
||||||
"server": "google.com",
|
"server": "google.com",
|
||||||
"server_port": 443,
|
"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) |
|
| `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) |
|
| `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
|
#### password
|
||||||
|
|
||||||
设置密码。
|
ShadowTLS 密码。
|
||||||
|
|
||||||
仅在 ShadowTLS v2 协议中可用。
|
仅在 ShadowTLS 协议版本 2 中可用。
|
||||||
|
|
||||||
|
#### users
|
||||||
|
|
||||||
|
ShadowTLS 用户。
|
||||||
|
|
||||||
|
仅在 ShadowTLS 协议版本 3 中可用。
|
||||||
|
|
||||||
#### handshake
|
#### handshake
|
||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
|
握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
|
||||||
|
|
||||||
|
#### handshake_for_server_name
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
对于特定服务器名称的握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
|
||||||
|
|
||||||
|
仅在 ShadowTLS 协议版本 2/3 中可用。
|
||||||
|
|
||||||
|
#### strict_mode
|
||||||
|
|
||||||
|
ShadowTLS 严格模式。
|
||||||
|
|
||||||
|
仅在 ShadowTLS 协议版本 3 中可用。
|
||||||
|
|||||||
@@ -46,8 +46,15 @@
|
|||||||
"exclude_package": [
|
"exclude_package": [
|
||||||
"com.android.captiveportallogin"
|
"com.android.captiveportallogin"
|
||||||
],
|
],
|
||||||
...
|
"platform": {
|
||||||
// Listen Fields
|
"http_proxy": {
|
||||||
|
"enabled": false,
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 8080
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
... // Listen Fields
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -187,6 +194,14 @@ Limit android packages in route.
|
|||||||
|
|
||||||
Exclude android packages in route.
|
Exclude android packages in route.
|
||||||
|
|
||||||
|
#### platform
|
||||||
|
|
||||||
|
Platform-specific settings, provided by client applications.
|
||||||
|
|
||||||
|
#### platform.http_proxy
|
||||||
|
|
||||||
|
System HTTP proxy settings.
|
||||||
|
|
||||||
### Listen Fields
|
### Listen Fields
|
||||||
|
|
||||||
See [Listen Fields](/configuration/shared/listen) for details.
|
See [Listen Fields](/configuration/shared/listen) for details.
|
||||||
|
|||||||
@@ -46,8 +46,15 @@
|
|||||||
"exclude_package": [
|
"exclude_package": [
|
||||||
"com.android.captiveportallogin"
|
"com.android.captiveportallogin"
|
||||||
],
|
],
|
||||||
...
|
"platform": {
|
||||||
// 监听字段
|
"http_proxy": {
|
||||||
|
"enabled": false,
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 8080
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
... // 监听字段
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -148,19 +155,19 @@ TCP/IP 栈。
|
|||||||
|
|
||||||
UID 规则仅在 Linux 下被支持,并且需要 `auto_route`。
|
UID 规则仅在 Linux 下被支持,并且需要 `auto_route`。
|
||||||
|
|
||||||
限制被路由的的用户。默认不限制。
|
限制被路由的用户。默认不限制。
|
||||||
|
|
||||||
#### include_uid_range
|
#### include_uid_range
|
||||||
|
|
||||||
限制被路由的的用户范围。
|
限制被路由的用户范围。
|
||||||
|
|
||||||
#### exclude_uid
|
#### exclude_uid
|
||||||
|
|
||||||
排除路由的的用户。
|
排除路由的用户。
|
||||||
|
|
||||||
#### exclude_uid_range
|
#### exclude_uid_range
|
||||||
|
|
||||||
排除路由的的用户范围。
|
排除路由的用户范围。
|
||||||
|
|
||||||
#### include_android_user
|
#### include_android_user
|
||||||
|
|
||||||
@@ -183,6 +190,14 @@ TCP/IP 栈。
|
|||||||
|
|
||||||
排除路由的 Android 应用包名。
|
排除路由的 Android 应用包名。
|
||||||
|
|
||||||
|
#### platform
|
||||||
|
|
||||||
|
平台特定的设置,由客户端应用提供。
|
||||||
|
|
||||||
|
#### platform.http_proxy
|
||||||
|
|
||||||
|
系统 HTTP 代理设置。
|
||||||
|
|
||||||
### 监听字段
|
### 监听字段
|
||||||
|
|
||||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||||
|
|||||||
54
docs/configuration/inbound/vless.md
Normal file
54
docs/configuration/inbound/vless.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "vless",
|
||||||
|
"tag": "vless-in",
|
||||||
|
|
||||||
|
... // Listen Fields
|
||||||
|
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "sekai",
|
||||||
|
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||||
|
"flow": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tls": {},
|
||||||
|
"transport": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Listen Fields
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen) for details.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### users
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
VLESS users.
|
||||||
|
|
||||||
|
#### users.uuid
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
VLESS user id.
|
||||||
|
|
||||||
|
#### users.flow
|
||||||
|
|
||||||
|
VLESS Sub-protocol.
|
||||||
|
|
||||||
|
Available values:
|
||||||
|
|
||||||
|
* `xtls-rprx-vision`
|
||||||
|
|
||||||
|
#### tls
|
||||||
|
|
||||||
|
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||||
|
|
||||||
|
#### transport
|
||||||
|
|
||||||
|
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||||
54
docs/configuration/inbound/vless.zh.md
Normal file
54
docs/configuration/inbound/vless.zh.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
### 结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "vless",
|
||||||
|
"tag": "vless-in",
|
||||||
|
|
||||||
|
... // 监听字段
|
||||||
|
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "sekai",
|
||||||
|
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||||
|
"flow": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tls": {},
|
||||||
|
"transport": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 监听字段
|
||||||
|
|
||||||
|
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
|
#### users
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
VLESS 用户。
|
||||||
|
|
||||||
|
#### users.uuid
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
VLESS 用户 ID。
|
||||||
|
|
||||||
|
#### users.flow
|
||||||
|
|
||||||
|
VLESS 子协议。
|
||||||
|
|
||||||
|
可用值:
|
||||||
|
|
||||||
|
* `xtls-rprx-vision`
|
||||||
|
|
||||||
|
#### 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": {},
|
"log": {},
|
||||||
"dns": {},
|
"dns": {},
|
||||||
|
"ntp": {},
|
||||||
"inbounds": [],
|
"inbounds": [],
|
||||||
"outbounds": [],
|
"outbounds": [],
|
||||||
"route": {},
|
"route": {},
|
||||||
@@ -21,6 +22,7 @@ sing-box uses JSON for configuration files.
|
|||||||
|----------------|--------------------------------|
|
|----------------|--------------------------------|
|
||||||
| `log` | [Log](./log) |
|
| `log` | [Log](./log) |
|
||||||
| `dns` | [DNS](./dns) |
|
| `dns` | [DNS](./dns) |
|
||||||
|
| `ntp` | [NTP](./ntp) |
|
||||||
| `inbounds` | [Inbound](./inbound) |
|
| `inbounds` | [Inbound](./inbound) |
|
||||||
| `outbounds` | [Outbound](./outbound) |
|
| `outbounds` | [Outbound](./outbound) |
|
||||||
| `route` | [Route](./route) |
|
| `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) |
|
| `hysteria` | [Hysteria](./hysteria) |
|
||||||
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
|
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
|
||||||
| `vless` | [VLESS](./vless) |
|
| `vless` | [VLESS](./vless) |
|
||||||
|
| `shadowtls` | [ShadowTLS](./shadowtls) |
|
||||||
| `tor` | [Tor](./tor) |
|
| `tor` | [Tor](./tor) |
|
||||||
| `ssh` | [SSH](./ssh) |
|
| `ssh` | [SSH](./ssh) |
|
||||||
| `dns` | [DNS](./dns) |
|
| `dns` | [DNS](./dns) |
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
"server": "127.0.0.1",
|
"server": "127.0.0.1",
|
||||||
"server_port": 1080,
|
"server_port": 1080,
|
||||||
"version": 2,
|
"version": 3,
|
||||||
"password": "fuck me till the daylight",
|
"password": "fuck me till the daylight",
|
||||||
"tls": {},
|
"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) |
|
| `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) |
|
| `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
|
#### password
|
||||||
|
|
||||||
Set password.
|
Set password.
|
||||||
|
|
||||||
Only available in the ShadowTLS v2 protocol.
|
Only available in the ShadowTLS v2/v3 protocol.
|
||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
"server": "127.0.0.1",
|
"server": "127.0.0.1",
|
||||||
"server_port": 1080,
|
"server_port": 1080,
|
||||||
"version": 2,
|
"version": 3,
|
||||||
"password": "fuck me till the daylight",
|
"password": "fuck me till the daylight",
|
||||||
"tls": {},
|
"tls": {},
|
||||||
|
|
||||||
@@ -37,12 +37,13 @@ ShadowTLS 协议版本。
|
|||||||
|---------------|-----------------------------------------------------------------------------------------|
|
|---------------|-----------------------------------------------------------------------------------------|
|
||||||
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
|
| `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) |
|
| `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
|
#### password
|
||||||
|
|
||||||
设置密码。
|
设置密码。
|
||||||
|
|
||||||
仅在 ShadowTLS v2 协议中可用。
|
仅在 ShadowTLS v2/v3 协议中可用。
|
||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
"private_key": "",
|
"private_key": "",
|
||||||
"private_key_path": "$HOME/.ssh/id_rsa",
|
"private_key_path": "$HOME/.ssh/id_rsa",
|
||||||
"private_key_passphrase": "",
|
"private_key_passphrase": "",
|
||||||
|
"host_key": [
|
||||||
|
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdH..."
|
||||||
|
],
|
||||||
"host_key_algorithms": [],
|
"host_key_algorithms": [],
|
||||||
"client_version": "SSH-2.0-OpenSSH_7.4p1",
|
"client_version": "SSH-2.0-OpenSSH_7.4p1",
|
||||||
|
|
||||||
@@ -51,6 +54,10 @@ Private key path.
|
|||||||
|
|
||||||
Private key passphrase.
|
Private key passphrase.
|
||||||
|
|
||||||
|
#### host_key
|
||||||
|
|
||||||
|
Host key. Accept any if empty.
|
||||||
|
|
||||||
#### host_key_algorithms
|
#### host_key_algorithms
|
||||||
|
|
||||||
Host key algorithms.
|
Host key algorithms.
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
"private_key": "",
|
"private_key": "",
|
||||||
"private_key_path": "$HOME/.ssh/id_rsa",
|
"private_key_path": "$HOME/.ssh/id_rsa",
|
||||||
"private_key_passphrase": "",
|
"private_key_passphrase": "",
|
||||||
|
"host_key": [
|
||||||
|
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdH..."
|
||||||
|
],
|
||||||
"host_key_algorithms": [],
|
"host_key_algorithms": [],
|
||||||
"client_version": "SSH-2.0-OpenSSH_7.4p1",
|
"client_version": "SSH-2.0-OpenSSH_7.4p1",
|
||||||
|
|
||||||
@@ -51,6 +54,10 @@ SSH 用户, 默认使用 root。
|
|||||||
|
|
||||||
密钥密码。
|
密钥密码。
|
||||||
|
|
||||||
|
#### host_key
|
||||||
|
|
||||||
|
主机密钥,留空接受所有。
|
||||||
|
|
||||||
#### host_key_algorithms
|
#### host_key_algorithms
|
||||||
|
|
||||||
主机密钥算法。
|
主机密钥算法。
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"server": "127.0.0.1",
|
"server": "127.0.0.1",
|
||||||
"server_port": 1080,
|
"server_port": 1080,
|
||||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||||
|
"flow": "xtls-rprx-vision",
|
||||||
"network": "tcp",
|
"network": "tcp",
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"packet_encoding": "",
|
"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
|
### Fields
|
||||||
|
|
||||||
#### server
|
#### server
|
||||||
@@ -39,7 +36,15 @@ The server port.
|
|||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
The VLESS user id.
|
VLESS user id.
|
||||||
|
|
||||||
|
#### flow
|
||||||
|
|
||||||
|
VLESS Sub-protocol.
|
||||||
|
|
||||||
|
Available values:
|
||||||
|
|
||||||
|
* `xtls-rprx-vision`
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
|
|
||||||
@@ -55,6 +60,8 @@ TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
|||||||
|
|
||||||
#### packet_encoding
|
#### packet_encoding
|
||||||
|
|
||||||
|
UDP packet encoding, xudp is used by default.
|
||||||
|
|
||||||
| Encoding | Description |
|
| Encoding | Description |
|
||||||
|------------|-----------------------|
|
|------------|-----------------------|
|
||||||
| (none) | Disabled |
|
| (none) | Disabled |
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"server": "127.0.0.1",
|
"server": "127.0.0.1",
|
||||||
"server_port": 1080,
|
"server_port": 1080,
|
||||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||||
|
"flow": "xtls-rprx-vision",
|
||||||
"network": "tcp",
|
"network": "tcp",
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"packet_encoding": "",
|
"packet_encoding": "",
|
||||||
@@ -17,10 +18,6 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! warning ""
|
|
||||||
|
|
||||||
VLESS 协议与 v2ray 架构耦合且无人维护。 提供此出站仅出于兼容性目的。
|
|
||||||
|
|
||||||
### 字段
|
### 字段
|
||||||
|
|
||||||
#### server
|
#### server
|
||||||
@@ -41,6 +38,14 @@
|
|||||||
|
|
||||||
VLESS 用户 ID。
|
VLESS 用户 ID。
|
||||||
|
|
||||||
|
#### flow
|
||||||
|
|
||||||
|
VLESS 子协议。
|
||||||
|
|
||||||
|
可用值:
|
||||||
|
|
||||||
|
* `xtls-rprx-vision`
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
|
|
||||||
启用的网络协议。
|
启用的网络协议。
|
||||||
@@ -55,6 +60,8 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
|||||||
|
|
||||||
#### packet_encoding
|
#### packet_encoding
|
||||||
|
|
||||||
|
UDP 包编码,默认使用 xudp。
|
||||||
|
|
||||||
| 编码 | 描述 |
|
| 编码 | 描述 |
|
||||||
|------------|---------------|
|
|------------|---------------|
|
||||||
| (空) | 禁用 |
|
| (空) | 禁用 |
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ Encryption methods:
|
|||||||
* `none`
|
* `none`
|
||||||
* `zero`
|
* `zero`
|
||||||
* `aes-128-gcm`
|
* `aes-128-gcm`
|
||||||
* `chancha20-poly1305`
|
* `chacha20-poly1305`
|
||||||
|
|
||||||
Legacy encryption methods:
|
Legacy encryption methods:
|
||||||
|
|
||||||
@@ -86,6 +86,8 @@ TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
|||||||
|
|
||||||
#### packet_encoding
|
#### packet_encoding
|
||||||
|
|
||||||
|
UDP packet encoding.
|
||||||
|
|
||||||
| Encoding | Description |
|
| Encoding | Description |
|
||||||
|------------|-----------------------|
|
|------------|-----------------------|
|
||||||
| (none) | Disabled |
|
| (none) | Disabled |
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ VMess 用户 ID。
|
|||||||
* `none`
|
* `none`
|
||||||
* `zero`
|
* `zero`
|
||||||
* `aes-128-gcm`
|
* `aes-128-gcm`
|
||||||
* `chancha20-poly1305`
|
* `chacha20-poly1305`
|
||||||
|
|
||||||
旧加密方法:
|
旧加密方法:
|
||||||
|
|
||||||
@@ -86,6 +86,8 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
|||||||
|
|
||||||
#### packet_encoding
|
#### packet_encoding
|
||||||
|
|
||||||
|
UDP 包编码。
|
||||||
|
|
||||||
| 编码 | 描述 |
|
| 编码 | 描述 |
|
||||||
|------------|---------------|
|
|------------|---------------|
|
||||||
| (空) | 禁用 |
|
| (空) | 禁用 |
|
||||||
|
|||||||
@@ -26,6 +26,20 @@
|
|||||||
"key_id": "",
|
"key_id": "",
|
||||||
"mac_key": ""
|
"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": {
|
"utls": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"fingerprint": ""
|
"fingerprint": ""
|
||||||
|
},
|
||||||
|
"reality": {
|
||||||
|
"enabled": false,
|
||||||
|
"public_key": "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
|
||||||
|
"short_id": "0123456789abcdef"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -199,6 +218,7 @@ Available fingerprint values:
|
|||||||
* ios
|
* ios
|
||||||
* android
|
* android
|
||||||
* random
|
* random
|
||||||
|
* randomized
|
||||||
|
|
||||||
Chrome fingerprint will be used if empty.
|
Chrome fingerprint will be used if empty.
|
||||||
|
|
||||||
@@ -275,6 +295,54 @@ The key identifier.
|
|||||||
|
|
||||||
The MAC key.
|
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 `sing-box generate reality-keypair`.
|
||||||
|
|
||||||
|
#### public_key
|
||||||
|
|
||||||
|
==Client only==
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
Public key, generated by `sing-box generate reality-keypair`.
|
||||||
|
|
||||||
|
#### 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
|
### Reload
|
||||||
|
|
||||||
For server configuration, certificate and key will be automatically reloaded if modified.
|
For server configuration, certificate and key will be automatically reloaded if modified.
|
||||||
@@ -26,6 +26,20 @@
|
|||||||
"key_id": "",
|
"key_id": "",
|
||||||
"mac_key": ""
|
"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": {
|
"utls": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"fingerprint": ""
|
"fingerprint": ""
|
||||||
|
},
|
||||||
|
"reality": {
|
||||||
|
"enabled": false,
|
||||||
|
"public_key": "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
|
||||||
|
"short_id": "0123456789abcdef"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -199,6 +218,7 @@ uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻
|
|||||||
* ios
|
* ios
|
||||||
* android
|
* android
|
||||||
* random
|
* random
|
||||||
|
* randomized
|
||||||
|
|
||||||
默认使用 chrome 指纹。
|
默认使用 chrome 指纹。
|
||||||
|
|
||||||
@@ -271,6 +291,52 @@ EAB(外部帐户绑定)包含将 ACME 帐户绑定或映射到其他已知
|
|||||||
|
|
||||||
MAC 密钥。
|
MAC 密钥。
|
||||||
|
|
||||||
|
### Reality 字段
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
默认安装不包含 reality 服务器,参阅 [安装](/zh/#_2)。
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
默认安装不包含被 reality 客户端需要的 uTLS, 参阅 [安装](/zh/#_2)。
|
||||||
|
|
||||||
|
#### handshake
|
||||||
|
|
||||||
|
==仅服务器==
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
|
||||||
|
|
||||||
|
#### private_key
|
||||||
|
|
||||||
|
==仅服务器==
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
私钥,由 `sing-box generate reality-keypair` 生成。
|
||||||
|
|
||||||
|
#### public_key
|
||||||
|
|
||||||
|
==仅客户端==
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
公钥,由 `sing-box generate reality-keypair` 生成。
|
||||||
|
|
||||||
|
#### short_id
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
一个八位十六进制的字符串。
|
||||||
|
|
||||||
|
#### max_time_difference
|
||||||
|
|
||||||
|
服务器与和客户端之间允许的最大时间差。
|
||||||
|
|
||||||
|
默认禁用检查。
|
||||||
|
|
||||||
### 重载
|
### 重载
|
||||||
|
|
||||||
对于服务器配置,如果修改,证书和密钥将自动重新加载。
|
对于服务器配置,如果修改,证书和密钥将自动重新加载。
|
||||||
@@ -7,8 +7,13 @@
|
|||||||
"type": "shadowtls",
|
"type": "shadowtls",
|
||||||
"listen": "::",
|
"listen": "::",
|
||||||
"listen_port": 4443,
|
"listen_port": 4443,
|
||||||
"version": 2,
|
"version": 3,
|
||||||
"password": "fuck me till the daylight",
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "sekai",
|
||||||
|
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||||
|
}
|
||||||
|
],
|
||||||
"handshake": {
|
"handshake": {
|
||||||
"server": "google.com",
|
"server": "google.com",
|
||||||
"server_port": 443
|
"server_port": 443
|
||||||
@@ -47,11 +52,15 @@
|
|||||||
"tag": "shadowtls-out",
|
"tag": "shadowtls-out",
|
||||||
"server": "127.0.0.1",
|
"server": "127.0.0.1",
|
||||||
"server_port": 4443,
|
"server_port": 4443,
|
||||||
"version": 2,
|
"version": 3,
|
||||||
"password": "fuck me till the daylight",
|
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||||
"tls": {
|
"tls": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"server_name": "google.com"
|
"server_name": "google.com",
|
||||||
|
"utls": {
|
||||||
|
"enabled": true,
|
||||||
|
"fingerprint": "chrome"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -8,44 +8,6 @@ Welcome to the wiki page for the sing-box project.
|
|||||||
|
|
||||||
The universal proxy platform.
|
The universal proxy platform.
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
sing-box requires Golang **1.18.5** or a higher version.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go install -v github.com/sagernet/sing-box/cmd/sing-box@latest
|
|
||||||
```
|
|
||||||
|
|
||||||
Install with options:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@latest
|
|
||||||
```
|
|
||||||
|
|
||||||
| 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_grpc` | Build with standard gRPC support, see [V2Ray Transport#gRPC](./configuration/shared/v2ray-transport#grpc). |
|
|
||||||
| `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). |
|
|
||||||
| `with_utls` | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](./configuration/shared/tls#utls). |
|
|
||||||
| `with_acme` | Build with ACME TLS certificate issuer support, see [TLS](./configuration/shared/tls). |
|
|
||||||
| `with_clash_api` | Build with Clash API support, see [Experimental](./configuration/experimental#clash-api-fields). |
|
|
||||||
| `with_v2ray_api` | Build with V2Ray API support, see [Experimental](./configuration/experimental#v2ray-api-fields). |
|
|
||||||
| `with_gvisor` | Build with gVisor support, see [Tun inbound](./configuration/inbound/tun#stack) and [WireGuard outbound](./configuration/outbound/wireguard#system_interface). |
|
|
||||||
| `with_embedded_tor` (CGO required) | Build with embedded Tor support, see [Tor outbound](./configuration/outbound/tor). |
|
|
||||||
| `with_lwip` (CGO required) | Build with LWIP Tun stack support, see [Tun inbound](./configuration/inbound/tun#stack). |
|
|
||||||
|
|
||||||
The binary is built under $GOPATH/bin
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sing-box version
|
|
||||||
```
|
|
||||||
|
|
||||||
It is also recommended to use systemd to manage sing-box service,
|
|
||||||
see [Linux server installation example](./examples/linux-server-installation).
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -8,44 +8,6 @@ description: 欢迎来到该 sing-box 项目的文档页。
|
|||||||
|
|
||||||
通用代理平台。
|
通用代理平台。
|
||||||
|
|
||||||
## 安装
|
|
||||||
|
|
||||||
sing-box 需要 Golang **1.18.5** 或更高版本。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go install -v github.com/sagernet/sing-box/cmd/sing-box@latest
|
|
||||||
```
|
|
||||||
|
|
||||||
自定义安装:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@latest
|
|
||||||
```
|
|
||||||
|
|
||||||
| 构建标志 | 描述 |
|
|
||||||
|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
||||||
| `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_wireguard` | 启用 WireGuard 支持,参阅 [WireGuard 出站](./configuration/outbound/wireguard)。 |
|
|
||||||
| `with_shadowsocksr` | 启用 ShadowsocksR 支持,参阅 [ShadowsocksR 出站](./configuration/outbound/shadowsocksr)。 |
|
|
||||||
| `with_ech` | 启用 TLS ECH 扩展支持,参阅 [TLS](./configuration/shared/tls#ech)。 |
|
|
||||||
| `with_utls` | 启用 uTLS 支持,参阅 [实验性](./configuration/experimental#clash-api-fields)。 |
|
|
||||||
| `with_acme` | 启用 ACME TLS 证书签发支持,参阅 [TLS](./configuration/shared/tls)。 |
|
|
||||||
| `with_clash_api` | 启用 Clash API 支持,参阅 [实验性](./configuration/experimental#clash-api-fields)。 |
|
|
||||||
| `with_v2ray_api` | 启用 V2Rat API 支持,参阅 [实验性](./configuration/experimental#v2ray-api-fields)。 |
|
|
||||||
| `with_gvisor` | 启用 gVisor 支持,参阅 [Tun 入站](./configuration/inbound/tun#stack) 和 [WireGuard 出站](./configuration/outbound/wireguard#system_interface)。 |
|
|
||||||
| `with_embedded_tor` (需要 CGO) | 启用 嵌入式 Tor 支持,参阅 [Tor 出站](./configuration/outbound/tor)。 |
|
|
||||||
| `with_lwip` (需要 CGO) | 启用 LWIP Tun 栈支持,参阅 [Tun 入站](./configuration/inbound/tun#stack)。 |
|
|
||||||
|
|
||||||
二进制文件将被构建在 `$GOPATH/bin` 下。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sing-box version
|
|
||||||
```
|
|
||||||
|
|
||||||
同时推荐使用 systemd 来管理 sing-box 服务器实例。
|
|
||||||
参阅 [Linux 服务器安装示例](./examples/linux-server-installation)。
|
|
||||||
|
|
||||||
## 授权
|
## 授权
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
21
docs/installation/clients/sfi/index.md
Normal file
21
docs/installation/clients/sfi/index.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# SFI
|
||||||
|
|
||||||
|
Experimental official iOS client for sing-box.
|
||||||
|
|
||||||
|
#### Requirements
|
||||||
|
|
||||||
|
* iOS 15.0+
|
||||||
|
* macOS 12.0+ with Apple Silicon
|
||||||
|
|
||||||
|
#### Download
|
||||||
|
|
||||||
|
* [TestFlight](https://testflight.apple.com/join/c6ylui2j)
|
||||||
|
|
||||||
|
#### Limit
|
||||||
|
|
||||||
|
* `system` tun stack not working
|
||||||
|
|
||||||
|
#### Privacy policy
|
||||||
|
|
||||||
|
* SFI did not collect or share personal data.
|
||||||
|
* The data generated by the software is always on your device.
|
||||||
21
docs/installation/clients/sfi/index.zh.md
Normal file
21
docs/installation/clients/sfi/index.zh.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# SFI
|
||||||
|
|
||||||
|
实验性的官方 iOS sing-box 客户端。
|
||||||
|
|
||||||
|
#### 要求
|
||||||
|
|
||||||
|
* iOS 15.0+
|
||||||
|
* macOS 12.0+ with Apple Silicon
|
||||||
|
|
||||||
|
#### 下载
|
||||||
|
|
||||||
|
* [TestFlight](https://testflight.apple.com/join/c6ylui2j)
|
||||||
|
|
||||||
|
#### 限制
|
||||||
|
|
||||||
|
* `system` tun stack 不工作
|
||||||
|
|
||||||
|
#### 隐私政策
|
||||||
|
|
||||||
|
* SFI 不收集或共享个人数据。
|
||||||
|
* 软件生成的数据始终在您的设备上。
|
||||||
39
docs/installation/from-source.md
Normal file
39
docs/installation/from-source.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Install from source
|
||||||
|
|
||||||
|
sing-box requires Golang **1.18.5** or a higher version.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install -v github.com/sagernet/sing-box/cmd/sing-box@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Install with options:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
| 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_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). |
|
||||||
|
| `with_utls` | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](./configuration/shared/tls#utls). |
|
||||||
|
| `with_reality_server` | Build with reality TLS server support, see [TLS](./configuration/shared/tls). |
|
||||||
|
| `with_acme` | Build with ACME TLS certificate issuer support, see [TLS](./configuration/shared/tls). |
|
||||||
|
| `with_clash_api` | Build with Clash API support, see [Experimental](./configuration/experimental#clash-api-fields). |
|
||||||
|
| `with_v2ray_api` | Build with V2Ray API support, see [Experimental](./configuration/experimental#v2ray-api-fields). |
|
||||||
|
| `with_gvisor` | Build with gVisor support, see [Tun inbound](./configuration/inbound/tun#stack) and [WireGuard outbound](./configuration/outbound/wireguard#system_interface). |
|
||||||
|
| `with_embedded_tor` (CGO required) | Build with embedded Tor support, see [Tor outbound](./configuration/outbound/tor). |
|
||||||
|
| `with_lwip` (CGO required) | Build with LWIP Tun stack support, see [Tun inbound](./configuration/inbound/tun#stack). |
|
||||||
|
|
||||||
|
The binary is built under $GOPATH/bin
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sing-box version
|
||||||
|
```
|
||||||
|
|
||||||
|
It is also recommended to use systemd to manage sing-box service,
|
||||||
|
see [Linux server installation example](./examples/linux-server-installation).
|
||||||
39
docs/installation/from-source.zh.md
Normal file
39
docs/installation/from-source.zh.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# 从源代码安装
|
||||||
|
|
||||||
|
sing-box 需要 Golang **1.18.5** 或更高版本。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install -v github.com/sagernet/sing-box/cmd/sing-box@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
自定义安装:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
| 构建标志 | 描述 |
|
||||||
|
|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `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)。 |
|
||||||
|
| `with_utls` | 启用 [uTLS](https://github.com/refraction-networking/utls) 支持,参阅 [TLS](./configuration/shared/tls#utls)。 |
|
||||||
|
| `with_reality_server` | 启用 reality TLS 服务器支持,参阅 [TLS](./configuration/shared/tls)。 |
|
||||||
|
| `with_acme` | 启用 ACME TLS 证书签发支持,参阅 [TLS](./configuration/shared/tls)。 |
|
||||||
|
| `with_clash_api` | 启用 Clash API 支持,参阅 [实验性](./configuration/experimental#clash-api-fields)。 |
|
||||||
|
| `with_v2ray_api` | 启用 V2Ray API 支持,参阅 [实验性](./configuration/experimental#v2ray-api-fields)。 |
|
||||||
|
| `with_gvisor` | 启用 gVisor 支持,参阅 [Tun 入站](./configuration/inbound/tun#stack) 和 [WireGuard 出站](./configuration/outbound/wireguard#system_interface)。 |
|
||||||
|
| `with_embedded_tor` (需要 CGO) | 启用 嵌入式 Tor 支持,参阅 [Tor 出站](./configuration/outbound/tor)。 |
|
||||||
|
| `with_lwip` (需要 CGO) | 启用 LWIP Tun 栈支持,参阅 [Tun 入站](./configuration/inbound/tun#stack)。 |
|
||||||
|
|
||||||
|
二进制文件将被构建在 `$GOPATH/bin` 下。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sing-box version
|
||||||
|
```
|
||||||
|
|
||||||
|
同时推荐使用 systemd 来管理 sing-box 服务器实例。
|
||||||
|
参阅 [Linux 服务器安装示例](./examples/linux-server-installation)。
|
||||||
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,9 +42,9 @@ type Server struct {
|
|||||||
httpServer *http.Server
|
httpServer *http.Server
|
||||||
trafficManager *trafficontrol.Manager
|
trafficManager *trafficontrol.Manager
|
||||||
urlTestHistory *urltest.HistoryStorage
|
urlTestHistory *urltest.HistoryStorage
|
||||||
tcpListener net.Listener
|
|
||||||
mode string
|
mode string
|
||||||
storeSelected bool
|
storeSelected bool
|
||||||
|
cacheFilePath string
|
||||||
cacheFile adapter.ClashCacheFile
|
cacheFile adapter.ClashCacheFile
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,11 +71,12 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
|||||||
if cachePath == "" {
|
if cachePath == "" {
|
||||||
cachePath = "cache.db"
|
cachePath = "cache.db"
|
||||||
}
|
}
|
||||||
cacheFile, err := cachefile.Open(cachePath)
|
if foundPath, loaded := C.FindPath(cachePath); loaded {
|
||||||
if err != nil {
|
cachePath = foundPath
|
||||||
return nil, E.Cause(err, "open cache file")
|
} else {
|
||||||
|
cachePath = C.BasePath(cachePath)
|
||||||
}
|
}
|
||||||
server.cacheFile = cacheFile
|
server.cacheFilePath = cachePath
|
||||||
}
|
}
|
||||||
cors := cors.New(cors.Options{
|
cors := cors.New(cors.Options{
|
||||||
AllowedOrigins: []string{"*"},
|
AllowedOrigins: []string{"*"},
|
||||||
@@ -99,10 +100,11 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
|||||||
r.Mount("/script", scriptRouter())
|
r.Mount("/script", scriptRouter())
|
||||||
r.Mount("/profile", profileRouter())
|
r.Mount("/profile", profileRouter())
|
||||||
r.Mount("/cache", cacheRouter())
|
r.Mount("/cache", cacheRouter())
|
||||||
|
r.Mount("/dns", dnsRouter(router))
|
||||||
})
|
})
|
||||||
if options.ExternalUI != "" {
|
if options.ExternalUI != "" {
|
||||||
chiRouter.Group(func(r chi.Router) {
|
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", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
|
||||||
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
|
||||||
fs.ServeHTTP(w, r)
|
fs.ServeHTTP(w, r)
|
||||||
@@ -113,12 +115,18 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Start() error {
|
func (s *Server) Start() error {
|
||||||
|
if s.cacheFilePath != "" {
|
||||||
|
cacheFile, err := cachefile.Open(s.cacheFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open cache file")
|
||||||
|
}
|
||||||
|
s.cacheFile = cacheFile
|
||||||
|
}
|
||||||
listener, err := net.Listen("tcp", s.httpServer.Addr)
|
listener, err := net.Listen("tcp", s.httpServer.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "external controller listen error")
|
return E.Cause(err, "external controller listen error")
|
||||||
}
|
}
|
||||||
s.logger.Info("restful api listening at ", listener.Addr())
|
s.logger.Info("restful api listening at ", listener.Addr())
|
||||||
s.tcpListener = listener
|
|
||||||
go func() {
|
go func() {
|
||||||
err = s.httpServer.Serve(listener)
|
err = s.httpServer.Serve(listener)
|
||||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
@@ -131,7 +139,6 @@ func (s *Server) Start() error {
|
|||||||
func (s *Server) Close() error {
|
func (s *Server) Close() error {
|
||||||
return common.Close(
|
return common.Close(
|
||||||
common.PtrOrNil(s.httpServer),
|
common.PtrOrNil(s.httpServer),
|
||||||
s.tcpListener,
|
|
||||||
s.trafficManager,
|
s.trafficManager,
|
||||||
s.cacheFile,
|
s.cacheFile,
|
||||||
)
|
)
|
||||||
|
|||||||
11
experimental/libbox/command.go
Normal file
11
experimental/libbox/command.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package libbox
|
||||||
|
|
||||||
|
const (
|
||||||
|
CommandLog int32 = iota
|
||||||
|
CommandStatus
|
||||||
|
CommandServiceStop
|
||||||
|
CommandServiceReload
|
||||||
|
CommandCloseConnections
|
||||||
|
)
|
||||||
75
experimental/libbox/command_client.go
Normal file
75
experimental/libbox/command_client.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CommandClient struct {
|
||||||
|
sharedDirectory string
|
||||||
|
handler CommandClientHandler
|
||||||
|
conn net.Conn
|
||||||
|
options CommandClientOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandClientOptions struct {
|
||||||
|
Command int32
|
||||||
|
StatusInterval int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandClientHandler interface {
|
||||||
|
Connected()
|
||||||
|
Disconnected(message string)
|
||||||
|
WriteLog(message string)
|
||||||
|
WriteStatus(message *StatusMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCommandClient(sharedDirectory string, handler CommandClientHandler, options *CommandClientOptions) *CommandClient {
|
||||||
|
return &CommandClient{
|
||||||
|
sharedDirectory: sharedDirectory,
|
||||||
|
handler: handler,
|
||||||
|
options: common.PtrValueOrDefault(options),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clientConnect(sharedDirectory string) (net.Conn, error) {
|
||||||
|
return net.DialUnix("unix", nil, &net.UnixAddr{
|
||||||
|
Name: filepath.Join(sharedDirectory, "command.sock"),
|
||||||
|
Net: "unix",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandClient) Connect() error {
|
||||||
|
conn, err := clientConnect(c.sharedDirectory)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.conn = conn
|
||||||
|
err = binary.Write(conn, binary.BigEndian, uint8(c.options.Command))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch c.options.Command {
|
||||||
|
case CommandLog:
|
||||||
|
c.handler.Connected()
|
||||||
|
go c.handleLogConn(conn)
|
||||||
|
case CommandStatus:
|
||||||
|
err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "write interval")
|
||||||
|
}
|
||||||
|
c.handler.Connected()
|
||||||
|
go c.handleStatusConn(conn)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandClient) Disconnect() error {
|
||||||
|
return common.Close(c.conn)
|
||||||
|
}
|
||||||
30
experimental/libbox/command_conntrack.go
Normal file
30
experimental/libbox/command_conntrack.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
runtimeDebug "runtime/debug"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ClientCloseConnections(sharedDirectory string) error {
|
||||||
|
conn, err := clientConnect(sharedDirectory)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
return binary.Write(conn, binary.BigEndian, uint8(CommandCloseConnections))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommandServer) handleCloseConnections(conn net.Conn) error {
|
||||||
|
conntrack.Close()
|
||||||
|
go func() {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
runtimeDebug.FreeOSMemory()
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
103
experimental/libbox/command_log.go
Normal file
103
experimental/libbox/command_log.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *CommandServer) WriteMessage(message string) {
|
||||||
|
s.subscriber.Emit(message)
|
||||||
|
s.access.Lock()
|
||||||
|
s.savedLines.PushBack(message)
|
||||||
|
if s.savedLines.Len() > 100 {
|
||||||
|
s.savedLines.Remove(s.savedLines.Front())
|
||||||
|
}
|
||||||
|
s.access.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func readLog(reader io.Reader) ([]byte, error) {
|
||||||
|
var messageLength uint16
|
||||||
|
err := binary.Read(reader, binary.BigEndian, &messageLength)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data := make([]byte, messageLength)
|
||||||
|
_, err = io.ReadFull(reader, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeLog(writer io.Writer, message []byte) error {
|
||||||
|
err := binary.Write(writer, binary.BigEndian, uint16(len(message)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write(message)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommandServer) handleLogConn(conn net.Conn) error {
|
||||||
|
var savedLines []string
|
||||||
|
s.access.Lock()
|
||||||
|
savedLines = make([]string, 0, s.savedLines.Len())
|
||||||
|
for element := s.savedLines.Front(); element != nil; element = element.Next() {
|
||||||
|
savedLines = append(savedLines, element.Value)
|
||||||
|
}
|
||||||
|
s.access.Unlock()
|
||||||
|
subscription, done, err := s.observer.Subscribe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.observer.UnSubscribe(subscription)
|
||||||
|
for _, line := range savedLines {
|
||||||
|
err = writeLog(conn, []byte(line))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx := connKeepAlive(conn)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case message := <-subscription:
|
||||||
|
err = writeLog(conn, []byte(message))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case <-done:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandClient) handleLogConn(conn net.Conn) {
|
||||||
|
for {
|
||||||
|
message, err := readLog(conn)
|
||||||
|
if err != nil {
|
||||||
|
c.handler.Disconnected(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.handler.WriteLog(string(message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func connKeepAlive(reader io.Reader) context.Context {
|
||||||
|
ctx, cancel := context.WithCancelCause(context.Background())
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
_, err := readLog(reader)
|
||||||
|
if err != nil {
|
||||||
|
cancel(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
48
experimental/libbox/command_reload.go
Normal file
48
experimental/libbox/command_reload.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ClientServiceReload(sharedDirectory string) error {
|
||||||
|
conn, err := clientConnect(sharedDirectory)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
err = binary.Write(conn, binary.BigEndian, uint8(CommandServiceReload))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var hasError bool
|
||||||
|
err = binary.Read(conn, binary.BigEndian, &hasError)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if hasError {
|
||||||
|
errorMessage, err := rw.ReadVString(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return E.New(errorMessage)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommandServer) handleServiceReload(conn net.Conn) error {
|
||||||
|
rErr := s.handler.ServiceReload()
|
||||||
|
err := binary.Write(conn, binary.BigEndian, rErr != nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if rErr != nil {
|
||||||
|
return rw.WriteVString(conn, rErr.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
99
experimental/libbox/command_server.go
Normal file
99
experimental/libbox/command_server.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/observable"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CommandServer struct {
|
||||||
|
sockPath string
|
||||||
|
listener net.Listener
|
||||||
|
handler CommandServerHandler
|
||||||
|
|
||||||
|
access sync.Mutex
|
||||||
|
savedLines *list.List[string]
|
||||||
|
subscriber *observable.Subscriber[string]
|
||||||
|
observer *observable.Observer[string]
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandServerHandler interface {
|
||||||
|
ServiceStop() error
|
||||||
|
ServiceReload() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCommandServer(sharedDirectory string, handler CommandServerHandler) *CommandServer {
|
||||||
|
server := &CommandServer{
|
||||||
|
sockPath: filepath.Join(sharedDirectory, "command.sock"),
|
||||||
|
handler: handler,
|
||||||
|
savedLines: new(list.List[string]),
|
||||||
|
subscriber: observable.NewSubscriber[string](128),
|
||||||
|
}
|
||||||
|
server.observer = observable.NewObserver[string](server.subscriber, 64)
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommandServer) Start() error {
|
||||||
|
os.Remove(s.sockPath)
|
||||||
|
listener, err := net.ListenUnix("unix", &net.UnixAddr{
|
||||||
|
Name: s.sockPath,
|
||||||
|
Net: "unix",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.listener = listener
|
||||||
|
go s.loopConnection(listener)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommandServer) Close() error {
|
||||||
|
return s.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommandServer) loopConnection(listener net.Listener) {
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
hErr := s.handleConnection(conn)
|
||||||
|
if hErr != nil && !E.IsClosed(err) {
|
||||||
|
log.Warn("log-server: process connection: ", hErr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommandServer) handleConnection(conn net.Conn) error {
|
||||||
|
defer conn.Close()
|
||||||
|
var command uint8
|
||||||
|
err := binary.Read(conn, binary.BigEndian, &command)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "read command")
|
||||||
|
}
|
||||||
|
switch int32(command) {
|
||||||
|
case CommandLog:
|
||||||
|
return s.handleLogConn(conn)
|
||||||
|
case CommandStatus:
|
||||||
|
return s.handleStatusConn(conn)
|
||||||
|
case CommandServiceStop:
|
||||||
|
return s.handleServiceStop(conn)
|
||||||
|
case CommandServiceReload:
|
||||||
|
return s.handleServiceReload(conn)
|
||||||
|
case CommandCloseConnections:
|
||||||
|
return s.handleCloseConnections(conn)
|
||||||
|
default:
|
||||||
|
return E.New("unknown command: ", command)
|
||||||
|
}
|
||||||
|
}
|
||||||
63
experimental/libbox/command_status.go
Normal file
63
experimental/libbox/command_status.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StatusMessage struct {
|
||||||
|
Memory int64
|
||||||
|
Goroutines int32
|
||||||
|
Connections int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func readStatus() StatusMessage {
|
||||||
|
var memStats runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&memStats)
|
||||||
|
var message StatusMessage
|
||||||
|
message.Memory = int64(memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased)
|
||||||
|
message.Goroutines = int32(runtime.NumGoroutine())
|
||||||
|
message.Connections = int32(conntrack.Count())
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommandServer) handleStatusConn(conn net.Conn) error {
|
||||||
|
var interval int64
|
||||||
|
err := binary.Read(conn, binary.BigEndian, &interval)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "read interval")
|
||||||
|
}
|
||||||
|
ticker := time.NewTicker(time.Duration(interval))
|
||||||
|
defer ticker.Stop()
|
||||||
|
ctx := connKeepAlive(conn)
|
||||||
|
for {
|
||||||
|
err = binary.Write(conn, binary.BigEndian, readStatus())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
case <-ticker.C:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandClient) handleStatusConn(conn net.Conn) {
|
||||||
|
for {
|
||||||
|
var message StatusMessage
|
||||||
|
err := binary.Read(conn, binary.BigEndian, &message)
|
||||||
|
if err != nil {
|
||||||
|
c.handler.Disconnected(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.handler.WriteStatus(&message)
|
||||||
|
}
|
||||||
|
}
|
||||||
50
experimental/libbox/command_stop.go
Normal file
50
experimental/libbox/command_stop.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ClientServiceStop(sharedDirectory string) error {
|
||||||
|
conn, err := clientConnect(sharedDirectory)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
err = binary.Write(conn, binary.BigEndian, uint8(CommandServiceStop))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var hasError bool
|
||||||
|
err = binary.Read(conn, binary.BigEndian, &hasError)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if hasError {
|
||||||
|
errorMessage, err := rw.ReadVString(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return E.New(errorMessage)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommandServer) handleServiceStop(conn net.Conn) error {
|
||||||
|
rErr := s.handler.ServiceStop()
|
||||||
|
err := binary.Write(conn, binary.BigEndian, rErr != nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if rErr != nil {
|
||||||
|
return rw.WriteVString(conn, rErr.Error())
|
||||||
|
}
|
||||||
|
debug.FreeOSMemory()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user