mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
200 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aeb7308e81 | ||
|
|
bb1ebfda83 | ||
|
|
c05c798221 | ||
|
|
55b1bcc6a5 | ||
|
|
d6eddce420 | ||
|
|
4bf057139b | ||
|
|
a1b28b8282 | ||
|
|
d0aaf71770 | ||
|
|
2f31202c6b | ||
|
|
e4cc510712 | ||
|
|
e329bf6865 | ||
|
|
2badcec765 | ||
|
|
e71c13b1a2 | ||
|
|
a959a67ed3 | ||
|
|
a1044af579 | ||
|
|
a64b57451a | ||
|
|
f0e2318cbd | ||
|
|
ebec308fd8 | ||
|
|
ca094587be | ||
|
|
ca3b86c781 | ||
|
|
5a1d0047b9 | ||
|
|
4669854039 | ||
|
|
2eecdc38a4 | ||
|
|
83581b7c1a | ||
|
|
d346f0023d | ||
|
|
47b7a29cbd | ||
|
|
cffc07579d | ||
|
|
0ef268637e | ||
|
|
50f5a76380 | ||
|
|
20ca05dd36 | ||
|
|
5a792b186a | ||
|
|
3f458064a3 | ||
|
|
5269231df0 | ||
|
|
fc8e49994c | ||
|
|
e911d4aa4b | ||
|
|
01f6e70bc5 | ||
|
|
5f1e39a42c | ||
|
|
4f7770e254 | ||
|
|
e8c4c942c0 | ||
|
|
253976d6c0 | ||
|
|
f0571b4122 | ||
|
|
1b71e52e90 | ||
|
|
6d24be23da | ||
|
|
2a45c178fa | ||
|
|
81e214812f | ||
|
|
4d23773a25 | ||
|
|
40a0b69918 | ||
|
|
a7b37c5953 | ||
|
|
03663a5093 | ||
|
|
b08226a850 | ||
|
|
edbae5dc4d | ||
|
|
0f8ad0234b | ||
|
|
661eadc3bd | ||
|
|
50c1290567 | ||
|
|
eaccc9759a | ||
|
|
925214869b | ||
|
|
6a2bfd26d0 | ||
|
|
72a81afb76 | ||
|
|
240abe204c | ||
|
|
7c49196792 | ||
|
|
3a2808cff6 | ||
|
|
005d6cf4cf | ||
|
|
36dff630d6 | ||
|
|
1825869124 | ||
|
|
3cadc90375 | ||
|
|
2c6967d7f9 | ||
|
|
fe866b123a | ||
|
|
cbef1b1e59 | ||
|
|
e21f84932c | ||
|
|
7a679bc328 | ||
|
|
6635dd9abc | ||
|
|
ce164724ea | ||
|
|
a3ef7a7d88 | ||
|
|
71218ef0d3 | ||
|
|
e777b4c6dc | ||
|
|
6815f94180 | ||
|
|
b013acd89d | ||
|
|
f7c2eb6e76 | ||
|
|
3ef9b1b343 | ||
|
|
2224c68959 | ||
|
|
bb7d03d1db | ||
|
|
50036924e8 | ||
|
|
c2c3f7284f | ||
|
|
f6fee53676 | ||
|
|
63b8e8ed23 | ||
|
|
6ae86eda98 | ||
|
|
267d9617b7 | ||
|
|
0a06ccae50 | ||
|
|
8de0fad9f5 | ||
|
|
e05bf6308e | ||
|
|
a20a0cb455 | ||
|
|
d29f7475d2 | ||
|
|
aaa6702863 | ||
|
|
bb928f096a | ||
|
|
9f01d5c5b4 | ||
|
|
11629a931b | ||
|
|
126f825241 | ||
|
|
998cc7bd22 | ||
|
|
3efccaa8f5 | ||
|
|
d57b35ec30 | ||
|
|
e82dab027d | ||
|
|
9350f3983b | ||
|
|
53b123241f | ||
|
|
97286eea1e | ||
|
|
343e24969d | ||
|
|
31c294d998 | ||
|
|
3b161ab30c | ||
|
|
41fd1778a7 | ||
|
|
ac930cf1aa | ||
|
|
e143fc510d | ||
|
|
bea177a4cd | ||
|
|
aa05a4d050 | ||
|
|
a8112ff824 | ||
|
|
a7710c3845 | ||
|
|
cb2e15f8a7 | ||
|
|
23aa8a0543 | ||
|
|
edf7d046eb | ||
|
|
de0b5cc1c2 | ||
|
|
2686e8afea | ||
|
|
d9853ca2be | ||
|
|
b617eb5adf | ||
|
|
ddf38799e2 | ||
|
|
5291d43dc8 | ||
|
|
a634830d85 | ||
|
|
e5d191ca73 | ||
|
|
2371f0fd51 | ||
|
|
cfdce7a96f | ||
|
|
dc8ac01dec | ||
|
|
5f18738b2b | ||
|
|
7b4e4ca2d0 | ||
|
|
01ba4668b6 | ||
|
|
e782d21806 | ||
|
|
00155d61fc | ||
|
|
8f2273a2b4 | ||
|
|
0d0526afa2 | ||
|
|
ac2d07b61a | ||
|
|
d35487f422 | ||
|
|
2749f4a013 | ||
|
|
45c679648e | ||
|
|
5f2f7fc8b9 | ||
|
|
83c79102cf | ||
|
|
8b95292e53 | ||
|
|
3de7a2ddd3 | ||
|
|
8437a6cb4e | ||
|
|
9c4d08c6e1 | ||
|
|
e26096085e | ||
|
|
2f1b2199c5 | ||
|
|
af791db01f | ||
|
|
abcf030d89 | ||
|
|
7840dc73e3 | ||
|
|
df9050400e | ||
|
|
fdd38d6cf8 | ||
|
|
9891fd672f | ||
|
|
92a84ee112 | ||
|
|
992331f17e | ||
|
|
4fb227ed86 | ||
|
|
5a1ddea100 | ||
|
|
fbaa2f9de9 | ||
|
|
97ab9bb194 | ||
|
|
61ac141124 | ||
|
|
d4d49d9df5 | ||
|
|
c60a944aac | ||
|
|
17584c245f | ||
|
|
6e84b694a4 | ||
|
|
34a93171f0 | ||
|
|
678f6ef72f | ||
|
|
ae8187ed15 | ||
|
|
12dd1ac87f | ||
|
|
85c8f00885 | ||
|
|
e7b7ae811f | ||
|
|
a9743b77f6 | ||
|
|
4068871d97 | ||
|
|
f05afcea39 | ||
|
|
688e9daef4 | ||
|
|
64edacffb7 | ||
|
|
743df5373b | ||
|
|
e80084316d | ||
|
|
9dcd427743 | ||
|
|
d17e93384b | ||
|
|
c1ffcf365e | ||
|
|
3040e97222 | ||
|
|
5f063fb0b5 | ||
|
|
a7dadd8671 | ||
|
|
c320be75a7 | ||
|
|
bd7adcbb7e | ||
|
|
1d6d3edec5 | ||
|
|
46bfeb574c | ||
|
|
a1449ee40e | ||
|
|
8cb41b5fa6 | ||
|
|
53475c7390 | ||
|
|
5d8af150a7 | ||
|
|
69499a51a5 | ||
|
|
4c050d7f4b | ||
|
|
533fca9fa3 | ||
|
|
187bf2f7bc | ||
|
|
983a4222ad | ||
|
|
2ea506aeb8 | ||
|
|
5b343d4c72 | ||
|
|
be61ca64d4 | ||
|
|
efe33cf48d |
14
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
14
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -44,12 +44,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Version
|
label: Version
|
||||||
description: If you are using the original command line program, please provide the output of the `sing-box version` command.
|
description: If you are using the original command line program, please provide the output of the `sing-box version` command.
|
||||||
value: |-
|
render: shell
|
||||||
<details>
|
|
||||||
```console
|
|
||||||
# Replace this line with the output
|
|
||||||
```
|
|
||||||
</details>
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Description
|
label: Description
|
||||||
@@ -69,9 +64,4 @@ body:
|
|||||||
If you encounter a crash with the graphical client, please provide crash logs.
|
If you encounter a crash with the graphical client, please provide crash logs.
|
||||||
For Apple platform clients, please check `Settings - View Service Log` for crash logs.
|
For Apple platform clients, please check `Settings - View Service Log` for crash logs.
|
||||||
For the Android client, please check the `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` file for crash logs.
|
For the Android client, please check the `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` file for crash logs.
|
||||||
value: |-
|
render: shell
|
||||||
<details>
|
|
||||||
```console
|
|
||||||
# Replace this line with logs
|
|
||||||
```
|
|
||||||
</details>
|
|
||||||
28
.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
28
.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
@@ -44,12 +44,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: 版本
|
label: 版本
|
||||||
description: 如果您使用原始命令行程序,请提供 `sing-box version` 命令的输出。
|
description: 如果您使用原始命令行程序,请提供 `sing-box version` 命令的输出。
|
||||||
value: |-
|
render: shell
|
||||||
<details>
|
|
||||||
```console
|
|
||||||
# 使用输出内容覆盖此行
|
|
||||||
```
|
|
||||||
</details>
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: 描述
|
label: 描述
|
||||||
@@ -69,9 +64,18 @@ body:
|
|||||||
如果您遭遇图形界面应用程序崩溃,请提供崩溃日志。
|
如果您遭遇图形界面应用程序崩溃,请提供崩溃日志。
|
||||||
对于 Apple 平台图形客户端程序,请检查 `Settings - View Service Log` 以导出崩溃日志。
|
对于 Apple 平台图形客户端程序,请检查 `Settings - View Service Log` 以导出崩溃日志。
|
||||||
对于 Android 图形客户端程序,请检查 `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` 文件以导出崩溃日志。
|
对于 Android 图形客户端程序,请检查 `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` 文件以导出崩溃日志。
|
||||||
value: |-
|
render: shell
|
||||||
<details>
|
- type: checkboxes
|
||||||
```console
|
attributes:
|
||||||
# 使用日志内容覆盖此行
|
label: 完整性要求
|
||||||
```
|
description: 我保证我提供了完整的可以在本地重现该问题的服务器、客户端配置文件与流程,而不是一个脱敏的复杂客户端配置文件,否则该 issue 将被关闭。
|
||||||
</details>
|
options:
|
||||||
|
- label: 我保证
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: 负责性要求
|
||||||
|
description: 我保证我阅读了文档,了解所有我编写的配置文件项的含义,而不是大量堆砌看似有用的选项或默认值,否则该 issue 将被关闭。
|
||||||
|
options:
|
||||||
|
- label: 我保证
|
||||||
|
required: true
|
||||||
18
.github/workflows/debug.yml
vendored
18
.github/workflows/debug.yml
vendored
@@ -3,6 +3,7 @@ name: Debug build
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
|
- stable-next
|
||||||
- main-next
|
- main-next
|
||||||
- dev-next
|
- dev-next
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
@@ -11,6 +12,7 @@ on:
|
|||||||
- '!.github/workflows/debug.yml'
|
- '!.github/workflows/debug.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
|
- stable-next
|
||||||
- main-next
|
- main-next
|
||||||
- dev-next
|
- dev-next
|
||||||
|
|
||||||
@@ -20,7 +22,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Get latest go version
|
- name: Get latest go version
|
||||||
@@ -28,7 +30,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
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
|
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@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.version.outputs.go_version }}
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
- name: Add cache to Go proxy
|
- name: Add cache to Go proxy
|
||||||
@@ -48,11 +50,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.18.10
|
go-version: 1.18.10
|
||||||
- name: Cache go module
|
- name: Cache go module
|
||||||
@@ -68,11 +70,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.20.7
|
go-version: 1.20.7
|
||||||
- name: Cache go module
|
- name: Cache go module
|
||||||
@@ -199,7 +201,7 @@ jobs:
|
|||||||
TAGS: with_clash_api,with_quic
|
TAGS: with_clash_api,with_quic
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Get latest go version
|
- name: Get latest go version
|
||||||
@@ -207,7 +209,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
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
|
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@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.version.outputs.go_version }}
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
- name: Build
|
- name: Build
|
||||||
|
|||||||
14
.github/workflows/docker.yml
vendored
14
.github/workflows/docker.yml
vendored
@@ -9,20 +9,20 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Setup QEMU for Docker Buildx
|
- name: Setup QEMU for Docker Buildx
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Docker metadata
|
- name: Docker metadata
|
||||||
id: metadata
|
id: metadata
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/sagernet/sing-box
|
images: ghcr.io/sagernet/sing-box
|
||||||
- name: Get tag to build
|
- name: Get tag to build
|
||||||
@@ -35,10 +35,12 @@ 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@v4
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
|
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
|
||||||
target: dist
|
target: dist
|
||||||
|
build-args: |
|
||||||
|
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
||||||
tags: |
|
tags: |
|
||||||
${{ steps.tag.outputs.latest }}
|
${{ steps.tag.outputs.latest }}
|
||||||
${{ steps.tag.outputs.versioned }}
|
${{ steps.tag.outputs.versioned }}
|
||||||
|
|||||||
10
.github/workflows/lint.yml
vendored
10
.github/workflows/lint.yml
vendored
@@ -3,6 +3,7 @@ name: Lint
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
|
- stable-next
|
||||||
- main-next
|
- main-next
|
||||||
- dev-next
|
- dev-next
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
@@ -11,6 +12,7 @@ on:
|
|||||||
- '!.github/workflows/lint.yml'
|
- '!.github/workflows/lint.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
|
- stable-next
|
||||||
- main-next
|
- main-next
|
||||||
- dev-next
|
- dev-next
|
||||||
|
|
||||||
@@ -20,7 +22,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Get latest go version
|
- name: Get latest go version
|
||||||
@@ -28,10 +30,12 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
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
|
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@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.version.outputs.go_version }}
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v3
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
|
args: --timeout=30m
|
||||||
|
install-mode: binary
|
||||||
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@v8
|
- uses: actions/stale@v9
|
||||||
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
|
||||||
|
|||||||
@@ -16,12 +16,15 @@ builds:
|
|||||||
- with_quic
|
- with_quic
|
||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
|
- with_ech
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_reality_server
|
- with_reality_server
|
||||||
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
targets:
|
targets:
|
||||||
|
- linux_386
|
||||||
- linux_amd64_v1
|
- linux_amd64_v1
|
||||||
- linux_amd64_v3
|
- linux_amd64_v3
|
||||||
- linux_arm64
|
- linux_arm64
|
||||||
@@ -35,6 +38,36 @@ builds:
|
|||||||
- darwin_amd64_v3
|
- darwin_amd64_v3
|
||||||
- darwin_arm64
|
- darwin_arm64
|
||||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
|
- id: legacy
|
||||||
|
main: ./cmd/sing-box
|
||||||
|
flags:
|
||||||
|
- -v
|
||||||
|
- -trimpath
|
||||||
|
asmflags:
|
||||||
|
- all=-trimpath={{.Env.GOPATH}}
|
||||||
|
gcflags:
|
||||||
|
- all=-trimpath={{.Env.GOPATH}}
|
||||||
|
ldflags:
|
||||||
|
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
|
||||||
|
tags:
|
||||||
|
- with_gvisor
|
||||||
|
- with_quic
|
||||||
|
- with_dhcp
|
||||||
|
- with_wireguard
|
||||||
|
- with_ech
|
||||||
|
- with_utls
|
||||||
|
- with_reality_server
|
||||||
|
- with_acme
|
||||||
|
- with_clash_api
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
- GOROOT=/nix/store/kg6i737jjqs923jcijnm003h68c1dghj-go-1.20.11/share/go
|
||||||
|
gobinary: /nix/store/kg6i737jjqs923jcijnm003h68c1dghj-go-1.20.11/bin/go
|
||||||
|
targets:
|
||||||
|
- windows_amd64_v1
|
||||||
|
- windows_386
|
||||||
|
- darwin_amd64_v1
|
||||||
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
- id: android
|
- id: android
|
||||||
main: ./cmd/sing-box
|
main: ./cmd/sing-box
|
||||||
flags:
|
flags:
|
||||||
@@ -51,7 +84,10 @@ builds:
|
|||||||
- with_quic
|
- with_quic
|
||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
|
- with_ech
|
||||||
- with_utls
|
- with_utls
|
||||||
|
- with_reality_server
|
||||||
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=1
|
- CGO_ENABLED=1
|
||||||
@@ -88,6 +124,9 @@ snapshot:
|
|||||||
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
||||||
archives:
|
archives:
|
||||||
- id: archive
|
- id: archive
|
||||||
|
builds:
|
||||||
|
- main
|
||||||
|
- android
|
||||||
format: tar.gz
|
format: tar.gz
|
||||||
format_overrides:
|
format_overrides:
|
||||||
- goos: windows
|
- goos: windows
|
||||||
@@ -96,6 +135,17 @@ archives:
|
|||||||
files:
|
files:
|
||||||
- LICENSE
|
- LICENSE
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||||
|
- id: archive-legacy
|
||||||
|
builds:
|
||||||
|
- legacy
|
||||||
|
format: tar.gz
|
||||||
|
format_overrides:
|
||||||
|
- goos: windows
|
||||||
|
format: zip
|
||||||
|
wrap_in_directory: true
|
||||||
|
files:
|
||||||
|
- LICENSE
|
||||||
|
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
|
||||||
nfpms:
|
nfpms:
|
||||||
- id: package
|
- id: package
|
||||||
package_name: sing-box
|
package_name: sing-box
|
||||||
@@ -108,6 +158,7 @@ nfpms:
|
|||||||
formats:
|
formats:
|
||||||
- deb
|
- deb
|
||||||
- rpm
|
- rpm
|
||||||
|
- archlinux
|
||||||
priority: extra
|
priority: extra
|
||||||
contents:
|
contents:
|
||||||
- src: release/config/config.json
|
- src: release/config/config.json
|
||||||
|
|||||||
12
Dockerfile
12
Dockerfile
@@ -1,23 +1,27 @@
|
|||||||
FROM golang:1.21-alpine AS builder
|
FROM --platform=$BUILDPLATFORM golang:1.21-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
|
||||||
|
ARG TARGETOS TARGETARCH
|
||||||
ARG GOPROXY=""
|
ARG GOPROXY=""
|
||||||
ENV GOPROXY ${GOPROXY}
|
ENV GOPROXY ${GOPROXY}
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
|
ENV GOOS=$TARGETOS
|
||||||
|
ENV GOARCH=$TARGETARCH
|
||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
&& apk add git build-base \
|
&& apk add git build-base \
|
||||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||||
&& go build -v -trimpath -tags with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_clash_api,with_acme \
|
&& go build -v -trimpath -tags \
|
||||||
|
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_clash_api" \
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
FROM alpine AS dist
|
FROM --platform=$TARGETPLATFORM alpine AS dist
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
&& apk upgrade \
|
&& apk upgrade \
|
||||||
&& apk add bash tzdata ca-certificates \
|
&& apk add bash tzdata ca-certificates \
|
||||||
&& rm -rf /var/cache/apk/*
|
&& rm -rf /var/cache/apk/*
|
||||||
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
|
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
|
||||||
ENTRYPOINT ["sing-box"]
|
ENTRYPOINT ["sing-box"]
|
||||||
|
|||||||
44
Makefile
44
Makefile
@@ -1,9 +1,9 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS_GO118 = with_gvisor,with_dhcp,with_wireguard,with_utls,with_reality_server,with_clash_api
|
TAGS_GO118 = with_gvisor,with_dhcp,with_wireguard,with_utls,with_reality_server,with_clash_api
|
||||||
TAGS_GO120 = with_quic
|
TAGS_GO120 = with_quic,with_ech
|
||||||
TAGS ?= $(TAGS_GO118),$(TAGS_GO120)
|
TAGS ?= $(TAGS_GO118),$(TAGS_GO120)
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
|
||||||
|
|
||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||||
@@ -14,7 +14,7 @@ MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
|
|||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
PREFIX ?= $(shell go env GOPATH)
|
PREFIX ?= $(shell go env GOPATH)
|
||||||
|
|
||||||
.PHONY: test release
|
.PHONY: test release docs
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
@@ -28,7 +28,7 @@ ci_build:
|
|||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
install:
|
install:
|
||||||
go build -o $(PREFIX)/bin/$(NAME) $(PARAMS) $(MAIN)
|
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
@gofumpt -l -w .
|
@gofumpt -l -w .
|
||||||
@@ -61,9 +61,9 @@ proto_install:
|
|||||||
release:
|
release:
|
||||||
go run ./cmd/internal/build goreleaser release --clean --skip-publish || exit 1
|
go run ./cmd/internal/build goreleaser release --clean --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/*.pkg.tar.zst dist/release
|
||||||
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release
|
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release
|
||||||
rm -r dist
|
rm -r dist/release
|
||||||
|
|
||||||
release_install:
|
release_install:
|
||||||
go install -v github.com/goreleaser/goreleaser@latest
|
go install -v github.com/goreleaser/goreleaser@latest
|
||||||
@@ -73,18 +73,21 @@ update_android_version:
|
|||||||
go run ./cmd/internal/update_android_version
|
go run ./cmd/internal/update_android_version
|
||||||
|
|
||||||
build_android:
|
build_android:
|
||||||
cd ../sing-box-for-android && ./gradlew :app:assembleRelease
|
cd ../sing-box-for-android && ./gradlew :app:assemblePlayRelease && ./gradlew --stop
|
||||||
|
|
||||||
upload_android:
|
upload_android:
|
||||||
mkdir -p dist/release_android
|
mkdir -p dist/release_android
|
||||||
cp ../sing-box-for-android/app/build/outputs/apk/release/*.apk dist/release_android
|
cp ../sing-box-for-android/app/build/outputs/apk/play/release/*.apk dist/release_android
|
||||||
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release_android
|
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release_android
|
||||||
rm -rf dist/release_android
|
rm -rf dist/release_android
|
||||||
|
|
||||||
release_android: lib_android update_android_version build_android upload_android
|
release_android: lib_android update_android_version build_android upload_android
|
||||||
|
|
||||||
publish_android:
|
publish_android:
|
||||||
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadRelease
|
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle
|
||||||
|
|
||||||
|
publish_android_appcenter:
|
||||||
|
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadPlayRelease
|
||||||
|
|
||||||
build_ios:
|
build_ios:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
@@ -93,7 +96,7 @@ build_ios:
|
|||||||
|
|
||||||
upload_ios_app_store:
|
upload_ios_app_store:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist
|
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
release_ios: build_ios upload_ios_app_store
|
release_ios: build_ios upload_ios_app_store
|
||||||
|
|
||||||
@@ -104,7 +107,7 @@ build_macos:
|
|||||||
|
|
||||||
upload_macos_app_store:
|
upload_macos_app_store:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath build/SFM.xcarchive -exportOptionsPlist SFI/Upload.plist
|
xcodebuild -exportArchive -archivePath build/SFM.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
release_macos: build_macos upload_macos_app_store
|
release_macos: build_macos upload_macos_app_store
|
||||||
|
|
||||||
@@ -115,7 +118,7 @@ build_macos_independent:
|
|||||||
|
|
||||||
notarize_macos_independent:
|
notarize_macos_independent:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath "build/SFM.System.xcarchive" -exportOptionsPlist SFM.System/Upload.plist
|
xcodebuild -exportArchive -archivePath "build/SFM.System.xcarchive" -exportOptionsPlist SFM.System/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
wait_notarize_macos_independent:
|
wait_notarize_macos_independent:
|
||||||
sleep 60
|
sleep 60
|
||||||
@@ -137,12 +140,11 @@ release_macos_independent: build_macos_independent notarize_macos_independent wa
|
|||||||
build_tvos:
|
build_tvos:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFT.xcarchive && \
|
rm -rf build/SFT.xcarchive && \
|
||||||
export DEVELOPER_DIR=/Applications/Xcode-beta.app/Contents/Developer && \
|
|
||||||
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive
|
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive
|
||||||
|
|
||||||
upload_tvos_app_store:
|
upload_tvos_app_store:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist
|
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
release_tvos: build_tvos upload_tvos_app_store
|
release_tvos: build_tvos upload_tvos_app_store
|
||||||
|
|
||||||
@@ -150,10 +152,8 @@ update_apple_version:
|
|||||||
go run ./cmd/internal/update_apple_version
|
go run ./cmd/internal/update_apple_version
|
||||||
|
|
||||||
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_independent
|
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_independent
|
||||||
rm -rf dist
|
|
||||||
|
|
||||||
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
||||||
rm -rf dist
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@go test -v ./... && \
|
@go test -v ./... && \
|
||||||
@@ -179,9 +179,17 @@ lib:
|
|||||||
|
|
||||||
lib_install:
|
lib_install:
|
||||||
go get -v -d
|
go get -v -d
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20230728014906-3de089147f59
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20230915142329-c6740b6d2950
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230728014906-3de089147f59
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230915142329-c6740b6d2950
|
||||||
|
|
||||||
|
docs:
|
||||||
|
mkdocs serve
|
||||||
|
|
||||||
|
publish_docs:
|
||||||
|
mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
|
||||||
|
|
||||||
|
docs_install:
|
||||||
|
pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
|
||||||
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
|
||||||
|
|||||||
104
adapter/conn_router.go
Normal file
104
adapter/conn_router.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConnectionRouter interface {
|
||||||
|
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
|
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouteHandler(
|
||||||
|
metadata InboundContext,
|
||||||
|
router ConnectionRouter,
|
||||||
|
logger logger.ContextLogger,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &routeHandlerWrapper{
|
||||||
|
metadata: metadata,
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouteContextHandler(
|
||||||
|
router ConnectionRouter,
|
||||||
|
logger logger.ContextLogger,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &routeContextHandlerWrapper{
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
|
||||||
|
|
||||||
|
type routeHandlerWrapper struct {
|
||||||
|
metadata InboundContext
|
||||||
|
router ConnectionRouter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
myMetadata := w.metadata
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RouteConnection(ctx, conn, myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
|
myMetadata := w.metadata
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.logger.ErrorContext(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
|
||||||
|
|
||||||
|
type routeContextHandlerWrapper struct {
|
||||||
|
router ConnectionRouter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
myMetadata := ContextFrom(ctx)
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RouteConnection(ctx, conn, *myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
|
myMetadata := ContextFrom(ctx)
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.logger.ErrorContext(ctx, err)
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@ type OutboundGroup interface {
|
|||||||
|
|
||||||
type URLTestGroup interface {
|
type URLTestGroup interface {
|
||||||
OutboundGroup
|
OutboundGroup
|
||||||
URLTest(ctx context.Context, url string) (map[string]uint16, error)
|
URLTest(ctx context.Context) (map[string]uint16, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func OutboundTag(detour Outbound) string {
|
func OutboundTag(detour Outbound) string {
|
||||||
|
|||||||
@@ -75,3 +75,11 @@ func AppendContext(ctx context.Context) (context.Context, *InboundContext) {
|
|||||||
metadata = new(InboundContext)
|
metadata = new(InboundContext)
|
||||||
return WithContext(ctx, metadata), metadata
|
return WithContext(ctx, metadata), metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
||||||
|
var newMetadata InboundContext
|
||||||
|
if metadata := ContextFrom(ctx); metadata != nil {
|
||||||
|
newMetadata = *metadata
|
||||||
|
}
|
||||||
|
return WithContext(ctx, &newMetadata), &newMetadata
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,20 +2,20 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/geoip"
|
"github.com/sagernet/sing-box/common/geoip"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
N "github.com/sagernet/sing/common/network"
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
mdns "github.com/miekg/dns"
|
mdns "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Router interface {
|
type Router interface {
|
||||||
Service
|
Service
|
||||||
|
PostStarter
|
||||||
|
|
||||||
Outbounds() []Outbound
|
Outbounds() []Outbound
|
||||||
Outbound(tag string) (Outbound, bool)
|
Outbound(tag string) (Outbound, bool)
|
||||||
@@ -23,8 +23,7 @@ type Router interface {
|
|||||||
|
|
||||||
FakeIPStore() FakeIPStore
|
FakeIPStore() FakeIPStore
|
||||||
|
|
||||||
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
ConnectionRouter
|
||||||
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
|
||||||
|
|
||||||
GeoIPReader() *geoip.Reader
|
GeoIPReader() *geoip.Reader
|
||||||
LoadGeosite(code string) (Rule, error)
|
LoadGeosite(code string) (Rule, error)
|
||||||
@@ -43,10 +42,9 @@ type Router interface {
|
|||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
|
WIFIState() WIFIState
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
|
|
||||||
TimeService
|
|
||||||
|
|
||||||
ClashServer() ClashServer
|
ClashServer() ClashServer
|
||||||
SetClashServer(server ClashServer)
|
SetClashServer(server ClashServer)
|
||||||
|
|
||||||
@@ -56,18 +54,12 @@ type Router interface {
|
|||||||
ResetNetwork() error
|
ResetNetwork() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type routerContextKey struct{}
|
|
||||||
|
|
||||||
func ContextWithRouter(ctx context.Context, router Router) context.Context {
|
func ContextWithRouter(ctx context.Context, router Router) context.Context {
|
||||||
return context.WithValue(ctx, (*routerContextKey)(nil), router)
|
return service.ContextWith(ctx, router)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RouterFromContext(ctx context.Context) Router {
|
func RouterFromContext(ctx context.Context) Router {
|
||||||
metadata := ctx.Value((*routerContextKey)(nil))
|
return service.FromContext[Router](ctx)
|
||||||
if metadata == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return metadata.(Router)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Rule interface {
|
type Rule interface {
|
||||||
@@ -88,3 +80,8 @@ type DNSRule interface {
|
|||||||
type InterfaceUpdateListener interface {
|
type InterfaceUpdateListener interface {
|
||||||
InterfaceUpdated()
|
InterfaceUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WIFIState struct {
|
||||||
|
SSID string
|
||||||
|
BSSID string
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,7 +18,6 @@ type V2RayServerTransport interface {
|
|||||||
type V2RayServerTransportHandler interface {
|
type V2RayServerTransportHandler interface {
|
||||||
N.TCPConnectionHandler
|
N.TCPConnectionHandler
|
||||||
E.Handler
|
E.Handler
|
||||||
FallbackConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type V2RayClientTransport interface {
|
type V2RayClientTransport interface {
|
||||||
|
|||||||
22
box.go
22
box.go
@@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
"github.com/sagernet/sing/service/pause"
|
"github.com/sagernet/sing/service/pause"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ type Options struct {
|
|||||||
option.Options
|
option.Options
|
||||||
Context context.Context
|
Context context.Context
|
||||||
PlatformInterface platform.Interface
|
PlatformInterface platform.Interface
|
||||||
|
PlatformLogWriter log.PlatformWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(options Options) (*Box, error) {
|
func New(options Options) (*Box, error) {
|
||||||
@@ -47,13 +49,14 @@ func New(options Options) (*Box, error) {
|
|||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
}
|
}
|
||||||
|
ctx = service.ContextWithDefaultRegistry(ctx)
|
||||||
ctx = pause.ContextWithDefaultManager(ctx)
|
ctx = pause.ContextWithDefaultManager(ctx)
|
||||||
createdAt := time.Now()
|
createdAt := time.Now()
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
||||||
var needClashAPI bool
|
var needClashAPI bool
|
||||||
var needV2RayAPI bool
|
var needV2RayAPI bool
|
||||||
if experimentalOptions.ClashAPI != nil || options.PlatformInterface != nil {
|
if experimentalOptions.ClashAPI != nil || options.PlatformLogWriter != nil {
|
||||||
needClashAPI = true
|
needClashAPI = true
|
||||||
}
|
}
|
||||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||||
@@ -69,7 +72,7 @@ func New(options Options) (*Box, error) {
|
|||||||
Observable: needClashAPI,
|
Observable: needClashAPI,
|
||||||
DefaultWriter: defaultLogWriter,
|
DefaultWriter: defaultLogWriter,
|
||||||
BaseTime: createdAt,
|
BaseTime: createdAt,
|
||||||
PlatformWriter: options.PlatformInterface,
|
PlatformWriter: options.PlatformLogWriter,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create log factory")
|
return nil, E.Cause(err, "create log factory")
|
||||||
@@ -255,7 +258,7 @@ func (s *Box) start() error {
|
|||||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return s.postStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) postStart() error {
|
func (s *Box) postStart() error {
|
||||||
@@ -266,16 +269,17 @@ func (s *Box) postStart() error {
|
|||||||
return E.Cause(err, "start ", serviceName)
|
return E.Cause(err, "start ", serviceName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for serviceName, service := range s.outbounds {
|
for _, outbound := range s.outbounds {
|
||||||
if lateService, isLateService := service.(adapter.PostStarter); isLateService {
|
if lateOutbound, isLateOutbound := outbound.(adapter.PostStarter); isLateOutbound {
|
||||||
s.logger.Trace("post-starting ", service)
|
s.logger.Trace("post-starting outbound/", outbound.Tag())
|
||||||
err := lateService.PostStart()
|
err := lateOutbound.PostStart()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "post-start ", serviceName)
|
return E.Cause(err, "post-start outbound/", outbound.Tag())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
s.logger.Trace("post-starting router")
|
||||||
|
return s.router.PostStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) Close() error {
|
func (s *Box) Close() error {
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ func (s *Box) startOutbounds() error {
|
|||||||
}
|
}
|
||||||
problemOutbound := outbounds[problemOutboundTag]
|
problemOutbound := outbounds[problemOutboundTag]
|
||||||
if problemOutbound == nil {
|
if problemOutbound == nil {
|
||||||
return E.New("dependency[", problemOutbound, "] not found for outbound[", outboundTags[oCurrent], "]")
|
return E.New("dependency[", problemOutboundTag, "] not found for outbound[", outboundTags[oCurrent], "]")
|
||||||
}
|
}
|
||||||
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
|
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
build_shared.FindSDK()
|
build_shared.FindSDK()
|
||||||
|
|
||||||
if os.Getenv("build.Default.GOPATH") == "" {
|
if os.Getenv("GOPATH") == "" {
|
||||||
os.Setenv("GOPATH", build.Default.GOPATH)
|
os.Setenv("GOPATH", build.Default.GOPATH)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func init() {
|
|||||||
sharedFlags = append(sharedFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
sharedFlags = append(sharedFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
||||||
debugFlags = append(debugFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
debugFlags = append(debugFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||||
|
|
||||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_ech", "with_utls", "with_clash_api")
|
||||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ func ReadTag() (string, error) {
|
|||||||
}
|
}
|
||||||
shortCommit, _ := shell.Exec("git", "rev-parse", "--short", "HEAD").ReadOutput()
|
shortCommit, _ := shell.Exec("git", "rev-parse", "--short", "HEAD").ReadOutput()
|
||||||
version := badversion.Parse(currentTagRev[1:])
|
version := badversion.Parse(currentTagRev[1:])
|
||||||
if version.PreReleaseIdentifier == "" {
|
|
||||||
version.Patch++
|
|
||||||
}
|
|
||||||
return version.String() + "-" + shortCommit, nil
|
return version.String() + "-" + shortCommit, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,11 +29,17 @@ func main() {
|
|||||||
newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfa"}, newVersion.VersionString())
|
newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfa"}, newVersion.VersionString())
|
||||||
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfa.independent", "io.nekohasekai.sfa.system"}, newVersion.String())
|
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfa.independent", "io.nekohasekai.sfa.system"}, newVersion.String())
|
||||||
if updated0 || updated1 {
|
if updated0 || updated1 {
|
||||||
log.Info("updated version to ", newVersion.VersionString())
|
log.Info("updated version to ", newVersion.VersionString(), " (", newVersion.String(), ")")
|
||||||
common.Must(os.WriteFile("sing-box.xcodeproj/project.pbxproj.bak", []byte(projectContent), 0o644))
|
}
|
||||||
|
var updated2 bool
|
||||||
|
if macProjectVersion := os.Getenv("MACOS_PROJECT_VERSION"); macProjectVersion != "" {
|
||||||
|
newContent, updated2 = findAndReplaceProjectVersion(objectsMap, newContent, []string{"SFM"}, macProjectVersion)
|
||||||
|
if updated2 {
|
||||||
|
log.Info("updated macos project version to ", macProjectVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if updated0 || updated1 || updated2 {
|
||||||
common.Must(os.WriteFile("sing-box.xcodeproj/project.pbxproj", []byte(newContent), 0o644))
|
common.Must(os.WriteFile("sing-box.xcodeproj/project.pbxproj", []byte(newContent), 0o644))
|
||||||
} else {
|
|
||||||
log.Info("version not changed")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +67,30 @@ func findAndReplace(objectsMap map[string]any, projectContent string, bundleIDLi
|
|||||||
return projectContent, updated
|
return projectContent, updated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findAndReplaceProjectVersion(objectsMap map[string]any, projectContent string, directoryList []string, newVersion string) (string, bool) {
|
||||||
|
objectKeyList := findObjectKeyByDirectory(objectsMap, directoryList)
|
||||||
|
var updated bool
|
||||||
|
for _, objectKey := range objectKeyList {
|
||||||
|
matchRegexp := common.Must1(regexp.Compile(objectKey + ".*= \\{"))
|
||||||
|
indexes := matchRegexp.FindStringIndex(projectContent)
|
||||||
|
if len(indexes) < 2 {
|
||||||
|
println(projectContent)
|
||||||
|
log.Fatal("failed to find object key ", objectKey, ": ", strings.Index(projectContent, objectKey))
|
||||||
|
}
|
||||||
|
indexStart := indexes[1]
|
||||||
|
indexEnd := indexStart + strings.Index(projectContent[indexStart:], "}")
|
||||||
|
versionStart := indexStart + strings.Index(projectContent[indexStart:indexEnd], "CURRENT_PROJECT_VERSION = ") + 26
|
||||||
|
versionEnd := versionStart + strings.Index(projectContent[versionStart:indexEnd], ";")
|
||||||
|
version := projectContent[versionStart:versionEnd]
|
||||||
|
if version == newVersion {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
updated = true
|
||||||
|
projectContent = projectContent[:versionStart] + newVersion + projectContent[versionEnd:]
|
||||||
|
}
|
||||||
|
return projectContent, updated
|
||||||
|
}
|
||||||
|
|
||||||
func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
|
func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
|
||||||
var objectKeyList []string
|
var objectKeyList []string
|
||||||
for objectKey, object := range objectsMap {
|
for objectKey, object := range objectsMap {
|
||||||
@@ -78,3 +108,24 @@ func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
|
|||||||
}
|
}
|
||||||
return objectKeyList
|
return objectKeyList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findObjectKeyByDirectory(objectsMap map[string]any, directoryList []string) []string {
|
||||||
|
var objectKeyList []string
|
||||||
|
for objectKey, object := range objectsMap {
|
||||||
|
buildSettings := object.(map[string]any)["buildSettings"]
|
||||||
|
if buildSettings == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
infoPListFile := buildSettings.(map[string]any)["INFOPLIST_FILE"]
|
||||||
|
if infoPListFile == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, searchDirectory := range directoryList {
|
||||||
|
if strings.HasPrefix(infoPListFile.(string), searchDirectory+"/") {
|
||||||
|
objectKeyList = append(objectKeyList, objectKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return objectKeyList
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
|
|
||||||
"github.com/gofrs/uuid/v5"
|
"github.com/gofrs/uuid/v5"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var commandGenerate = &cobra.Command{
|
var commandGenerate = &cobra.Command{
|
||||||
@@ -22,8 +21,7 @@ var commandGenerate = &cobra.Command{
|
|||||||
func init() {
|
func init() {
|
||||||
commandGenerate.AddCommand(commandGenerateUUID)
|
commandGenerate.AddCommand(commandGenerateUUID)
|
||||||
commandGenerate.AddCommand(commandGenerateRandom)
|
commandGenerate.AddCommand(commandGenerateRandom)
|
||||||
commandGenerate.AddCommand(commandGenerateWireGuardKeyPair)
|
|
||||||
commandGenerate.AddCommand(commandGenerateRealityKeyPair)
|
|
||||||
mainCommand.AddCommand(commandGenerate)
|
mainCommand.AddCommand(commandGenerate)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,48 +90,3 @@ func generateUUID() error {
|
|||||||
_, err = os.Stdout.WriteString(newUUID.String() + "\n")
|
_, err = os.Stdout.WriteString(newUUID.String() + "\n")
|
||||||
return err
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
39
cmd/sing-box/cmd_generate_ech.go
Normal file
39
cmd/sing-box/cmd_generate_ech.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pqSignatureSchemesEnabled bool
|
||||||
|
|
||||||
|
var commandGenerateECHKeyPair = &cobra.Command{
|
||||||
|
Use: "ech-keypair <plain_server_name>",
|
||||||
|
Short: "Generate TLS ECH key pair",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := generateECHKeyPair(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandGenerateECHKeyPair.Flags().BoolVar(&pqSignatureSchemesEnabled, "pq-signature-schemes-enabled", false, "Enable PQ signature schemes")
|
||||||
|
commandGenerate.AddCommand(commandGenerateECHKeyPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateECHKeyPair(serverName string) error {
|
||||||
|
configPem, keyPem, err := tls.ECHKeygenDefault(serverName, pqSignatureSchemesEnabled)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
os.Stdout.WriteString(configPem)
|
||||||
|
os.Stdout.WriteString(keyPem)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
40
cmd/sing-box/cmd_generate_tls.go
Normal file
40
cmd/sing-box/cmd_generate_tls.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var flagGenerateTLSKeyPairMonths int
|
||||||
|
|
||||||
|
var commandGenerateTLSKeyPair = &cobra.Command{
|
||||||
|
Use: "tls-keypair <server_name>",
|
||||||
|
Short: "Generate TLS self sign key pair",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := generateTLSKeyPair(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandGenerateTLSKeyPair.Flags().IntVarP(&flagGenerateTLSKeyPairMonths, "months", "m", 1, "Valid months")
|
||||||
|
commandGenerate.AddCommand(commandGenerateTLSKeyPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateTLSKeyPair(serverName string) error {
|
||||||
|
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
os.Stdout.WriteString(string(privateKeyPem) + "\n")
|
||||||
|
os.Stdout.WriteString(string(publicKeyPem) + "\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
40
cmd/sing-box/cmd_generate_vapid.go
Normal file
40
cmd/sing-box/cmd_generate_vapid.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
//go:build go1.20
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdh"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandGenerateVAPIDKeyPair = &cobra.Command{
|
||||||
|
Use: "vapid-keypair",
|
||||||
|
Short: "Generate VAPID key pair",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := generateVAPIDKeyPair()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandGenerate.AddCommand(commandGenerateVAPIDKeyPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateVAPIDKeyPair() error {
|
||||||
|
privateKey, err := ecdh.P256().GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
publicKey := privateKey.PublicKey()
|
||||||
|
os.Stdout.WriteString("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey.Bytes()) + "\n")
|
||||||
|
os.Stdout.WriteString("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey.Bytes()) + "\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
61
cmd/sing-box/cmd_generate_wireguard.go
Normal file
61
cmd/sing-box/cmd_generate_wireguard.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandGenerate.AddCommand(commandGenerateWireGuardKeyPair)
|
||||||
|
commandGenerate.AddCommand(commandGenerateRealityKeyPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
174
cmd/sing-box/cmd_merge.go
Normal file
174
cmd/sing-box/cmd_merge.go
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandMerge = &cobra.Command{
|
||||||
|
Use: "merge [output]",
|
||||||
|
Short: "Merge configurations",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := merge(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
mainCommand.AddCommand(commandMerge)
|
||||||
|
}
|
||||||
|
|
||||||
|
func merge(outputPath string) error {
|
||||||
|
mergedOptions, err := readConfigAndMerge()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = mergePathResources(&mergedOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
encoder := json.NewEncoder(buffer)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
err = encoder.Encode(mergedOptions)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "encode config")
|
||||||
|
}
|
||||||
|
if existsContent, err := os.ReadFile(outputPath); err != nil {
|
||||||
|
if string(existsContent) == buffer.String() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = rw.WriteFile(outputPath, buffer.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outputPath, _ = filepath.Abs(outputPath)
|
||||||
|
os.Stderr.WriteString(outputPath + "\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergePathResources(options *option.Options) error {
|
||||||
|
for index, inbound := range options.Inbounds {
|
||||||
|
switch inbound.Type {
|
||||||
|
case C.TypeHTTP:
|
||||||
|
inbound.HTTPOptions.TLS = mergeTLSInboundOptions(inbound.HTTPOptions.TLS)
|
||||||
|
case C.TypeMixed:
|
||||||
|
inbound.MixedOptions.TLS = mergeTLSInboundOptions(inbound.MixedOptions.TLS)
|
||||||
|
case C.TypeVMess:
|
||||||
|
inbound.VMessOptions.TLS = mergeTLSInboundOptions(inbound.VMessOptions.TLS)
|
||||||
|
case C.TypeTrojan:
|
||||||
|
inbound.TrojanOptions.TLS = mergeTLSInboundOptions(inbound.TrojanOptions.TLS)
|
||||||
|
case C.TypeNaive:
|
||||||
|
inbound.NaiveOptions.TLS = mergeTLSInboundOptions(inbound.NaiveOptions.TLS)
|
||||||
|
case C.TypeHysteria:
|
||||||
|
inbound.HysteriaOptions.TLS = mergeTLSInboundOptions(inbound.HysteriaOptions.TLS)
|
||||||
|
case C.TypeVLESS:
|
||||||
|
inbound.VLESSOptions.TLS = mergeTLSInboundOptions(inbound.VLESSOptions.TLS)
|
||||||
|
case C.TypeTUIC:
|
||||||
|
inbound.TUICOptions.TLS = mergeTLSInboundOptions(inbound.TUICOptions.TLS)
|
||||||
|
case C.TypeHysteria2:
|
||||||
|
inbound.Hysteria2Options.TLS = mergeTLSInboundOptions(inbound.Hysteria2Options.TLS)
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
options.Inbounds[index] = inbound
|
||||||
|
}
|
||||||
|
for index, outbound := range options.Outbounds {
|
||||||
|
switch outbound.Type {
|
||||||
|
case C.TypeHTTP:
|
||||||
|
outbound.HTTPOptions.TLS = mergeTLSOutboundOptions(outbound.HTTPOptions.TLS)
|
||||||
|
case C.TypeVMess:
|
||||||
|
outbound.VMessOptions.TLS = mergeTLSOutboundOptions(outbound.VMessOptions.TLS)
|
||||||
|
case C.TypeTrojan:
|
||||||
|
outbound.TrojanOptions.TLS = mergeTLSOutboundOptions(outbound.TrojanOptions.TLS)
|
||||||
|
case C.TypeHysteria:
|
||||||
|
outbound.HysteriaOptions.TLS = mergeTLSOutboundOptions(outbound.HysteriaOptions.TLS)
|
||||||
|
case C.TypeSSH:
|
||||||
|
outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions)
|
||||||
|
case C.TypeVLESS:
|
||||||
|
outbound.VLESSOptions.TLS = mergeTLSOutboundOptions(outbound.VLESSOptions.TLS)
|
||||||
|
case C.TypeTUIC:
|
||||||
|
outbound.TUICOptions.TLS = mergeTLSOutboundOptions(outbound.TUICOptions.TLS)
|
||||||
|
case C.TypeHysteria2:
|
||||||
|
outbound.Hysteria2Options.TLS = mergeTLSOutboundOptions(outbound.Hysteria2Options.TLS)
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
options.Outbounds[index] = outbound
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeTLSInboundOptions(options *option.InboundTLSOptions) *option.InboundTLSOptions {
|
||||||
|
if options == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if options.CertificatePath != "" {
|
||||||
|
if content, err := os.ReadFile(options.CertificatePath); err == nil {
|
||||||
|
options.Certificate = trimStringArray(strings.Split(string(content), "\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.KeyPath != "" {
|
||||||
|
if content, err := os.ReadFile(options.KeyPath); err == nil {
|
||||||
|
options.Key = trimStringArray(strings.Split(string(content), "\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.ECH != nil {
|
||||||
|
if options.ECH.KeyPath != "" {
|
||||||
|
if content, err := os.ReadFile(options.ECH.KeyPath); err == nil {
|
||||||
|
options.ECH.Key = trimStringArray(strings.Split(string(content), "\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.OutboundTLSOptions {
|
||||||
|
if options == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if options.CertificatePath != "" {
|
||||||
|
if content, err := os.ReadFile(options.CertificatePath); err == nil {
|
||||||
|
options.Certificate = trimStringArray(strings.Split(string(content), "\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.ECH != nil {
|
||||||
|
if options.ECH.ConfigPath != "" {
|
||||||
|
if content, err := os.ReadFile(options.ECH.ConfigPath); err == nil {
|
||||||
|
options.ECH.Config = trimStringArray(strings.Split(string(content), "\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeSSHOutboundOptions(options option.SSHOutboundOptions) option.SSHOutboundOptions {
|
||||||
|
if options.PrivateKeyPath != "" {
|
||||||
|
if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
|
||||||
|
options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimStringArray(array []string) []string {
|
||||||
|
return common.Filter(array, func(it string) bool {
|
||||||
|
return strings.TrimSpace(it) != ""
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -143,14 +143,16 @@ func create() (*box.Box, context.CancelFunc, error) {
|
|||||||
signal.Stop(osSignals)
|
signal.Stop(osSignals)
|
||||||
close(osSignals)
|
close(osSignals)
|
||||||
}()
|
}()
|
||||||
|
startCtx, finishStart := context.WithCancel(context.Background())
|
||||||
go func() {
|
go func() {
|
||||||
_, loaded := <-osSignals
|
_, loaded := <-osSignals
|
||||||
if loaded {
|
if loaded {
|
||||||
cancel()
|
cancel()
|
||||||
|
closeMonitor(startCtx)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
err = instance.Start()
|
err = instance.Start()
|
||||||
|
finishStart()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
return nil, nil, E.Cause(err, "start service")
|
return nil, nil, E.Cause(err, "start service")
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func NewConn(conn net.Conn) (net.Conn, error) {
|
|||||||
element := openConnection.PushBack(conn)
|
element := openConnection.PushBack(conn)
|
||||||
connAccess.Unlock()
|
connAccess.Unlock()
|
||||||
if KillerEnabled {
|
if KillerEnabled {
|
||||||
err := killerCheck()
|
err := KillerCheck()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
package conntrack
|
package conntrack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
|
||||||
runtimeDebug "runtime/debug"
|
runtimeDebug "runtime/debug"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/memory"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
KillerEnabled bool
|
KillerEnabled bool
|
||||||
MemoryLimit int64
|
MemoryLimit uint64
|
||||||
killerLastCheck time.Time
|
killerLastCheck time.Time
|
||||||
)
|
)
|
||||||
|
|
||||||
func killerCheck() error {
|
func KillerCheck() error {
|
||||||
if !KillerEnabled {
|
if !KillerEnabled {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -23,10 +23,7 @@ func killerCheck() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
killerLastCheck = nowTime
|
killerLastCheck = nowTime
|
||||||
var memStats runtime.MemStats
|
if memory.Total() > MemoryLimit {
|
||||||
runtime.ReadMemStats(&memStats)
|
|
||||||
inuseMemory := int64(memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased)
|
|
||||||
if inuseMemory > MemoryLimit {
|
|
||||||
Close()
|
Close()
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
@@ -18,7 +18,7 @@ func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
|
|||||||
element := openConnection.PushBack(conn)
|
element := openConnection.PushBack(conn)
|
||||||
connAccess.Unlock()
|
connAccess.Unlock()
|
||||||
if KillerEnabled {
|
if KillerEnabled {
|
||||||
err := killerCheck()
|
err := KillerCheck()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -6,7 +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/conntrack"
|
||||||
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"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
@@ -137,10 +137,12 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, 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 trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
|
||||||
} else {
|
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
||||||
|
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||||
|
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
|
||||||
|
} else {
|
||||||
|
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,12 @@ func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error)
|
|||||||
}
|
}
|
||||||
domainStrategy := dns.DomainStrategy(options.DomainStrategy)
|
domainStrategy := dns.DomainStrategy(options.DomainStrategy)
|
||||||
if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" {
|
if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" {
|
||||||
dialer = NewResolveDialer(router, dialer, domainStrategy, time.Duration(options.FallbackDelay))
|
dialer = NewResolveDialer(
|
||||||
|
router,
|
||||||
|
dialer,
|
||||||
|
options.Detour == "" && !options.TCPFastOpen,
|
||||||
|
domainStrategy,
|
||||||
|
time.Duration(options.FallbackDelay))
|
||||||
}
|
}
|
||||||
return dialer, nil
|
return dialer, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,14 +16,16 @@ import (
|
|||||||
|
|
||||||
type ResolveDialer struct {
|
type ResolveDialer struct {
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
|
parallel bool
|
||||||
router adapter.Router
|
router adapter.Router
|
||||||
strategy dns.DomainStrategy
|
strategy dns.DomainStrategy
|
||||||
fallbackDelay time.Duration
|
fallbackDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolveDialer(router adapter.Router, dialer N.Dialer, strategy dns.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer {
|
func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer {
|
||||||
return &ResolveDialer{
|
return &ResolveDialer{
|
||||||
dialer,
|
dialer,
|
||||||
|
parallel,
|
||||||
router,
|
router,
|
||||||
strategy,
|
strategy,
|
||||||
fallbackDelay,
|
fallbackDelay,
|
||||||
@@ -34,7 +36,7 @@ func (d *ResolveDialer) DialContext(ctx context.Context, network string, destina
|
|||||||
if !destination.IsFqdn() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.DialContext(ctx, network, destination)
|
return d.dialer.DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
ctx, metadata := adapter.AppendContext(ctx)
|
ctx, metadata := adapter.ExtendContext(ctx)
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
metadata.Destination = destination
|
metadata.Destination = destination
|
||||||
metadata.Domain = ""
|
metadata.Domain = ""
|
||||||
@@ -48,14 +50,18 @@ func (d *ResolveDialer) DialContext(ctx context.Context, network string, destina
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, d.fallbackDelay)
|
if d.parallel {
|
||||||
|
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, d.fallbackDelay)
|
||||||
|
} else {
|
||||||
|
return N.DialSerial(ctx, d.dialer, network, destination, addresses)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
if !destination.IsFqdn() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.ListenPacket(ctx, destination)
|
return d.dialer.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
ctx, metadata := adapter.AppendContext(ctx)
|
ctx, metadata := adapter.ExtendContext(ctx)
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
metadata.Destination = destination
|
metadata.Destination = destination
|
||||||
metadata.Domain = ""
|
metadata.Domain = ""
|
||||||
|
|||||||
158
common/humanize/bytes.go
Normal file
158
common/humanize/bytes.go
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IEC Sizes.
|
||||||
|
// kibis of bits
|
||||||
|
const (
|
||||||
|
Byte = 1 << (iota * 10)
|
||||||
|
KiByte
|
||||||
|
MiByte
|
||||||
|
GiByte
|
||||||
|
TiByte
|
||||||
|
PiByte
|
||||||
|
EiByte
|
||||||
|
)
|
||||||
|
|
||||||
|
// SI Sizes.
|
||||||
|
const (
|
||||||
|
IByte = 1
|
||||||
|
KByte = IByte * 1000
|
||||||
|
MByte = KByte * 1000
|
||||||
|
GByte = MByte * 1000
|
||||||
|
TByte = GByte * 1000
|
||||||
|
PByte = TByte * 1000
|
||||||
|
EByte = PByte * 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultSizeTable = map[string]uint64{
|
||||||
|
"b": Byte,
|
||||||
|
"kib": KiByte,
|
||||||
|
"kb": KByte,
|
||||||
|
"mib": MiByte,
|
||||||
|
"mb": MByte,
|
||||||
|
"gib": GiByte,
|
||||||
|
"gb": GByte,
|
||||||
|
"tib": TiByte,
|
||||||
|
"tb": TByte,
|
||||||
|
"pib": PiByte,
|
||||||
|
"pb": PByte,
|
||||||
|
"eib": EiByte,
|
||||||
|
"eb": EByte,
|
||||||
|
// Without suffix
|
||||||
|
"": Byte,
|
||||||
|
"ki": KiByte,
|
||||||
|
"k": KByte,
|
||||||
|
"mi": MiByte,
|
||||||
|
"m": MByte,
|
||||||
|
"gi": GiByte,
|
||||||
|
"g": GByte,
|
||||||
|
"ti": TiByte,
|
||||||
|
"t": TByte,
|
||||||
|
"pi": PiByte,
|
||||||
|
"p": PByte,
|
||||||
|
"ei": EiByte,
|
||||||
|
"e": EByte,
|
||||||
|
}
|
||||||
|
|
||||||
|
var memorysSizeTable = map[string]uint64{
|
||||||
|
"b": Byte,
|
||||||
|
"kb": KiByte,
|
||||||
|
"mb": MiByte,
|
||||||
|
"gb": GiByte,
|
||||||
|
"tb": TiByte,
|
||||||
|
"pb": PiByte,
|
||||||
|
"eb": EiByte,
|
||||||
|
"": Byte,
|
||||||
|
"k": KiByte,
|
||||||
|
"m": MiByte,
|
||||||
|
"g": GiByte,
|
||||||
|
"t": TiByte,
|
||||||
|
"p": PiByte,
|
||||||
|
"e": EiByte,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultSizes = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
|
||||||
|
iSizes = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
||||||
|
)
|
||||||
|
|
||||||
|
func Bytes(s uint64) string {
|
||||||
|
return humanateBytes(s, 1000, defaultSizes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MemoryBytes(s uint64) string {
|
||||||
|
return humanateBytes(s, 1024, defaultSizes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IBytes(s uint64) string {
|
||||||
|
return humanateBytes(s, 1024, iSizes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logn(n, b float64) float64 {
|
||||||
|
return math.Log(n) / math.Log(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func humanateBytes(s uint64, base float64, sizes []string) string {
|
||||||
|
if s < 10 {
|
||||||
|
return fmt.Sprintf("%d B", s)
|
||||||
|
}
|
||||||
|
e := math.Floor(logn(float64(s), base))
|
||||||
|
suffix := sizes[int(e)]
|
||||||
|
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
||||||
|
f := "%.0f %s"
|
||||||
|
if val < 10 {
|
||||||
|
f = "%.1f %s"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(f, val, suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseBytes(s string) (uint64, error) {
|
||||||
|
return parseBytes0(s, defaultSizeTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseMemoryBytes(s string) (uint64, error) {
|
||||||
|
return parseBytes0(s, memorysSizeTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseBytes0(s string, sizeTable map[string]uint64) (uint64, error) {
|
||||||
|
lastDigit := 0
|
||||||
|
hasComma := false
|
||||||
|
for _, r := range s {
|
||||||
|
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if r == ',' {
|
||||||
|
hasComma = true
|
||||||
|
}
|
||||||
|
lastDigit++
|
||||||
|
}
|
||||||
|
|
||||||
|
num := s[:lastDigit]
|
||||||
|
if hasComma {
|
||||||
|
num = strings.Replace(num, ",", "", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := strconv.ParseFloat(num, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||||
|
if m, ok := sizeTable[extra]; ok {
|
||||||
|
f *= float64(m)
|
||||||
|
if f >= math.MaxUint64 {
|
||||||
|
return 0, fmt.Errorf("too large: %v", s)
|
||||||
|
}
|
||||||
|
return uint64(f), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, fmt.Errorf("unhandled size name: %v", extra)
|
||||||
|
}
|
||||||
75
common/interrupt/conn.go
Normal file
75
common/interrupt/conn.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package interrupt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*type GroupedConn interface {
|
||||||
|
MarkAsInternal()
|
||||||
|
}
|
||||||
|
|
||||||
|
func MarkAsInternal(conn any) {
|
||||||
|
if groupedConn, isGroupConn := common.Cast[GroupedConn](conn); isGroupConn {
|
||||||
|
groupedConn.MarkAsInternal()
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
type Conn struct {
|
||||||
|
net.Conn
|
||||||
|
group *Group
|
||||||
|
element *list.Element[*groupConnItem]
|
||||||
|
}
|
||||||
|
|
||||||
|
/*func (c *Conn) MarkAsInternal() {
|
||||||
|
c.element.Value.internal = true
|
||||||
|
}*/
|
||||||
|
|
||||||
|
func (c *Conn) Close() error {
|
||||||
|
c.group.access.Lock()
|
||||||
|
defer c.group.access.Unlock()
|
||||||
|
c.group.connections.Remove(c.element)
|
||||||
|
return c.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) ReaderReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) WriterReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Upstream() any {
|
||||||
|
return c.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type PacketConn struct {
|
||||||
|
net.PacketConn
|
||||||
|
group *Group
|
||||||
|
element *list.Element[*groupConnItem]
|
||||||
|
}
|
||||||
|
|
||||||
|
/*func (c *PacketConn) MarkAsInternal() {
|
||||||
|
c.element.Value.internal = true
|
||||||
|
}*/
|
||||||
|
|
||||||
|
func (c *PacketConn) Close() error {
|
||||||
|
c.group.access.Lock()
|
||||||
|
defer c.group.access.Unlock()
|
||||||
|
c.group.connections.Remove(c.element)
|
||||||
|
return c.PacketConn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) ReaderReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) WriterReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) Upstream() any {
|
||||||
|
return c.PacketConn
|
||||||
|
}
|
||||||
13
common/interrupt/context.go
Normal file
13
common/interrupt/context.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package interrupt
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
type contextKeyIsExternalConnection struct{}
|
||||||
|
|
||||||
|
func ContextWithIsExternalConnection(ctx context.Context) context.Context {
|
||||||
|
return context.WithValue(ctx, contextKeyIsExternalConnection{}, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsExternalConnectionFromContext(ctx context.Context) bool {
|
||||||
|
return ctx.Value(contextKeyIsExternalConnection{}) != nil
|
||||||
|
}
|
||||||
52
common/interrupt/group.go
Normal file
52
common/interrupt/group.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package interrupt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Group struct {
|
||||||
|
access sync.Mutex
|
||||||
|
connections list.List[*groupConnItem]
|
||||||
|
}
|
||||||
|
|
||||||
|
type groupConnItem struct {
|
||||||
|
conn io.Closer
|
||||||
|
isExternal bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGroup() *Group {
|
||||||
|
return &Group{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) NewConn(conn net.Conn, isExternal bool) net.Conn {
|
||||||
|
g.access.Lock()
|
||||||
|
defer g.access.Unlock()
|
||||||
|
item := g.connections.PushBack(&groupConnItem{conn, isExternal})
|
||||||
|
return &Conn{Conn: conn, group: g, element: item}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) NewPacketConn(conn net.PacketConn, isExternal bool) net.PacketConn {
|
||||||
|
g.access.Lock()
|
||||||
|
defer g.access.Unlock()
|
||||||
|
item := g.connections.PushBack(&groupConnItem{conn, isExternal})
|
||||||
|
return &PacketConn{PacketConn: conn, group: g, element: item}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) Interrupt(interruptExternalConnections bool) {
|
||||||
|
g.access.Lock()
|
||||||
|
defer g.access.Unlock()
|
||||||
|
var toDelete []*list.Element[*groupConnItem]
|
||||||
|
for element := g.connections.Front(); element != nil; element = element.Next() {
|
||||||
|
if !element.Value.isExternal || interruptExternalConnections {
|
||||||
|
element.Value.conn.Close()
|
||||||
|
toDelete = append(toDelete, element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, element := range toDelete {
|
||||||
|
g.connections.Remove(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,42 @@
|
|||||||
package mux
|
package mux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-mux"
|
"github.com/sagernet/sing-mux"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewClientWithOptions(dialer N.Dialer, options option.MultiplexOptions) (*Client, error) {
|
type Client = mux.Client
|
||||||
|
|
||||||
|
func NewClientWithOptions(dialer N.Dialer, logger logger.Logger, options option.OutboundMultiplexOptions) (*Client, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
var brutalOptions mux.BrutalOptions
|
||||||
|
if options.Brutal != nil && options.Brutal.Enabled {
|
||||||
|
brutalOptions = mux.BrutalOptions{
|
||||||
|
Enabled: true,
|
||||||
|
SendBPS: uint64(options.Brutal.UpMbps * C.MbpsToBps),
|
||||||
|
ReceiveBPS: uint64(options.Brutal.DownMbps * C.MbpsToBps),
|
||||||
|
}
|
||||||
|
if brutalOptions.SendBPS < mux.BrutalMinSpeedBPS {
|
||||||
|
return nil, E.New("brutal: invalid upload speed")
|
||||||
|
}
|
||||||
|
if brutalOptions.ReceiveBPS < mux.BrutalMinSpeedBPS {
|
||||||
|
return nil, E.New("brutal: invalid download speed")
|
||||||
|
}
|
||||||
|
}
|
||||||
return mux.NewClient(mux.Options{
|
return mux.NewClient(mux.Options{
|
||||||
Dialer: dialer,
|
Dialer: dialer,
|
||||||
|
Logger: logger,
|
||||||
Protocol: options.Protocol,
|
Protocol: options.Protocol,
|
||||||
MaxConnections: options.MaxConnections,
|
MaxConnections: options.MaxConnections,
|
||||||
MinStreams: options.MinStreams,
|
MinStreams: options.MinStreams,
|
||||||
MaxStreams: options.MaxStreams,
|
MaxStreams: options.MaxStreams,
|
||||||
Padding: options.Padding,
|
Padding: options.Padding,
|
||||||
|
Brutal: brutalOptions,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
Client = mux.Client
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
Destination = mux.Destination
|
|
||||||
HandleConnection = mux.HandleConnection
|
|
||||||
)
|
|
||||||
65
common/mux/router.go
Normal file
65
common/mux/router.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-mux"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Router struct {
|
||||||
|
router adapter.ConnectionRouter
|
||||||
|
service *mux.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouter, error) {
|
||||||
|
if !options.Enabled {
|
||||||
|
return router, nil
|
||||||
|
}
|
||||||
|
var brutalOptions mux.BrutalOptions
|
||||||
|
if options.Brutal != nil && options.Brutal.Enabled {
|
||||||
|
brutalOptions = mux.BrutalOptions{
|
||||||
|
Enabled: true,
|
||||||
|
SendBPS: uint64(options.Brutal.UpMbps * C.MbpsToBps),
|
||||||
|
ReceiveBPS: uint64(options.Brutal.DownMbps * C.MbpsToBps),
|
||||||
|
}
|
||||||
|
if brutalOptions.SendBPS < mux.BrutalMinSpeedBPS {
|
||||||
|
return nil, E.New("brutal: invalid upload speed")
|
||||||
|
}
|
||||||
|
if brutalOptions.ReceiveBPS < mux.BrutalMinSpeedBPS {
|
||||||
|
return nil, E.New("brutal: invalid download speed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
service, err := mux.NewService(mux.ServiceOptions{
|
||||||
|
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
|
||||||
|
return log.ContextWithNewID(ctx)
|
||||||
|
},
|
||||||
|
Logger: logger,
|
||||||
|
Handler: adapter.NewRouteContextHandler(router, logger),
|
||||||
|
Padding: options.Padding,
|
||||||
|
Brutal: brutalOptions,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Router{router, service}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
|
if metadata.Destination == mux.Destination {
|
||||||
|
return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata))
|
||||||
|
} else {
|
||||||
|
return r.router.RouteConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
|
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
32
common/mux/v2ray_legacy.go
Normal file
32
common/mux/v2ray_legacy.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
vmess "github.com/sagernet/sing-vmess"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type V2RayLegacyRouter struct {
|
||||||
|
router adapter.ConnectionRouter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewV2RayLegacyRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) adapter.ConnectionRouter {
|
||||||
|
return &V2RayLegacyRouter{router, logger}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *V2RayLegacyRouter) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
|
if metadata.Destination.Fqdn == vmess.MuxDestination.Fqdn {
|
||||||
|
r.logger.InfoContext(ctx, "inbound legacy multiplex connection")
|
||||||
|
return vmess.HandleMuxConnection(ctx, conn, adapter.NewRouteHandler(metadata, r.router, r.logger))
|
||||||
|
}
|
||||||
|
return r.router.RouteConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *V2RayLegacyRouter) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
|
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package proxyproto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
|
|
||||||
"github.com/pires/go-proxyproto"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ N.Dialer = (*Dialer)(nil)
|
|
||||||
|
|
||||||
type Dialer struct {
|
|
||||||
N.Dialer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
|
||||||
switch N.NetworkName(network) {
|
|
||||||
case N.NetworkTCP:
|
|
||||||
conn, err := d.Dialer.DialContext(ctx, network, destination)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var source M.Socksaddr
|
|
||||||
metadata := adapter.ContextFrom(ctx)
|
|
||||||
if metadata != nil {
|
|
||||||
source = metadata.Source
|
|
||||||
}
|
|
||||||
if !source.IsValid() {
|
|
||||||
source = M.SocksaddrFromNet(conn.LocalAddr())
|
|
||||||
}
|
|
||||||
if destination.Addr.Is6() {
|
|
||||||
source = M.SocksaddrFrom(netip.AddrFrom16(source.Addr.As16()), source.Port)
|
|
||||||
}
|
|
||||||
h := proxyproto.HeaderProxyFromAddrs(1, source.TCPAddr(), destination.TCPAddr())
|
|
||||||
_, err = h.WriteTo(conn)
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, E.Cause(err, "write proxy protocol header")
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
default:
|
|
||||||
return d.Dialer.DialContext(ctx, network, destination)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package proxyproto
|
|
||||||
|
|
||||||
import (
|
|
||||||
std_bufio "bufio"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
|
|
||||||
"github.com/pires/go-proxyproto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Listener struct {
|
|
||||||
net.Listener
|
|
||||||
AcceptNoHeader bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) Accept() (net.Conn, error) {
|
|
||||||
conn, err := l.Listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
bufReader := std_bufio.NewReader(conn)
|
|
||||||
header, err := proxyproto.Read(bufReader)
|
|
||||||
if err != nil && !(l.AcceptNoHeader && err == proxyproto.ErrNoProxyProtocol) {
|
|
||||||
return nil, &Error{err}
|
|
||||||
}
|
|
||||||
if bufReader.Buffered() > 0 {
|
|
||||||
cache := buf.NewSize(bufReader.Buffered())
|
|
||||||
_, err = cache.ReadFullFrom(bufReader, cache.FreeLen())
|
|
||||||
if err != nil {
|
|
||||||
return nil, &Error{err}
|
|
||||||
}
|
|
||||||
conn = bufio.NewCachedConn(conn, cache)
|
|
||||||
}
|
|
||||||
if header != nil {
|
|
||||||
return &bufio.AddrConn{Conn: conn, Metadata: M.Metadata{
|
|
||||||
Source: M.SocksaddrFromNet(header.SourceAddr).Unwrap(),
|
|
||||||
Destination: M.SocksaddrFromNet(header.DestinationAddr).Unwrap(),
|
|
||||||
}}, 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,43 +1,73 @@
|
|||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/shell"
|
"github.com/sagernet/sing/common/shell"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type AndroidSystemProxy struct {
|
||||||
useRish bool
|
useRish bool
|
||||||
rishPath string
|
rishPath string
|
||||||
)
|
serverAddr M.Socksaddr
|
||||||
|
supportSOCKS bool
|
||||||
|
isEnabled bool
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*AndroidSystemProxy, error) {
|
||||||
userId := os.Getuid()
|
userId := os.Getuid()
|
||||||
|
var (
|
||||||
|
useRish bool
|
||||||
|
rishPath string
|
||||||
|
)
|
||||||
if userId == 0 || userId == 1000 || userId == 2000 {
|
if userId == 0 || userId == 1000 || userId == 2000 {
|
||||||
useRish = false
|
useRish = false
|
||||||
} else {
|
} else {
|
||||||
rishPath, useRish = C.FindPath("rish")
|
rishPath, useRish = C.FindPath("rish")
|
||||||
|
if !useRish {
|
||||||
|
return nil, E.Cause(os.ErrPermission, "root or system (adb) permission is required for set system proxy")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return &AndroidSystemProxy{
|
||||||
|
useRish: useRish,
|
||||||
func runAndroidShell(name string, args ...string) error {
|
rishPath: rishPath,
|
||||||
if !useRish {
|
serverAddr: serverAddr,
|
||||||
return shell.Exec(name, args...).Attach().Run()
|
supportSOCKS: supportSOCKS,
|
||||||
} else {
|
|
||||||
return shell.Exec("sh", rishPath, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
|
|
||||||
err := runAndroidShell("settings", "put", "global", "http_proxy", F.ToString("127.0.0.1:", port))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return func() error {
|
|
||||||
return runAndroidShell("settings", "put", "global", "http_proxy", ":0")
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *AndroidSystemProxy) IsEnabled() bool {
|
||||||
|
return p.isEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AndroidSystemProxy) Enable() error {
|
||||||
|
err := p.runAndroidShell("settings", "put", "global", "http_proxy", p.serverAddr.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.isEnabled = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AndroidSystemProxy) Disable() error {
|
||||||
|
err := p.runAndroidShell("settings", "put", "global", "http_proxy", ":0")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.isEnabled = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AndroidSystemProxy) runAndroidShell(name string, args ...string) error {
|
||||||
|
if !p.useRish {
|
||||||
|
return shell.Exec(name, args...).Attach().Run()
|
||||||
|
} else {
|
||||||
|
return shell.Exec("sh", p.rishPath, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,56 +1,56 @@
|
|||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/shell"
|
"github.com/sagernet/sing/common/shell"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
type systemProxy struct {
|
type DarwinSystemProxy struct {
|
||||||
monitor tun.DefaultInterfaceMonitor
|
monitor tun.DefaultInterfaceMonitor
|
||||||
interfaceName string
|
interfaceName string
|
||||||
element *list.Element[tun.DefaultInterfaceUpdateCallback]
|
element *list.Element[tun.DefaultInterfaceUpdateCallback]
|
||||||
port uint16
|
serverAddr M.Socksaddr
|
||||||
isMixed bool
|
supportSOCKS bool
|
||||||
|
isEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *systemProxy) update(event int) {
|
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) {
|
||||||
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
|
interfaceMonitor := adapter.RouterFromContext(ctx).InterfaceMonitor()
|
||||||
if p.interfaceName == newInterfaceName {
|
if interfaceMonitor == nil {
|
||||||
return
|
return nil, E.New("missing interface monitor")
|
||||||
}
|
}
|
||||||
if p.interfaceName != "" {
|
proxy := &DarwinSystemProxy{
|
||||||
_ = p.unset()
|
monitor: interfaceMonitor,
|
||||||
|
serverAddr: serverAddr,
|
||||||
|
supportSOCKS: supportSOCKS,
|
||||||
}
|
}
|
||||||
p.interfaceName = newInterfaceName
|
proxy.element = interfaceMonitor.RegisterCallback(proxy.update)
|
||||||
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
|
return proxy, nil
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if p.isMixed {
|
|
||||||
err = shell.Exec("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
err = shell.Exec("networksetup", "-setwebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
_ = shell.Exec("networksetup", "-setsecurewebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *systemProxy) unset() error {
|
func (p *DarwinSystemProxy) IsEnabled() bool {
|
||||||
|
return p.isEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DarwinSystemProxy) Enable() error {
|
||||||
|
return p.update0()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DarwinSystemProxy) Disable() error {
|
||||||
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
|
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if p.isMixed {
|
if p.supportSOCKS {
|
||||||
err = shell.Exec("networksetup", "-setsocksfirewallproxystate", interfaceDisplayName, "off").Attach().Run()
|
err = shell.Exec("networksetup", "-setsocksfirewallproxystate", interfaceDisplayName, "off").Attach().Run()
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -59,9 +59,53 @@ func (p *systemProxy) unset() error {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
err = shell.Exec("networksetup", "-setsecurewebproxystate", interfaceDisplayName, "off").Attach().Run()
|
err = shell.Exec("networksetup", "-setsecurewebproxystate", interfaceDisplayName, "off").Attach().Run()
|
||||||
}
|
}
|
||||||
|
if err == nil {
|
||||||
|
p.isEnabled = false
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *DarwinSystemProxy) update(event int) {
|
||||||
|
if event&tun.EventInterfaceUpdate == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !p.isEnabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = p.update0()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DarwinSystemProxy) update0() error {
|
||||||
|
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
|
||||||
|
if p.interfaceName == newInterfaceName {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if p.interfaceName != "" {
|
||||||
|
_ = p.Disable()
|
||||||
|
}
|
||||||
|
p.interfaceName = newInterfaceName
|
||||||
|
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if p.supportSOCKS {
|
||||||
|
err = shell.Exec("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, p.serverAddr.AddrString(), strconv.Itoa(int(p.serverAddr.Port))).Attach().Run()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = shell.Exec("networksetup", "-setwebproxy", interfaceDisplayName, p.serverAddr.AddrString(), strconv.Itoa(int(p.serverAddr.Port))).Attach().Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = shell.Exec("networksetup", "-setsecurewebproxy", interfaceDisplayName, p.serverAddr.AddrString(), strconv.Itoa(int(p.serverAddr.Port))).Attach().Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.isEnabled = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func getInterfaceDisplayName(name string) (string, error) {
|
func getInterfaceDisplayName(name string) (string, error) {
|
||||||
content, err := shell.Exec("networksetup", "-listallhardwareports").ReadOutput()
|
content, err := shell.Exec("networksetup", "-listallhardwareports").ReadOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -77,21 +121,3 @@ func getInterfaceDisplayName(name string) (string, error) {
|
|||||||
}
|
}
|
||||||
return "", E.New(name, " not found in networksetup -listallhardwareports")
|
return "", E.New(name, " not found in networksetup -listallhardwareports")
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
|
|
||||||
interfaceMonitor := router.InterfaceMonitor()
|
|
||||||
if interfaceMonitor == nil {
|
|
||||||
return nil, E.New("missing interface monitor")
|
|
||||||
}
|
|
||||||
proxy := &systemProxy{
|
|
||||||
monitor: interfaceMonitor,
|
|
||||||
port: port,
|
|
||||||
isMixed: isMixed,
|
|
||||||
}
|
|
||||||
proxy.update(tun.EventInterfaceUpdate)
|
|
||||||
proxy.element = interfaceMonitor.RegisterCallback(proxy.update)
|
|
||||||
return func() error {
|
|
||||||
interfaceMonitor.UnregisterCallback(proxy.element)
|
|
||||||
return proxy.unset()
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,75 +3,161 @@
|
|||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/shell"
|
"github.com/sagernet/sing/common/shell"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type LinuxSystemProxy struct {
|
||||||
hasGSettings bool
|
hasGSettings bool
|
||||||
sudoUser string
|
hasKWriteConfig5 bool
|
||||||
)
|
sudoUser string
|
||||||
|
serverAddr M.Socksaddr
|
||||||
|
supportSOCKS bool
|
||||||
|
isEnabled bool
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*LinuxSystemProxy, error) {
|
||||||
hasGSettings = common.Error(exec.LookPath("gsettings")) == nil
|
hasGSettings := common.Error(exec.LookPath("gsettings")) == nil
|
||||||
|
hasKWriteConfig5 := common.Error(exec.LookPath("kwriteconfig5")) == nil
|
||||||
|
var sudoUser string
|
||||||
if os.Getuid() == 0 {
|
if os.Getuid() == 0 {
|
||||||
sudoUser = os.Getenv("SUDO_USER")
|
sudoUser = os.Getenv("SUDO_USER")
|
||||||
}
|
}
|
||||||
|
if !hasGSettings && !hasKWriteConfig5 {
|
||||||
|
return nil, E.New("unsupported desktop environment")
|
||||||
|
}
|
||||||
|
return &LinuxSystemProxy{
|
||||||
|
hasGSettings: hasGSettings,
|
||||||
|
hasKWriteConfig5: hasKWriteConfig5,
|
||||||
|
sudoUser: sudoUser,
|
||||||
|
serverAddr: serverAddr,
|
||||||
|
supportSOCKS: supportSOCKS,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runAsUser(name string, args ...string) error {
|
func (p *LinuxSystemProxy) IsEnabled() bool {
|
||||||
|
return p.isEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LinuxSystemProxy) Enable() error {
|
||||||
|
if p.hasGSettings {
|
||||||
|
err := p.runAsUser("gsettings", "set", "org.gnome.system.proxy.http", "enabled", "true")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if p.supportSOCKS {
|
||||||
|
err = p.setGnomeProxy("ftp", "http", "https", "socks")
|
||||||
|
} else {
|
||||||
|
err = p.setGnomeProxy("http", "https")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = p.runAsUser("gsettings", "set", "org.gnome.system.proxy", "use-same-proxy", F.ToString(p.supportSOCKS))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = p.runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "manual")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.hasKWriteConfig5 {
|
||||||
|
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "1")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if p.supportSOCKS {
|
||||||
|
err = p.setKDEProxy("ftp", "http", "https", "socks")
|
||||||
|
} else {
|
||||||
|
err = p.setKDEProxy("http", "https")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "Authmode", "0")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = p.runAsUser("dbus-send", "--type=signal", "/KIO/Scheduler", "org.kde.KIO.Scheduler.reparseSlaveConfiguration", "string:''")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.isEnabled = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LinuxSystemProxy) Disable() error {
|
||||||
|
if p.hasGSettings {
|
||||||
|
err := p.runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "none")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.hasKWriteConfig5 {
|
||||||
|
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "0")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = p.runAsUser("dbus-send", "--type=signal", "/KIO/Scheduler", "org.kde.KIO.Scheduler.reparseSlaveConfiguration", "string:''")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.isEnabled = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LinuxSystemProxy) runAsUser(name string, args ...string) error {
|
||||||
if os.Getuid() != 0 {
|
if os.Getuid() != 0 {
|
||||||
return shell.Exec(name, args...).Attach().Run()
|
return shell.Exec(name, args...).Attach().Run()
|
||||||
} else if sudoUser != "" {
|
} else if p.sudoUser != "" {
|
||||||
return shell.Exec("su", "-", sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
|
return shell.Exec("su", "-", p.sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
|
||||||
} else {
|
} else {
|
||||||
return E.New("set system proxy: unable to set as root")
|
return E.New("set system proxy: unable to set as root")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
|
func (p *LinuxSystemProxy) setGnomeProxy(proxyTypes ...string) error {
|
||||||
if !hasGSettings {
|
|
||||||
return nil, E.New("unsupported desktop environment")
|
|
||||||
}
|
|
||||||
err := runAsUser("gsettings", "set", "org.gnome.system.proxy.http", "enabled", "true")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if isMixed {
|
|
||||||
err = setGnomeProxy(port, "ftp", "http", "https", "socks")
|
|
||||||
} else {
|
|
||||||
err = setGnomeProxy(port, "http", "https")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = runAsUser("gsettings", "set", "org.gnome.system.proxy", "use-same-proxy", F.ToString(isMixed))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "manual")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return func() error {
|
|
||||||
return runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "none")
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setGnomeProxy(port uint16, proxyTypes ...string) error {
|
|
||||||
for _, proxyType := range proxyTypes {
|
for _, proxyType := range proxyTypes {
|
||||||
err := runAsUser("gsettings", "set", "org.gnome.system.proxy."+proxyType, "host", "127.0.0.1")
|
err := p.runAsUser("gsettings", "set", "org.gnome.system.proxy."+proxyType, "host", p.serverAddr.AddrString())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = runAsUser("gsettings", "set", "org.gnome.system.proxy."+proxyType, "port", F.ToString(port))
|
err = p.runAsUser("gsettings", "set", "org.gnome.system.proxy."+proxyType, "port", F.ToString(p.serverAddr.Port))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LinuxSystemProxy) setKDEProxy(proxyTypes ...string) error {
|
||||||
|
for _, proxyType := range proxyTypes {
|
||||||
|
var proxyUrl string
|
||||||
|
if proxyType == "socks" {
|
||||||
|
proxyUrl = "socks://" + p.serverAddr.String()
|
||||||
|
} else {
|
||||||
|
proxyUrl = "http://" + p.serverAddr.String()
|
||||||
|
}
|
||||||
|
err := p.runAsUser(
|
||||||
|
"kwriteconfig5",
|
||||||
|
"--file",
|
||||||
|
"kioslaverc",
|
||||||
|
"--group",
|
||||||
|
"Proxy Settings",
|
||||||
|
"--key", proxyType+"Proxy",
|
||||||
|
proxyUrl,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,12 @@
|
|||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
|
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (SystemProxy, error) {
|
||||||
return nil, os.ErrInvalid
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,43 @@
|
|||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"context"
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/wininet"
|
"github.com/sagernet/sing/common/wininet"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
|
type WindowsSystemProxy struct {
|
||||||
err := wininet.SetSystemProxy(F.ToString("http://127.0.0.1:", port), "")
|
serverAddr M.Socksaddr
|
||||||
if err != nil {
|
supportSOCKS bool
|
||||||
return nil, err
|
isEnabled bool
|
||||||
}
|
}
|
||||||
return func() error {
|
|
||||||
return wininet.ClearSystemProxy()
|
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*WindowsSystemProxy, error) {
|
||||||
|
return &WindowsSystemProxy{
|
||||||
|
serverAddr: serverAddr,
|
||||||
|
supportSOCKS: supportSOCKS,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *WindowsSystemProxy) IsEnabled() bool {
|
||||||
|
return p.isEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *WindowsSystemProxy) Enable() error {
|
||||||
|
err := wininet.SetSystemProxy("http://"+p.serverAddr.String(), "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.isEnabled = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *WindowsSystemProxy) Disable() error {
|
||||||
|
err := wininet.ClearSystemProxy()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.isEnabled = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
7
common/settings/system_proxy.go
Normal file
7
common/settings/system_proxy.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
type SystemProxy interface {
|
||||||
|
IsEnabled() bool
|
||||||
|
Enable() error
|
||||||
|
Disable() error
|
||||||
|
}
|
||||||
@@ -182,11 +182,52 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
switch frameType {
|
switch frameType {
|
||||||
case 0x0:
|
case 0x00: // PADDING
|
||||||
continue
|
continue
|
||||||
case 0x1:
|
case 0x01: // PING
|
||||||
continue
|
continue
|
||||||
case 0x6:
|
case 0x02, 0x03: // ACK
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // Largest Acknowledged
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // ACK Delay
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ackRangeCount, err := qtls.ReadUvarint(decryptedReader) // ACK Range Count
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // First ACK Range
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := 0; i < int(ackRangeCount); i++ {
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // Gap
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // ACK Range Length
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if frameType == 0x03 {
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // ECT0 Count
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // ECT1 Count
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // ECN-CE Count
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 0x06: // CRYPTO
|
||||||
var offset uint64
|
var offset uint64
|
||||||
offset, err = qtls.ReadUvarint(decryptedReader)
|
offset, err = qtls.ReadUvarint(decryptedReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -208,8 +249,26 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
case 0x1c: // CONNECTION_CLOSE
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // Error Code
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // Frame Type
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var length uint64
|
||||||
|
length, err = qtls.ReadUvarint(decryptedReader) // Reason Phrase Length
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = decryptedReader.Seek(int64(length), io.SeekCurrent) // Reason Phrase
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// ignore unknown frame type
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tlsHdr := make([]byte, 5)
|
tlsHdr := make([]byte, 5)
|
||||||
|
|||||||
@@ -9,10 +9,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"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"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
|
"github.com/libdns/alidns"
|
||||||
|
"github.com/libdns/cloudflare"
|
||||||
"github.com/mholt/acmez/acme"
|
"github.com/mholt/acmez/acme"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
@@ -74,6 +77,24 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
|
|||||||
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
||||||
Logger: config.Logger,
|
Logger: config.Logger,
|
||||||
}
|
}
|
||||||
|
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
|
||||||
|
var solver certmagic.DNS01Solver
|
||||||
|
switch dnsOptions.Provider {
|
||||||
|
case C.DNSProviderAliDNS:
|
||||||
|
solver.DNSProvider = &alidns.Provider{
|
||||||
|
AccKeyID: dnsOptions.AliDNSOptions.AccessKeyID,
|
||||||
|
AccKeySecret: dnsOptions.AliDNSOptions.AccessKeySecret,
|
||||||
|
RegionID: dnsOptions.AliDNSOptions.RegionID,
|
||||||
|
}
|
||||||
|
case C.DNSProviderCloudflare:
|
||||||
|
solver.DNSProvider = &cloudflare.Provider{
|
||||||
|
APIToken: dnsOptions.CloudflareOptions.APIToken,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, nil, E.New("unsupported ACME DNS01 provider type: " + dnsOptions.Provider)
|
||||||
|
}
|
||||||
|
acmeConfig.DNS01Solver = &solver
|
||||||
|
}
|
||||||
if options.ExternalAccount != nil && options.ExternalAccount.KeyID != "" {
|
if options.ExternalAccount != nil && options.ExternalAccount.KeyID != "" {
|
||||||
acmeConfig.ExternalAccount = (*acme.EAB)(options.ExternalAccount)
|
acmeConfig.ExternalAccount = (*acme.EAB)(options.ExternalAccount)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,29 +13,29 @@ import (
|
|||||||
aTLS "github.com/sagernet/sing/common/tls"
|
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(ctx context.Context, router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return dialer, nil
|
return dialer, nil
|
||||||
}
|
}
|
||||||
config, err := NewClient(router, serverAddress, options)
|
config, err := NewClient(ctx, serverAddress, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return NewDialer(dialer, config), nil
|
return NewDialer(dialer, config), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
return NewECHClient(router, serverAddress, options)
|
return NewECHClient(ctx, serverAddress, options)
|
||||||
} else if options.Reality != nil && options.Reality.Enabled {
|
} else if options.Reality != nil && options.Reality.Enabled {
|
||||||
return NewRealityClient(router, serverAddress, options)
|
return NewRealityClient(ctx, 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(ctx, serverAddress, options)
|
||||||
} else {
|
} else {
|
||||||
return NewSTDClient(router, serverAddress, options)
|
return NewSTDClient(ctx, serverAddress, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,50 +7,53 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
cftls "github.com/sagernet/cloudflare-tls"
|
cftls "github.com/sagernet/cloudflare-tls"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ECHClientConfig struct {
|
type echClientConfig struct {
|
||||||
config *cftls.Config
|
config *cftls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) ServerName() string {
|
func (c *echClientConfig) ServerName() string {
|
||||||
return e.config.ServerName
|
return c.config.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) SetServerName(serverName string) {
|
func (c *echClientConfig) SetServerName(serverName string) {
|
||||||
e.config.ServerName = serverName
|
c.config.ServerName = serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) NextProtos() []string {
|
func (c *echClientConfig) NextProtos() []string {
|
||||||
return e.config.NextProtos
|
return c.config.NextProtos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) SetNextProtos(nextProto []string) {
|
func (c *echClientConfig) SetNextProtos(nextProto []string) {
|
||||||
e.config.NextProtos = nextProto
|
c.config.NextProtos = nextProto
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) Config() (*STDConfig, error) {
|
func (c *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, error) {
|
func (c *echClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
return &echConnWrapper{cftls.Client(conn, e.config)}, nil
|
return &echConnWrapper{cftls.Client(conn, c.config)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) Clone() Config {
|
func (c *echClientConfig) Clone() Config {
|
||||||
return &ECHClientConfig{
|
return &echClientConfig{
|
||||||
config: e.config.Clone(),
|
config: c.config.Clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +83,7 @@ func (c *echConnWrapper) Upstream() any {
|
|||||||
return c.Conn
|
return c.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewECHClient(ctx context.Context, 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
|
||||||
@@ -94,7 +97,7 @@ func NewECHClient(router adapter.Router, serverAddress string, options option.Ou
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tlsConfig cftls.Config
|
var tlsConfig cftls.Config
|
||||||
tlsConfig.Time = router.TimeFunc()
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
@@ -146,8 +149,8 @@ func NewECHClient(router adapter.Router, serverAddress string, options option.Ou
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var certificate []byte
|
var certificate []byte
|
||||||
if options.Certificate != "" {
|
if len(options.Certificate) > 0 {
|
||||||
certificate = []byte(options.Certificate)
|
certificate = []byte(strings.Join(options.Certificate, "\n"))
|
||||||
} else if options.CertificatePath != "" {
|
} else if options.CertificatePath != "" {
|
||||||
content, err := os.ReadFile(options.CertificatePath)
|
content, err := os.ReadFile(options.CertificatePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -168,24 +171,36 @@ func NewECHClient(router adapter.Router, serverAddress string, options option.Ou
|
|||||||
tlsConfig.ECHEnabled = true
|
tlsConfig.ECHEnabled = true
|
||||||
tlsConfig.PQSignatureSchemesEnabled = options.ECH.PQSignatureSchemesEnabled
|
tlsConfig.PQSignatureSchemesEnabled = options.ECH.PQSignatureSchemesEnabled
|
||||||
tlsConfig.DynamicRecordSizingDisabled = options.ECH.DynamicRecordSizingDisabled
|
tlsConfig.DynamicRecordSizingDisabled = options.ECH.DynamicRecordSizingDisabled
|
||||||
if options.ECH.Config != "" {
|
|
||||||
clientConfigContent, err := base64.StdEncoding.DecodeString(options.ECH.Config)
|
var echConfig []byte
|
||||||
|
if len(options.ECH.Config) > 0 {
|
||||||
|
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
|
||||||
|
} else if options.ECH.ConfigPath != "" {
|
||||||
|
content, err := os.ReadFile(options.ECH.ConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, E.Cause(err, "read ECH config")
|
||||||
}
|
}
|
||||||
clientConfig, err := cftls.UnmarshalECHConfigs(clientConfigContent)
|
echConfig = content
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsConfig.ClientECHConfigs = clientConfig
|
|
||||||
} else {
|
|
||||||
tlsConfig.GetClientECHConfigs = fetchECHClientConfig(router)
|
|
||||||
}
|
}
|
||||||
return &ECHClientConfig{&tlsConfig}, nil
|
|
||||||
|
if len(echConfig) > 0 {
|
||||||
|
block, rest := pem.Decode(echConfig)
|
||||||
|
if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 {
|
||||||
|
return nil, E.New("invalid ECH configs pem")
|
||||||
|
}
|
||||||
|
echConfigs, err := cftls.UnmarshalECHConfigs(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse ECH configs")
|
||||||
|
}
|
||||||
|
tlsConfig.ClientECHConfigs = echConfigs
|
||||||
|
} else {
|
||||||
|
tlsConfig.GetClientECHConfigs = fetchECHClientConfig(ctx)
|
||||||
|
}
|
||||||
|
return &echClientConfig{&tlsConfig}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchECHClientConfig(router adapter.Router) func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
func fetchECHClientConfig(ctx context.Context) func(_ context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
||||||
return func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
return func(_ context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
||||||
message := &mDNS.Msg{
|
message := &mDNS.Msg{
|
||||||
MsgHdr: mDNS.MsgHdr{
|
MsgHdr: mDNS.MsgHdr{
|
||||||
RecursionDesired: true,
|
RecursionDesired: true,
|
||||||
@@ -198,7 +213,7 @@ func fetchECHClientConfig(router adapter.Router) func(ctx context.Context, serve
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
response, err := router.Exchange(ctx, message)
|
response, err := adapter.RouterFromContext(ctx).Exchange(ctx, message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
169
common/tls/ech_keygen.go
Normal file
169
common/tls/ech_keygen.go
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
//go:build with_ech
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/pem"
|
||||||
|
|
||||||
|
cftls "github.com/sagernet/cloudflare-tls"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
"github.com/cloudflare/circl/hpke"
|
||||||
|
"github.com/cloudflare/circl/kem"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ECHKeygenDefault(serverName string, pqSignatureSchemesEnabled bool) (configPem string, keyPem string, err error) {
|
||||||
|
cipherSuites := []echCipherSuite{
|
||||||
|
{
|
||||||
|
kdf: hpke.KDF_HKDF_SHA256,
|
||||||
|
aead: hpke.AEAD_AES128GCM,
|
||||||
|
}, {
|
||||||
|
kdf: hpke.KDF_HKDF_SHA256,
|
||||||
|
aead: hpke.AEAD_ChaCha20Poly1305,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
keyConfig := []myECHKeyConfig{
|
||||||
|
{id: 0, kem: hpke.KEM_X25519_HKDF_SHA256},
|
||||||
|
}
|
||||||
|
if pqSignatureSchemesEnabled {
|
||||||
|
keyConfig = append(keyConfig, myECHKeyConfig{id: 1, kem: hpke.KEM_X25519_KYBER768_DRAFT00})
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPairs, err := echKeygen(0xfe0d, serverName, keyConfig, cipherSuites)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var configBuffer bytes.Buffer
|
||||||
|
var totalLen uint16
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
totalLen += uint16(len(keyPair.rawConf))
|
||||||
|
}
|
||||||
|
binary.Write(&configBuffer, binary.BigEndian, totalLen)
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
configBuffer.Write(keyPair.rawConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyBuffer bytes.Buffer
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
keyBuffer.Write(keyPair.rawKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer.Bytes()}))
|
||||||
|
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer.Bytes()}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type echKeyConfigPair struct {
|
||||||
|
id uint8
|
||||||
|
key cftls.EXP_ECHKey
|
||||||
|
rawKey []byte
|
||||||
|
conf myECHKeyConfig
|
||||||
|
rawConf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type echCipherSuite struct {
|
||||||
|
kdf hpke.KDF
|
||||||
|
aead hpke.AEAD
|
||||||
|
}
|
||||||
|
|
||||||
|
type myECHKeyConfig struct {
|
||||||
|
id uint8
|
||||||
|
kem hpke.KEM
|
||||||
|
seed []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite []echCipherSuite) ([]echKeyConfigPair, error) {
|
||||||
|
be := binary.BigEndian
|
||||||
|
// prepare for future update
|
||||||
|
if version != 0xfe0d {
|
||||||
|
return nil, E.New("unsupported ECH version", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
suiteBuf := make([]byte, 0, len(suite)*4+2)
|
||||||
|
suiteBuf = be.AppendUint16(suiteBuf, uint16(len(suite))*4)
|
||||||
|
for _, s := range suite {
|
||||||
|
if !s.kdf.IsValid() || !s.aead.IsValid() {
|
||||||
|
return nil, E.New("invalid HPKE cipher suite")
|
||||||
|
}
|
||||||
|
suiteBuf = be.AppendUint16(suiteBuf, uint16(s.kdf))
|
||||||
|
suiteBuf = be.AppendUint16(suiteBuf, uint16(s.aead))
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs := []echKeyConfigPair{}
|
||||||
|
for _, c := range conf {
|
||||||
|
pair := echKeyConfigPair{}
|
||||||
|
pair.id = c.id
|
||||||
|
pair.conf = c
|
||||||
|
|
||||||
|
if !c.kem.IsValid() {
|
||||||
|
return nil, E.New("invalid HPKE KEM")
|
||||||
|
}
|
||||||
|
|
||||||
|
kpGenerator := c.kem.Scheme().GenerateKeyPair
|
||||||
|
if len(c.seed) > 0 {
|
||||||
|
kpGenerator = func() (kem.PublicKey, kem.PrivateKey, error) {
|
||||||
|
pub, sec := c.kem.Scheme().DeriveKeyPair(c.seed)
|
||||||
|
return pub, sec, nil
|
||||||
|
}
|
||||||
|
if len(c.seed) < c.kem.Scheme().PrivateKeySize() {
|
||||||
|
return nil, E.New("HPKE KEM seed too short")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, sec, err := kpGenerator()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "generate ECH config key pair")
|
||||||
|
}
|
||||||
|
b := []byte{}
|
||||||
|
b = be.AppendUint16(b, version)
|
||||||
|
b = be.AppendUint16(b, 0) // length field
|
||||||
|
// contents
|
||||||
|
// key config
|
||||||
|
b = append(b, c.id)
|
||||||
|
b = be.AppendUint16(b, uint16(c.kem))
|
||||||
|
pubBuf, err := pub.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "serialize ECH public key")
|
||||||
|
}
|
||||||
|
b = be.AppendUint16(b, uint16(len(pubBuf)))
|
||||||
|
b = append(b, pubBuf...)
|
||||||
|
|
||||||
|
b = append(b, suiteBuf...)
|
||||||
|
// end key config
|
||||||
|
// max name len, not supported
|
||||||
|
b = append(b, 0)
|
||||||
|
// server name
|
||||||
|
b = append(b, byte(len(serverName)))
|
||||||
|
b = append(b, []byte(serverName)...)
|
||||||
|
// extensions, not supported
|
||||||
|
b = be.AppendUint16(b, 0)
|
||||||
|
|
||||||
|
be.PutUint16(b[2:], uint16(len(b)-4))
|
||||||
|
|
||||||
|
pair.rawConf = b
|
||||||
|
|
||||||
|
secBuf, err := sec.MarshalBinary()
|
||||||
|
sk := []byte{}
|
||||||
|
sk = be.AppendUint16(sk, uint16(len(secBuf)))
|
||||||
|
sk = append(sk, secBuf...)
|
||||||
|
sk = be.AppendUint16(sk, uint16(len(b)))
|
||||||
|
sk = append(sk, b...)
|
||||||
|
|
||||||
|
cfECHKeys, err := cftls.EXP_UnmarshalECHKeys(sk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "bug: can't parse generated ECH server key")
|
||||||
|
}
|
||||||
|
if len(cfECHKeys) != 1 {
|
||||||
|
return nil, E.New("bug: unexpected server key count")
|
||||||
|
}
|
||||||
|
pair.key = cfECHKeys[0]
|
||||||
|
pair.rawKey = sk
|
||||||
|
|
||||||
|
pairs = append(pairs, pair)
|
||||||
|
}
|
||||||
|
return pairs, nil
|
||||||
|
}
|
||||||
56
common/tls/ech_quic.go
Normal file
56
common/tls/ech_quic.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
//go:build with_quic && with_ech
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/sagernet/cloudflare-tls"
|
||||||
|
"github.com/sagernet/quic-go/ech"
|
||||||
|
"github.com/sagernet/quic-go/http3_ech"
|
||||||
|
"github.com/sagernet/sing-quic"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ qtls.Config = (*echClientConfig)(nil)
|
||||||
|
_ qtls.ServerConfig = (*echServerConfig)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *echClientConfig) Dial(ctx context.Context, conn net.PacketConn, addr net.Addr, config *quic.Config) (quic.Connection, error) {
|
||||||
|
return quic.Dial(ctx, conn, addr, c.config, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echClientConfig) DialEarly(ctx context.Context, conn net.PacketConn, addr net.Addr, config *quic.Config) (quic.EarlyConnection, error) {
|
||||||
|
return quic.DialEarly(ctx, conn, addr, c.config, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config, enableDatagrams bool) http.RoundTripper {
|
||||||
|
return &http3.RoundTripper{
|
||||||
|
TLSClientConfig: c.config,
|
||||||
|
QuicConfig: quicConfig,
|
||||||
|
EnableDatagrams: enableDatagrams,
|
||||||
|
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
|
quicConn, err := quic.DialEarly(ctx, conn, serverAddr.UDPAddr(), tlsCfg, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
*quicConnPtr = quicConn
|
||||||
|
return quicConn, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) Listen(conn net.PacketConn, config *quic.Config) (qtls.Listener, error) {
|
||||||
|
return quic.Listen(conn, c.config, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) ListenEarly(conn net.PacketConn, config *quic.Config) (qtls.EarlyListener, error) {
|
||||||
|
return quic.ListenEarly(conn, c.config, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) ConfigureHTTP3() {
|
||||||
|
http3.ConfigureTLSConfig(c.config)
|
||||||
|
}
|
||||||
343
common/tls/ech_server.go
Normal file
343
common/tls/ech_server.go
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
//go:build with_ech
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/pem"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
cftls "github.com/sagernet/cloudflare-tls"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
)
|
||||||
|
|
||||||
|
type echServerConfig struct {
|
||||||
|
config *cftls.Config
|
||||||
|
logger log.Logger
|
||||||
|
certificate []byte
|
||||||
|
key []byte
|
||||||
|
certificatePath string
|
||||||
|
keyPath string
|
||||||
|
watcher *fsnotify.Watcher
|
||||||
|
echKeyPath string
|
||||||
|
echWatcher *fsnotify.Watcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) ServerName() string {
|
||||||
|
return c.config.ServerName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) SetServerName(serverName string) {
|
||||||
|
c.config.ServerName = serverName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) NextProtos() []string {
|
||||||
|
return c.config.NextProtos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) SetNextProtos(nextProto []string) {
|
||||||
|
c.config.NextProtos = nextProto
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) Config() (*STDConfig, error) {
|
||||||
|
return nil, E.New("unsupported usage for ECH")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
|
return &echConnWrapper{cftls.Client(conn, c.config)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) Server(conn net.Conn) (Conn, error) {
|
||||||
|
return &echConnWrapper{cftls.Server(conn, c.config)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) Clone() Config {
|
||||||
|
return &echServerConfig{
|
||||||
|
config: c.config.Clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) Start() error {
|
||||||
|
if c.certificatePath != "" && c.keyPath != "" {
|
||||||
|
err := c.startWatcher()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Warn("create fsnotify watcher: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.echKeyPath != "" {
|
||||||
|
err := c.startECHWatcher()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Warn("create fsnotify watcher: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) startWatcher() error {
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c.certificatePath != "" {
|
||||||
|
err = watcher.Add(c.certificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.keyPath != "" {
|
||||||
|
err = watcher.Add(c.keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.watcher = watcher
|
||||||
|
go c.loopUpdate()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) loopUpdate() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event, ok := <-c.watcher.Events:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if event.Op&fsnotify.Write != fsnotify.Write {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := c.reloadKeyPair()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error(E.Cause(err, "reload TLS key pair"))
|
||||||
|
}
|
||||||
|
case err, ok := <-c.watcher.Errors:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.logger.Error(E.Cause(err, "fsnotify error"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) reloadKeyPair() error {
|
||||||
|
if c.certificatePath != "" {
|
||||||
|
certificate, err := os.ReadFile(c.certificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "reload certificate from ", c.certificatePath)
|
||||||
|
}
|
||||||
|
c.certificate = certificate
|
||||||
|
}
|
||||||
|
if c.keyPath != "" {
|
||||||
|
key, err := os.ReadFile(c.keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "reload key from ", c.keyPath)
|
||||||
|
}
|
||||||
|
c.key = key
|
||||||
|
}
|
||||||
|
keyPair, err := cftls.X509KeyPair(c.certificate, c.key)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "reload key pair")
|
||||||
|
}
|
||||||
|
c.config.Certificates = []cftls.Certificate{keyPair}
|
||||||
|
c.logger.Info("reloaded TLS certificate")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) startECHWatcher() error {
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = watcher.Add(c.echKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.echWatcher = watcher
|
||||||
|
go c.loopECHUpdate()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) loopECHUpdate() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event, ok := <-c.echWatcher.Events:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if event.Op&fsnotify.Write != fsnotify.Write {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := c.reloadECHKey()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error(E.Cause(err, "reload ECH key"))
|
||||||
|
}
|
||||||
|
case err, ok := <-c.echWatcher.Errors:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.logger.Error(E.Cause(err, "fsnotify error"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) reloadECHKey() error {
|
||||||
|
echKeyContent, err := os.ReadFile(c.echKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
block, rest := pem.Decode(echKeyContent)
|
||||||
|
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
|
||||||
|
return E.New("invalid ECH keys pem")
|
||||||
|
}
|
||||||
|
echKeys, err := cftls.EXP_UnmarshalECHKeys(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "parse ECH keys")
|
||||||
|
}
|
||||||
|
echKeySet, err := cftls.EXP_NewECHKeySet(echKeys)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "create ECH key set")
|
||||||
|
}
|
||||||
|
c.config.ServerECHProvider = echKeySet
|
||||||
|
c.logger.Info("reloaded ECH keys")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) Close() error {
|
||||||
|
var err error
|
||||||
|
if c.watcher != nil {
|
||||||
|
err = E.Append(err, c.watcher.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close certificate watcher")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if c.echWatcher != nil {
|
||||||
|
err = E.Append(err, c.echWatcher.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close ECH key watcher")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewECHServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
|
if !options.Enabled {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var tlsConfig cftls.Config
|
||||||
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
|
return nil, E.New("acme is unavailable in ech")
|
||||||
|
}
|
||||||
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
|
if options.ServerName != "" {
|
||||||
|
tlsConfig.ServerName = options.ServerName
|
||||||
|
}
|
||||||
|
if len(options.ALPN) > 0 {
|
||||||
|
tlsConfig.NextProtos = append(options.ALPN, tlsConfig.NextProtos...)
|
||||||
|
}
|
||||||
|
if options.MinVersion != "" {
|
||||||
|
minVersion, err := ParseTLSVersion(options.MinVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse min_version")
|
||||||
|
}
|
||||||
|
tlsConfig.MinVersion = minVersion
|
||||||
|
}
|
||||||
|
if options.MaxVersion != "" {
|
||||||
|
maxVersion, err := ParseTLSVersion(options.MaxVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse max_version")
|
||||||
|
}
|
||||||
|
tlsConfig.MaxVersion = maxVersion
|
||||||
|
}
|
||||||
|
if options.CipherSuites != nil {
|
||||||
|
find:
|
||||||
|
for _, cipherSuite := range options.CipherSuites {
|
||||||
|
for _, tlsCipherSuite := range tls.CipherSuites() {
|
||||||
|
if cipherSuite == tlsCipherSuite.Name {
|
||||||
|
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
|
||||||
|
continue find
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, E.New("unknown cipher_suite: ", cipherSuite)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var certificate []byte
|
||||||
|
var key []byte
|
||||||
|
if len(options.Certificate) > 0 {
|
||||||
|
certificate = []byte(strings.Join(options.Certificate, "\n"))
|
||||||
|
} else if options.CertificatePath != "" {
|
||||||
|
content, err := os.ReadFile(options.CertificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read certificate")
|
||||||
|
}
|
||||||
|
certificate = content
|
||||||
|
}
|
||||||
|
if len(options.Key) > 0 {
|
||||||
|
key = []byte(strings.Join(options.Key, "\n"))
|
||||||
|
} else if options.KeyPath != "" {
|
||||||
|
content, err := os.ReadFile(options.KeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read key")
|
||||||
|
}
|
||||||
|
key = content
|
||||||
|
}
|
||||||
|
|
||||||
|
if certificate == nil {
|
||||||
|
return nil, E.New("missing certificate")
|
||||||
|
} else if key == nil {
|
||||||
|
return nil, E.New("missing key")
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPair, err := cftls.X509KeyPair(certificate, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse x509 key pair")
|
||||||
|
}
|
||||||
|
tlsConfig.Certificates = []cftls.Certificate{keyPair}
|
||||||
|
|
||||||
|
var echKey []byte
|
||||||
|
if len(options.ECH.Key) > 0 {
|
||||||
|
echKey = []byte(strings.Join(options.ECH.Key, "\n"))
|
||||||
|
} else if options.KeyPath != "" {
|
||||||
|
content, err := os.ReadFile(options.ECH.KeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read ECH key")
|
||||||
|
}
|
||||||
|
echKey = content
|
||||||
|
} else {
|
||||||
|
return nil, E.New("missing ECH key")
|
||||||
|
}
|
||||||
|
|
||||||
|
block, rest := pem.Decode(echKey)
|
||||||
|
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
|
||||||
|
return nil, E.New("invalid ECH keys pem")
|
||||||
|
}
|
||||||
|
|
||||||
|
echKeys, err := cftls.EXP_UnmarshalECHKeys(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse ECH keys")
|
||||||
|
}
|
||||||
|
|
||||||
|
echKeySet, err := cftls.EXP_NewECHKeySet(echKeys)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "create ECH key set")
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig.ECHEnabled = true
|
||||||
|
tlsConfig.PQSignatureSchemesEnabled = options.ECH.PQSignatureSchemesEnabled
|
||||||
|
tlsConfig.DynamicRecordSizingDisabled = options.ECH.DynamicRecordSizingDisabled
|
||||||
|
tlsConfig.ServerECHProvider = echKeySet
|
||||||
|
|
||||||
|
return &echServerConfig{
|
||||||
|
config: &tlsConfig,
|
||||||
|
logger: logger,
|
||||||
|
certificate: certificate,
|
||||||
|
key: key,
|
||||||
|
certificatePath: options.CertificatePath,
|
||||||
|
keyPath: options.KeyPath,
|
||||||
|
echKeyPath: options.ECH.KeyPath,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -3,11 +3,23 @@
|
|||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
var errECHNotIncluded = E.New(`ECH is not included in this build, rebuild with -tags with_ech`)
|
||||||
return nil, E.New(`ECH is not included in this build, rebuild with -tags with_ech`)
|
|
||||||
|
func NewECHServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
|
return nil, errECHNotIncluded
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewECHClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
|
return nil, errECHNotIncluded
|
||||||
|
}
|
||||||
|
|
||||||
|
func ECHKeygenDefault(host string, pqSignatureSchemesEnabled bool) (configPem string, keyPem string, err error) {
|
||||||
|
return "", "", errECHNotIncluded
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,22 +11,34 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateKeyPair(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
||||||
|
privateKeyPem, publicKeyPem, err := GenerateKeyPair(timeFunc, serverName, timeFunc().Add(time.Hour))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
certificate, err := tls.X509KeyPair(publicKeyPem, privateKeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &certificate, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
|
||||||
if timeFunc == nil {
|
if timeFunc == nil {
|
||||||
timeFunc = time.Now
|
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
|
||||||
}
|
}
|
||||||
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
template := &x509.Certificate{
|
template := &x509.Certificate{
|
||||||
SerialNumber: serialNumber,
|
SerialNumber: serialNumber,
|
||||||
NotBefore: timeFunc().Add(time.Hour * -1),
|
NotBefore: timeFunc().Add(time.Hour * -1),
|
||||||
NotAfter: timeFunc().Add(time.Hour),
|
NotAfter: expire,
|
||||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
@@ -37,17 +49,13 @@ func GenerateKeyPair(timeFunc func() time.Time, serverName string) (*tls.Certifi
|
|||||||
}
|
}
|
||||||
publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
|
publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
privateDer, err := x509.MarshalPKCS8PrivateKey(key)
|
privateDer, err := x509.MarshalPKCS8PrivateKey(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
publicPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer})
|
publicKeyPem = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer})
|
||||||
privPem := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateDer})
|
privateKeyPem = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateDer})
|
||||||
keyPair, err := tls.X509KeyPair(publicPem, privPem)
|
return
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &keyPair, err
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/debug"
|
"github.com/sagernet/sing/common/debug"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -45,12 +44,12 @@ type RealityClientConfig struct {
|
|||||||
shortID [8]byte
|
shortID [8]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRealityClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*RealityClientConfig, error) {
|
func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (*RealityClientConfig, error) {
|
||||||
if options.UTLS == nil || !options.UTLS.Enabled {
|
if options.UTLS == nil || !options.UTLS.Enabled {
|
||||||
return nil, E.New("uTLS is required by reality client")
|
return nil, E.New("uTLS is required by reality client")
|
||||||
}
|
}
|
||||||
|
|
||||||
uClient, err := NewUTLSClient(router, serverAddress, options)
|
uClient, err := NewUTLSClient(ctx, serverAddress, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,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/sing/common/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
|
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
|
||||||
@@ -27,13 +28,13 @@ type RealityServerConfig struct {
|
|||||||
config *reality.Config
|
config *reality.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
|
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
|
||||||
var tlsConfig reality.Config
|
var tlsConfig reality.Config
|
||||||
|
|
||||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
return nil, E.New("acme is unavailable in reality")
|
return nil, E.New("acme is unavailable in reality")
|
||||||
}
|
}
|
||||||
tlsConfig.Time = router.TimeFunc()
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
tlsConfig.ServerName = options.ServerName
|
tlsConfig.ServerName = options.ServerName
|
||||||
}
|
}
|
||||||
@@ -66,10 +67,10 @@ func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Log
|
|||||||
return nil, E.New("unknown cipher_suite: ", cipherSuite)
|
return nil, E.New("unknown cipher_suite: ", cipherSuite)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.Certificate != "" || options.CertificatePath != "" {
|
if len(options.Certificate) > 0 || options.CertificatePath != "" {
|
||||||
return nil, E.New("certificate is unavailable in reality")
|
return nil, E.New("certificate is unavailable in reality")
|
||||||
}
|
}
|
||||||
if options.Key != "" || options.KeyPath != "" {
|
if len(options.Key) > 0 || options.KeyPath != "" {
|
||||||
return nil, E.New("key is unavailable in reality")
|
return nil, E.New("key is unavailable in reality")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +102,7 @@ func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Log
|
|||||||
tlsConfig.ShortIds[shortID] = true
|
tlsConfig.ShortIds[shortID] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
handshakeDialer, err := dialer.New(router, options.Reality.Handshake.DialerOptions)
|
handshakeDialer, err := dialer.New(adapter.RouterFromContext(ctx), options.Reality.Handshake.DialerOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ package tls
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
func NewRealityServer(ctx context.Context, 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`)
|
return nil, E.New(`reality server is not included in this build, rebuild with -tags with_reality_server`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,21 +4,22 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"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"
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if options.Reality != nil && options.Reality.Enabled {
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
return NewRealityServer(ctx, router, logger, options)
|
return NewECHServer(ctx, logger, options)
|
||||||
|
} else if options.Reality != nil && options.Reality.Enabled {
|
||||||
|
return NewRealityServer(ctx, logger, options)
|
||||||
} else {
|
} else {
|
||||||
return NewSTDServer(ctx, router, logger, options)
|
return NewSTDServer(ctx, logger, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"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"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type STDClientConfig struct {
|
type STDClientConfig struct {
|
||||||
@@ -44,7 +46,7 @@ func (s *STDClientConfig) Clone() Config {
|
|||||||
return &STDClientConfig{s.config.Clone()}
|
return &STDClientConfig{s.config.Clone()}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewSTDClient(ctx context.Context, 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
|
||||||
@@ -58,7 +60,7 @@ func NewSTDClient(router adapter.Router, serverAddress string, options option.Ou
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tlsConfig tls.Config
|
var tlsConfig tls.Config
|
||||||
tlsConfig.Time = router.TimeFunc()
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
@@ -110,8 +112,8 @@ func NewSTDClient(router adapter.Router, serverAddress string, options option.Ou
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var certificate []byte
|
var certificate []byte
|
||||||
if options.Certificate != "" {
|
if len(options.Certificate) > 0 {
|
||||||
certificate = []byte(options.Certificate)
|
certificate = []byte(strings.Join(options.Certificate, "\n"))
|
||||||
} else if options.CertificatePath != "" {
|
} else if options.CertificatePath != "" {
|
||||||
content, err := os.ReadFile(options.CertificatePath)
|
content, err := os.ReadFile(options.CertificatePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
)
|
)
|
||||||
@@ -156,7 +158,7 @@ func (c *STDServerConfig) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
func NewSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -175,7 +177,7 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
|
|||||||
} else {
|
} else {
|
||||||
tlsConfig = &tls.Config{}
|
tlsConfig = &tls.Config{}
|
||||||
}
|
}
|
||||||
tlsConfig.Time = router.TimeFunc()
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
tlsConfig.ServerName = options.ServerName
|
tlsConfig.ServerName = options.ServerName
|
||||||
}
|
}
|
||||||
@@ -211,8 +213,8 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
|
|||||||
var certificate []byte
|
var certificate []byte
|
||||||
var key []byte
|
var key []byte
|
||||||
if acmeService == nil {
|
if acmeService == nil {
|
||||||
if options.Certificate != "" {
|
if len(options.Certificate) > 0 {
|
||||||
certificate = []byte(options.Certificate)
|
certificate = []byte(strings.Join(options.Certificate, "\n"))
|
||||||
} else if options.CertificatePath != "" {
|
} else if options.CertificatePath != "" {
|
||||||
content, err := os.ReadFile(options.CertificatePath)
|
content, err := os.ReadFile(options.CertificatePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -220,8 +222,8 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
|
|||||||
}
|
}
|
||||||
certificate = content
|
certificate = content
|
||||||
}
|
}
|
||||||
if options.Key != "" {
|
if len(options.Key) > 0 {
|
||||||
key = []byte(options.Key)
|
key = []byte(strings.Join(options.Key, "\n"))
|
||||||
} else if options.KeyPath != "" {
|
} else if options.KeyPath != "" {
|
||||||
content, err := os.ReadFile(options.KeyPath)
|
content, err := os.ReadFile(options.KeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -231,7 +233,7 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
|
|||||||
}
|
}
|
||||||
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(router.TimeFunc(), info.ServerName)
|
return GenerateCertificate(ntp.TimeFuncFromContext(ctx), info.ServerName)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if certificate == nil {
|
if certificate == nil {
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"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"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
utls "github.com/sagernet/utls"
|
utls "github.com/sagernet/utls"
|
||||||
|
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
@@ -113,7 +114,7 @@ func (c *utlsALPNWrapper) HandshakeContext(ctx context.Context) error {
|
|||||||
return c.UConn.HandshakeContext(ctx)
|
return c.UConn.HandshakeContext(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
|
func NewUTLSClient(ctx context.Context, 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
|
||||||
@@ -127,7 +128,7 @@ func NewUTLSClient(router adapter.Router, serverAddress string, options option.O
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tlsConfig utls.Config
|
var tlsConfig utls.Config
|
||||||
tlsConfig.Time = router.TimeFunc()
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
@@ -168,8 +169,8 @@ func NewUTLSClient(router adapter.Router, serverAddress string, options option.O
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var certificate []byte
|
var certificate []byte
|
||||||
if options.Certificate != "" {
|
if len(options.Certificate) > 0 {
|
||||||
certificate = []byte(options.Certificate)
|
certificate = []byte(strings.Join(options.Certificate, "\n"))
|
||||||
} else if options.CertificatePath != "" {
|
} else if options.CertificatePath != "" {
|
||||||
content, err := os.ReadFile(options.CertificatePath)
|
content, err := os.ReadFile(options.CertificatePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -3,15 +3,16 @@
|
|||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"context"
|
||||||
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewUTLSClient(ctx context.Context, 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) {
|
func NewRealityClient(ctx context.Context, 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`)
|
return nil, E.New(`uTLS, which is required by reality client is not included in this build, rebuild with -tags with_utls`)
|
||||||
}
|
}
|
||||||
|
|||||||
53
common/uot/router.go
Normal file
53
common/uot/router.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package uot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/uot"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.ConnectionRouter = (*Router)(nil)
|
||||||
|
|
||||||
|
type Router struct {
|
||||||
|
router adapter.ConnectionRouter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) *Router {
|
||||||
|
return &Router{router, logger}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
|
switch metadata.Destination.Fqdn {
|
||||||
|
case uot.MagicAddress:
|
||||||
|
request, err := uot.ReadRequest(conn)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "read UoT request")
|
||||||
|
}
|
||||||
|
if request.IsConnect {
|
||||||
|
r.logger.InfoContext(ctx, "inbound UoT connect connection to ", request.Destination)
|
||||||
|
} else {
|
||||||
|
r.logger.InfoContext(ctx, "inbound UoT connection to ", request.Destination)
|
||||||
|
}
|
||||||
|
metadata.Domain = metadata.Destination.Fqdn
|
||||||
|
metadata.Destination = request.Destination
|
||||||
|
return r.router.RoutePacketConnection(ctx, uot.NewConn(conn, *request), metadata)
|
||||||
|
case uot.LegacyMagicAddress:
|
||||||
|
r.logger.InfoContext(ctx, "inbound legacy UoT connection")
|
||||||
|
metadata.Domain = metadata.Destination.Fqdn
|
||||||
|
metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
|
||||||
|
return r.RoutePacketConnection(ctx, uot.NewConn(conn, uot.Request{}), metadata)
|
||||||
|
}
|
||||||
|
return r.router.RouteConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
|
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
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"
|
||||||
)
|
)
|
||||||
@@ -96,33 +97,25 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer instance.Close()
|
defer instance.Close()
|
||||||
|
if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](instance); isEarlyConn && earlyConn.NeedHandshake() {
|
||||||
|
start = time.Now()
|
||||||
|
}
|
||||||
req, err := http.NewRequest(http.MethodHead, link, nil)
|
req, err := http.NewRequest(http.MethodHead, link, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req = req.WithContext(ctx)
|
|
||||||
|
|
||||||
transport := &http.Transport{
|
|
||||||
Dial: func(string, string) (net.Conn, error) {
|
|
||||||
return instance, nil
|
|
||||||
},
|
|
||||||
// from http.DefaultTransport
|
|
||||||
MaxIdleConns: 100,
|
|
||||||
IdleConnTimeout: 90 * time.Second,
|
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
client := http.Client{
|
client := http.Client{
|
||||||
Transport: transport,
|
Transport: &http.Transport{
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return instance, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
defer client.CloseIdleConnections()
|
defer client.CloseIdleConnections()
|
||||||
|
resp, err := client.Do(req.WithContext(ctx))
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
6
constant/dns.go
Normal file
6
constant/dns.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
const (
|
||||||
|
DNSProviderAliDNS = "alidns"
|
||||||
|
DNSProviderCloudflare = "cloudflare"
|
||||||
|
)
|
||||||
@@ -22,6 +22,7 @@ const (
|
|||||||
TypeShadowsocksR = "shadowsocksr"
|
TypeShadowsocksR = "shadowsocksr"
|
||||||
TypeVLESS = "vless"
|
TypeVLESS = "vless"
|
||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
|
TypeHysteria2 = "hysteria2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -65,6 +66,8 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "VLESS"
|
return "VLESS"
|
||||||
case TypeTUIC:
|
case TypeTUIC:
|
||||||
return "TUIC"
|
return "TUIC"
|
||||||
|
case TypeHysteria2:
|
||||||
|
return "Hysteria2"
|
||||||
case TypeSelector:
|
case TypeSelector:
|
||||||
return "Selector"
|
return "Selector"
|
||||||
case TypeURLTest:
|
case TypeURLTest:
|
||||||
|
|||||||
3
constant/speed.go
Normal file
3
constant/speed.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
const MbpsToBps = 125000
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
const (
|
const (
|
||||||
V2RayTransportTypeHTTP = "http"
|
V2RayTransportTypeHTTP = "http"
|
||||||
V2RayTransportTypeWebsocket = "ws"
|
V2RayTransportTypeWebsocket = "ws"
|
||||||
V2RayTransportTypeQUIC = "quic"
|
V2RayTransportTypeQUIC = "quic"
|
||||||
V2RayTransportTypeGRPC = "grpc"
|
V2RayTransportTypeGRPC = "grpc"
|
||||||
|
V2RayTransportTypeHTTPUpgrade = "httpupgrade"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ package box
|
|||||||
import (
|
import (
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
"github.com/sagernet/sing-box/common/conntrack"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ func applyDebugOptions(options option.DebugOptions) {
|
|||||||
}
|
}
|
||||||
if options.MemoryLimit != 0 {
|
if options.MemoryLimit != 0 {
|
||||||
// debug.SetMemoryLimit(int64(options.MemoryLimit))
|
// debug.SetMemoryLimit(int64(options.MemoryLimit))
|
||||||
conntrack.MemoryLimit = int64(options.MemoryLimit)
|
conntrack.MemoryLimit = uint64(options.MemoryLimit)
|
||||||
}
|
}
|
||||||
if options.OOMKiller != nil {
|
if options.OOMKiller != nil {
|
||||||
conntrack.KillerEnabled = *options.OOMKiller
|
conntrack.KillerEnabled = *options.OOMKiller
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ package box
|
|||||||
import (
|
import (
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
"github.com/sagernet/sing-box/common/conntrack"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,8 +27,8 @@ func applyDebugOptions(options option.DebugOptions) {
|
|||||||
debug.SetTraceback(options.TraceBack)
|
debug.SetTraceback(options.TraceBack)
|
||||||
}
|
}
|
||||||
if options.MemoryLimit != 0 {
|
if options.MemoryLimit != 0 {
|
||||||
debug.SetMemoryLimit(int64(options.MemoryLimit))
|
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5))
|
||||||
conntrack.MemoryLimit = int64(options.MemoryLimit)
|
conntrack.MemoryLimit = uint64(options.MemoryLimit)
|
||||||
}
|
}
|
||||||
if options.OOMKiller != nil {
|
if options.OOMKiller != nil {
|
||||||
conntrack.KillerEnabled = *options.OOMKiller
|
conntrack.KillerEnabled = *options.OOMKiller
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import (
|
|||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/badjson"
|
"github.com/sagernet/sing-box/common/badjson"
|
||||||
|
"github.com/sagernet/sing-box/common/humanize"
|
||||||
"github.com/sagernet/sing-box/common/json"
|
"github.com/sagernet/sing-box/common/json"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"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"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,9 +37,9 @@ func applyDebugListenOption(options option.DebugOptions) {
|
|||||||
runtime.ReadMemStats(&memStats)
|
runtime.ReadMemStats(&memStats)
|
||||||
|
|
||||||
var memObject badjson.JSONObject
|
var memObject badjson.JSONObject
|
||||||
memObject.Put("heap", humanize.IBytes(memStats.HeapInuse))
|
memObject.Put("heap", humanize.MemoryBytes(memStats.HeapInuse))
|
||||||
memObject.Put("stack", humanize.IBytes(memStats.StackInuse))
|
memObject.Put("stack", humanize.MemoryBytes(memStats.StackInuse))
|
||||||
memObject.Put("idle", humanize.IBytes(memStats.HeapIdle-memStats.HeapReleased))
|
memObject.Put("idle", humanize.MemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||||
memObject.Put("goroutines", runtime.NumGoroutine())
|
memObject.Put("goroutines", runtime.NumGoroutine())
|
||||||
memObject.Put("rss", rusageMaxRSS())
|
memObject.Put("rss", rusageMaxRSS())
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,558 @@
|
|||||||
|
---
|
||||||
|
icon: material/alert-decagram
|
||||||
|
---
|
||||||
|
|
||||||
|
# ChangeLog
|
||||||
|
|
||||||
|
#### 1.7.6
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.5
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.4
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
_Due to the long waiting time, this version is no longer waiting for approval
|
||||||
|
by the Apple App Store, so updates to Apple Platforms will be delayed._
|
||||||
|
|
||||||
|
#### 1.7.2
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.1
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
Important changes since 1.6:
|
||||||
|
|
||||||
|
* Add [exclude route support](/configuration/inbound/tun/) for TUN inbound
|
||||||
|
* Add `udp_disable_domain_unmapping` [inbound listen option](/configuration/shared/listen/) **1**
|
||||||
|
* Add [HTTPUpgrade V2Ray transport](/configuration/shared/v2ray-transport#HTTPUpgrade) support **2**
|
||||||
|
* Migrate multiplex and UoT server to inbound **3**
|
||||||
|
* Add TCP Brutal support for multiplex **4**
|
||||||
|
* Add `wifi_ssid` and `wifi_bssid` route and DNS rules **5**
|
||||||
|
* Update quic-go to v0.40.0
|
||||||
|
* Update gVisor to 20231113.0
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
If enabled, for UDP proxy requests addressed to a domain,
|
||||||
|
the original packet address will be sent in the response instead of the mapped domain.
|
||||||
|
|
||||||
|
This option is used for compatibility with clients that
|
||||||
|
do not support receiving UDP packets with domain addresses, such as Surge.
|
||||||
|
|
||||||
|
**2**:
|
||||||
|
|
||||||
|
Introduced in V2Ray 5.10.0.
|
||||||
|
|
||||||
|
The new HTTPUpgrade transport has better performance than WebSocket and is better suited for CDN abuse.
|
||||||
|
|
||||||
|
**3**:
|
||||||
|
|
||||||
|
Starting in 1.7.0, multiplexing support is no longer enabled by default
|
||||||
|
and needs to be turned on explicitly in inbound options.
|
||||||
|
|
||||||
|
**4**
|
||||||
|
|
||||||
|
Hysteria Brutal Congestion Control Algorithm in TCP. A kernel module needs to be installed on the Linux server,
|
||||||
|
see [TCP Brutal](/configuration/shared/tcp-brutal/) for details.
|
||||||
|
|
||||||
|
**5**:
|
||||||
|
|
||||||
|
Only supported in graphical clients on Android and iOS.
|
||||||
|
|
||||||
|
#### 1.7.0-rc.3
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.7
|
||||||
|
|
||||||
|
* macOS: Add button for uninstall SystemExtension in the standalone graphical client
|
||||||
|
* Fix missing UDP user context on TUIC/Hysteria2 inbounds
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-rc.2
|
||||||
|
|
||||||
|
* Fix missing UDP user context on TUIC/Hysteria2 inbounds
|
||||||
|
* macOS: Add button for uninstall SystemExtension in the standalone graphical client
|
||||||
|
|
||||||
|
#### 1.6.6
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-rc.1
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-beta.5
|
||||||
|
|
||||||
|
* Update gVisor to 20231113.0
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-beta.4
|
||||||
|
|
||||||
|
* Add `wifi_ssid` and `wifi_bssid` route and DNS rules **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Only supported in graphical clients on Android and iOS.
|
||||||
|
|
||||||
|
#### 1.7.0-beta.3
|
||||||
|
|
||||||
|
* Fix zero TTL was incorrectly reset
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.5
|
||||||
|
|
||||||
|
* Fix crash if TUIC inbound authentication failed
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-beta.2
|
||||||
|
|
||||||
|
* Fix crash if TUIC inbound authentication failed
|
||||||
|
* Update quic-go to v0.40.0
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.4
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-beta.1
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.3
|
||||||
|
|
||||||
|
* iOS/Android: Fix profile auto update
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-alpha.11
|
||||||
|
|
||||||
|
* iOS/Android: Fix profile auto update
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-alpha.10
|
||||||
|
|
||||||
|
* Fix tcp-brutal not working with TLS
|
||||||
|
* Fix Android client not closing in some cases
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.2
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.1
|
||||||
|
|
||||||
|
* Our [Android client](/installation/clients/sfa/) is now available in the Google Play Store ▶️
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-alpha.6
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-alpha.4
|
||||||
|
|
||||||
|
* Migrate multiplex and UoT server to inbound **1**
|
||||||
|
* Add TCP Brutal support for multiplex **2**
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Starting in 1.7.0, multiplexing support is no longer enabled by default and needs to be turned on explicitly in inbound
|
||||||
|
options.
|
||||||
|
|
||||||
|
**2**
|
||||||
|
|
||||||
|
Hysteria Brutal Congestion Control Algorithm in TCP. A kernel module needs to be installed on the Linux server,
|
||||||
|
see [TCP Brutal](/configuration/shared/tcp-brutal/) for details.
|
||||||
|
|
||||||
|
#### 1.7.0-alpha.3
|
||||||
|
|
||||||
|
* Add [HTTPUpgrade V2Ray transport](/configuration/shared/v2ray-transport#HTTPUpgrade) support **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Introduced in V2Ray 5.10.0.
|
||||||
|
|
||||||
|
The new HTTPUpgrade transport has better performance than WebSocket and is better suited for CDN abuse.
|
||||||
|
|
||||||
|
#### 1.6.0
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
Important changes since 1.5:
|
||||||
|
|
||||||
|
* Our [Apple tvOS client](/installation/clients/sft/) is now available in the App Store 🍎
|
||||||
|
* Update BBR congestion control for TUIC and Hysteria2 **1**
|
||||||
|
* Update brutal congestion control for Hysteria2
|
||||||
|
* Add `brutal_debug` option for Hysteria2
|
||||||
|
* Update legacy Hysteria protocol **2**
|
||||||
|
* Add TLS self sign key pair generate command
|
||||||
|
* Remove [Deprecated Features](/deprecated/) by agreement
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
None of the existing Golang BBR congestion control implementations have been reviewed or unit tested.
|
||||||
|
This update is intended to address the multi-send defects of the old implementation and may introduce new issues.
|
||||||
|
|
||||||
|
**2**
|
||||||
|
|
||||||
|
Based on discussions with the original author, the brutal CC and QUIC protocol parameters of
|
||||||
|
the old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2
|
||||||
|
|
||||||
|
#### 1.7.0-alpha.2
|
||||||
|
|
||||||
|
* Fix bugs introduced in 1.7.0-alpha.1
|
||||||
|
|
||||||
|
#### 1.7.0-alpha.1
|
||||||
|
|
||||||
|
* Add [exclude route support](/configuration/inbound/tun/) for TUN inbound
|
||||||
|
* Add `udp_disable_domain_unmapping` [inbound listen option](/configuration/shared/listen/) **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
If enabled, for UDP proxy requests addressed to a domain,
|
||||||
|
the original packet address will be sent in the response instead of the mapped domain.
|
||||||
|
|
||||||
|
This option is used for compatibility with clients that
|
||||||
|
do not support receiving UDP packets with domain addresses, such as Surge.
|
||||||
|
|
||||||
|
#### 1.5.5
|
||||||
|
|
||||||
|
* Fix IPv6 `auto_route` for Linux **1**
|
||||||
|
* Add legacy builds for old Windows and macOS systems **2**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
When `auto_route` is enabled and `strict_route` is disabled, the device can now be reached from external IPv6 addresses.
|
||||||
|
|
||||||
|
**2**:
|
||||||
|
|
||||||
|
Built using Go 1.20, the last version that will run on
|
||||||
|
Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High Sierra, 10.14 Mojave.
|
||||||
|
|
||||||
|
#### 1.6.0-rc.4
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.0-rc.1
|
||||||
|
|
||||||
|
* Add legacy builds for old Windows and macOS systems **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Built using Go 1.20, the last version that will run on
|
||||||
|
Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High Sierra, 10.14 Mojave.
|
||||||
|
|
||||||
|
#### 1.6.0-beta.4
|
||||||
|
|
||||||
|
* Fix IPv6 `auto_route` for Linux **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
When `auto_route` is enabled and `strict_route` is disabled, the device can now be reached from external IPv6 addresses.
|
||||||
|
|
||||||
|
#### 1.5.4
|
||||||
|
|
||||||
|
* Fix Clash cache crash on arm32 devices
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.0-beta.3
|
||||||
|
|
||||||
|
* Update the legacy Hysteria protocol **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**
|
||||||
|
|
||||||
|
Based on discussions with the original author, the brutal CC and QUIC protocol parameters of
|
||||||
|
the old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2
|
||||||
|
|
||||||
|
#### 1.6.0-beta.2
|
||||||
|
|
||||||
|
* Add TLS self sign key pair generate command
|
||||||
|
* Update brutal congestion control for Hysteria2
|
||||||
|
* Fix Clash cache crash on arm32 devices
|
||||||
|
* Update golang.org/x/net to v0.17.0
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.0-beta.3
|
||||||
|
|
||||||
|
* Update the legacy Hysteria protocol **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**
|
||||||
|
|
||||||
|
Based on discussions with the original author, the brutal CC and QUIC protocol parameters of
|
||||||
|
the old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2
|
||||||
|
|
||||||
|
#### 1.6.0-beta.2
|
||||||
|
|
||||||
|
* Add TLS self sign key pair generate command
|
||||||
|
* Update brutal congestion control for Hysteria2
|
||||||
|
* Fix Clash cache crash on arm32 devices
|
||||||
|
* Update golang.org/x/net to v0.17.0
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.5.3
|
||||||
|
|
||||||
|
* Fix compatibility with Android 14
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.0-beta.1
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.0-alpha.5
|
||||||
|
|
||||||
|
* Fix compatibility with Android 14
|
||||||
|
* Update BBR congestion control for TUIC and Hysteria2 **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
None of the existing Golang BBR congestion control implementations have been reviewed or unit tested.
|
||||||
|
This update is intended to fix a memory leak flaw in the new implementation introduced in 1.6.0-alpha.1 and may
|
||||||
|
introduce new issues.
|
||||||
|
|
||||||
|
#### 1.6.0-alpha.4
|
||||||
|
|
||||||
|
* Add `brutal_debug` option for Hysteria2
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.5.2
|
||||||
|
|
||||||
|
* Our [Apple tvOS client](/installation/clients/sft/) is now available in the App Store 🍎
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.0-alpha.3
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.0-alpha.2
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.5.1
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.0-alpha.1
|
||||||
|
|
||||||
|
* Update BBR congestion control for TUIC and Hysteria2 **1**
|
||||||
|
* Update quic-go to v0.39.0
|
||||||
|
* Update gVisor to 20230814.0
|
||||||
|
* Remove [Deprecated Features](/deprecated/) by agreement
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
None of the existing Golang BBR congestion control implementations have been reviewed or unit tested.
|
||||||
|
This update is intended to address the multi-send defects of the old implementation and may introduce new issues.
|
||||||
|
|
||||||
|
#### 1.5.0
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
Important changes since 1.4:
|
||||||
|
|
||||||
|
* Add TLS [ECH server](/configuration/shared/tls/) support
|
||||||
|
* Improve TLS TCH client configuration
|
||||||
|
* Add TLS ECH key pair generator **1**
|
||||||
|
* Add TLS ECH support for QUIC based protocols **2**
|
||||||
|
* Add KDE support for the `set_system_proxy` option in HTTP inbound
|
||||||
|
* Add Hysteria2 protocol support **3**
|
||||||
|
* Add `interrupt_exist_connections` option for `Selector` and `URLTest` outbounds **4**
|
||||||
|
* Add DNS01 challenge support for ACME TLS certificate issuer **5**
|
||||||
|
* Add `merge` command **6**
|
||||||
|
* Mark [Deprecated Features](/deprecated/)
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Command: `sing-box generate ech-keypair <plain_server_name> [--pq-signature-schemes-enabled]`
|
||||||
|
|
||||||
|
**2**:
|
||||||
|
|
||||||
|
All inbounds and outbounds are supported, including `Naiveproxy`, `Hysteria[/2]`, `TUIC` and `V2ray QUIC transport`.
|
||||||
|
|
||||||
|
**3**:
|
||||||
|
|
||||||
|
See [Hysteria2 inbound](/configuration/inbound/hysteria2/) and [Hysteria2 outbound](/configuration/outbound/hysteria2/)
|
||||||
|
|
||||||
|
For protocol description, please refer to [https://v2.hysteria.network](https://v2.hysteria.network)
|
||||||
|
|
||||||
|
**4**:
|
||||||
|
|
||||||
|
Interrupt existing connections when the selected outbound has changed.
|
||||||
|
|
||||||
|
Only inbound connections are affected by this setting, internal connections will always be interrupted.
|
||||||
|
|
||||||
|
**5**:
|
||||||
|
|
||||||
|
Only `Alibaba Cloud DNS` and `Cloudflare` are supported, see [ACME Fields](/configuration/shared/tls#acme-fields)
|
||||||
|
and [DNS01 Challenge Fields](/configuration/shared/dns01_challenge/).
|
||||||
|
|
||||||
|
**6**:
|
||||||
|
|
||||||
|
This command also parses path resources that appear in the configuration file and replaces them with embedded
|
||||||
|
configuration, such as TLS certificates or SSH private keys.
|
||||||
|
|
||||||
|
#### 1.5.0-rc.6
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.4.6
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.5.0-rc.5
|
||||||
|
|
||||||
|
* Fixed an improper authentication vulnerability in the SOCKS5 inbound
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**Security Advisory**
|
||||||
|
|
||||||
|
This update fixes an improper authentication vulnerability in the sing-box SOCKS inbound. This vulnerability allows an
|
||||||
|
attacker to craft special requests to bypass user authentication. All users exposing SOCKS servers with user
|
||||||
|
authentication in an insecure environment are advised to update immediately.
|
||||||
|
|
||||||
|
此更新修复了 sing-box SOCKS 入站中的一个不正确身份验证漏洞。 该漏洞允许攻击者制作特殊请求来绕过用户身份验证。建议所有将使用用户认证的
|
||||||
|
SOCKS 服务器暴露在不安全环境下的用户立更新。
|
||||||
|
|
||||||
|
#### 1.4.5
|
||||||
|
|
||||||
|
* Fixed an improper authentication vulnerability in the SOCKS5 inbound
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**Security Advisory**
|
||||||
|
|
||||||
|
This update fixes an improper authentication vulnerability in the sing-box SOCKS inbound. This vulnerability allows an
|
||||||
|
attacker to craft special requests to bypass user authentication. All users exposing SOCKS servers with user
|
||||||
|
authentication in an insecure environment are advised to update immediately.
|
||||||
|
|
||||||
|
此更新修复了 sing-box SOCKS 入站中的一个不正确身份验证漏洞。 该漏洞允许攻击者制作特殊请求来绕过用户身份验证。建议所有将使用用户认证的
|
||||||
|
SOCKS 服务器暴露在不安全环境下的用户立更新。
|
||||||
|
|
||||||
|
#### 1.5.0-rc.3
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.5.0-beta.12
|
||||||
|
|
||||||
|
* Add `merge` command **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
This command also parses path resources that appear in the configuration file and replaces them with embedded
|
||||||
|
configuration, such as TLS certificates or SSH private keys.
|
||||||
|
|
||||||
|
```
|
||||||
|
Merge configurations
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
sing-box merge [output] [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h, --help help for merge
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
-c, --config stringArray set configuration file path
|
||||||
|
-C, --config-directory stringArray set configuration directory path
|
||||||
|
-D, --directory string set working directory
|
||||||
|
--disable-color disable color output
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.5.0-beta.11
|
||||||
|
|
||||||
|
* Add DNS01 challenge support for ACME TLS certificate issuer **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Only `Alibaba Cloud DNS` and `Cloudflare` are supported,
|
||||||
|
see [ACME Fields](/configuration/shared/tls#acme-fields)
|
||||||
|
and [DNS01 Challenge Fields](/configuration/shared/dns01_challenge/).
|
||||||
|
|
||||||
|
#### 1.5.0-beta.10
|
||||||
|
|
||||||
|
* Add `interrupt_exist_connections` option for `Selector` and `URLTest` outbounds **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Interrupt existing connections when the selected outbound has changed.
|
||||||
|
|
||||||
|
Only inbound connections are affected by this setting, internal connections will always be interrupted.
|
||||||
|
|
||||||
|
#### 1.4.3
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.5.0-beta.8
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.4.2
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.5.0-beta.6
|
||||||
|
|
||||||
|
* Fix compatibility issues with official Hysteria2 server and client
|
||||||
|
* Fixes and improvements
|
||||||
|
* Mark [deprecated features](/deprecated/)
|
||||||
|
|
||||||
|
#### 1.5.0-beta.3
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
* Updated Hysteria2 documentation **1**
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Added notes indicating compatibility issues with the official
|
||||||
|
Hysteria2 server and client when using `fastOpen=false` or UDP MTU >= 1200.
|
||||||
|
|
||||||
|
#### 1.5.0-beta.2
|
||||||
|
|
||||||
|
* Add hysteria2 protocol support **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
See [Hysteria2 inbound](/configuration/inbound/hysteria2/) and [Hysteria2 outbound](/configuration/outbound/hysteria2/)
|
||||||
|
|
||||||
|
For protocol description, please refer to [https://v2.hysteria.network](https://v2.hysteria.network)
|
||||||
|
|
||||||
|
#### 1.5.0-beta.1
|
||||||
|
|
||||||
|
* Add TLS [ECH server](/configuration/shared/tls/) support
|
||||||
|
* Improve TLS TCH client configuration
|
||||||
|
* Add TLS ECH key pair generator **1**
|
||||||
|
* Add TLS ECH support for QUIC based protocols **2**
|
||||||
|
* Add KDE support for the `set_system_proxy` option in HTTP inbound
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Command: `sing-box generate ech-keypair <plain_server_name> [--pq-signature-schemes-enabled]`
|
||||||
|
|
||||||
|
**2**:
|
||||||
|
|
||||||
|
All inbounds and outbounds are supported, including `Naiveproxy`, `Hysteria`, `TUIC` and `V2ray QUIC transport`.
|
||||||
|
|
||||||
#### 1.4.1
|
#### 1.4.1
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
@@ -17,12 +572,12 @@ Important changes since 1.3:
|
|||||||
|
|
||||||
*1*:
|
*1*:
|
||||||
|
|
||||||
See [TUIC inbound](/configuration/inbound/tuic)
|
See [TUIC inbound](/configuration/inbound/tuic/)
|
||||||
and [TUIC outbound](/configuration/outbound/tuic)
|
and [TUIC outbound](/configuration/outbound/tuic/)
|
||||||
|
|
||||||
**2**:
|
**2**:
|
||||||
|
|
||||||
This is the TUIC port of the [UDP over TCP protocol](/configuration/shared/udp-over-tcp), designed to provide a QUIC
|
This is the TUIC port of the [UDP over TCP protocol](/configuration/shared/udp-over-tcp/), designed to provide a QUIC
|
||||||
stream based UDP relay mode that TUIC does not provide. Since it is an add-on protocol, you will need to use sing-box or
|
stream based UDP relay mode that TUIC does not provide. Since it is an add-on protocol, you will need to use sing-box or
|
||||||
another program compatible with the protocol as a server.
|
another program compatible with the protocol as a server.
|
||||||
|
|
||||||
@@ -53,7 +608,7 @@ Requires sing-box to be compiled with Go 1.21.
|
|||||||
|
|
||||||
**1**:
|
**1**:
|
||||||
|
|
||||||
This is the TUIC port of the [UDP over TCP protocol](/configuration/shared/udp-over-tcp), designed to provide a QUIC
|
This is the TUIC port of the [UDP over TCP protocol](/configuration/shared/udp-over-tcp/), designed to provide a QUIC
|
||||||
stream based UDP relay mode that TUIC does not provide. Since it is an add-on protocol, you will need to use sing-box or
|
stream based UDP relay mode that TUIC does not provide. Since it is an add-on protocol, you will need to use sing-box or
|
||||||
another program compatible with the protocol as a server.
|
another program compatible with the protocol as a server.
|
||||||
|
|
||||||
@@ -91,8 +646,8 @@ Requires sing-box to be compiled with Go 1.21.
|
|||||||
|
|
||||||
*1*:
|
*1*:
|
||||||
|
|
||||||
See [TUIC inbound](/configuration/inbound/tuic)
|
See [TUIC inbound](/configuration/inbound/tuic/)
|
||||||
and [TUIC outbound](/configuration/outbound/tuic)
|
and [TUIC outbound](/configuration/outbound/tuic/)
|
||||||
|
|
||||||
#### 1.3.6
|
#### 1.3.6
|
||||||
|
|
||||||
@@ -101,7 +656,7 @@ and [TUIC outbound](/configuration/outbound/tuic)
|
|||||||
#### 1.3.5
|
#### 1.3.5
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
* Introducing our [Apple tvOS](/installation/clients/sft) client applications **1**
|
* Introducing our [Apple tvOS](/installation/clients/sft/) client applications **1**
|
||||||
* Add per app proxy and app installed/updated trigger support for Android client
|
* Add per app proxy and app installed/updated trigger support for Android client
|
||||||
* Add profile sharing support for Android/iOS/macOS clients
|
* Add profile sharing support for Android/iOS/macOS clients
|
||||||
|
|
||||||
@@ -128,7 +683,7 @@ downloaded through TestFlight.
|
|||||||
|
|
||||||
#### 1.3.1-beta.3
|
#### 1.3.1-beta.3
|
||||||
|
|
||||||
* Introducing our [new iOS](/installation/clients/sfi) and [macOS](/installation/clients/sfm) client applications **1**
|
* Introducing our [new iOS](/installation/clients/sfi/) and [macOS](/installation/clients/sfm/) client applications **1**
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
**1**:
|
**1**:
|
||||||
@@ -149,7 +704,7 @@ The old testflight link and app are no longer valid.
|
|||||||
|
|
||||||
Important changes since 1.2:
|
Important changes since 1.2:
|
||||||
|
|
||||||
* Add [FakeIP](/configuration/dns/fakeip) support **1**
|
* Add [FakeIP](/configuration/dns/fakeip/) support **1**
|
||||||
* Improve multiplex **2**
|
* Improve multiplex **2**
|
||||||
* Add [DNS reverse mapping](/configuration/dns#reverse_mapping) support
|
* Add [DNS reverse mapping](/configuration/dns#reverse_mapping) support
|
||||||
* Add `rewrite_ttl` DNS rule action
|
* Add `rewrite_ttl` DNS rule action
|
||||||
@@ -176,11 +731,11 @@ Important changes since 1.2:
|
|||||||
|
|
||||||
*1*:
|
*1*:
|
||||||
|
|
||||||
See [FAQ](/faq/fakeip) for more information.
|
See [FAQ](/faq/fakeip/) for more information.
|
||||||
|
|
||||||
*2*:
|
*2*:
|
||||||
|
|
||||||
Added new `h2mux` multiplex protocol and `padding` multiplex option, see [Multiplex](/configuration/shared/multiplex).
|
Added new `h2mux` multiplex protocol and `padding` multiplex option, see [Multiplex](/configuration/shared/multiplex/).
|
||||||
|
|
||||||
#### 1.3-rc2
|
#### 1.3-rc2
|
||||||
|
|
||||||
@@ -242,7 +797,7 @@ Improved performance and reduced memory usage.
|
|||||||
|
|
||||||
*1*:
|
*1*:
|
||||||
|
|
||||||
Added new `h2mux` multiplex protocol and `padding` multiplex option, see [Multiplex](/configuration/shared/multiplex).
|
Added new `h2mux` multiplex protocol and `padding` multiplex option, see [Multiplex](/configuration/shared/multiplex/).
|
||||||
|
|
||||||
#### 1.2.6
|
#### 1.2.6
|
||||||
|
|
||||||
@@ -294,25 +849,25 @@ This is an incompatible update for XUDP in VLESS if vision flow is enabled.
|
|||||||
#### 1.3-beta1
|
#### 1.3-beta1
|
||||||
|
|
||||||
* Add [DNS reverse mapping](/configuration/dns#reverse_mapping) support
|
* Add [DNS reverse mapping](/configuration/dns#reverse_mapping) support
|
||||||
* Add [L3 routing](/configuration/route/ip-rule) support **1**
|
* Add [L3 routing](/configuration/route/ip-rule/) support **1**
|
||||||
* Add `rewrite_ttl` DNS rule action
|
* Add `rewrite_ttl` DNS rule action
|
||||||
* Add [FakeIP](/configuration/dns/fakeip) support **2**
|
* Add [FakeIP](/configuration/dns/fakeip/) support **2**
|
||||||
* Add `store_fakeip` Clash API option
|
* Add `store_fakeip` Clash API option
|
||||||
* Add multi-peer support for [WireGuard](/configuration/outbound/wireguard#peers) outbound
|
* Add multi-peer support for [WireGuard](/configuration/outbound/wireguard#peers) outbound
|
||||||
* Add loopback detect
|
* Add loopback detect
|
||||||
|
|
||||||
*1*:
|
*1*:
|
||||||
|
|
||||||
It can currently be used to [route connections directly to WireGuard](/examples/wireguard-direct) or block connections
|
It can currently be used to [route connections directly to WireGuard](/examples/wireguard-direct/) or block connections
|
||||||
at the IP layer.
|
at the IP layer.
|
||||||
|
|
||||||
*2*:
|
*2*:
|
||||||
|
|
||||||
See [FAQ](/faq/fakeip) for more information.
|
See [FAQ](/faq/fakeip/) for more information.
|
||||||
|
|
||||||
#### 1.2.3
|
#### 1.2.3
|
||||||
|
|
||||||
* Introducing our [new Android client application](/installation/clients/sfa)
|
* Introducing our [new Android client application](/installation/clients/sfa/)
|
||||||
* Improve UDP domain destination NAT
|
* Improve UDP domain destination NAT
|
||||||
* Update reality protocol
|
* Update reality protocol
|
||||||
* Fix TTL calculation for DNS response
|
* Fix TTL calculation for DNS response
|
||||||
@@ -341,16 +896,16 @@ to `domain` rule.
|
|||||||
|
|
||||||
Important changes since 1.1:
|
Important changes since 1.1:
|
||||||
|
|
||||||
* Introducing our [new iOS client application](/installation/clients/sfi)
|
* Introducing our [new iOS client application](/installation/clients/sfi/)
|
||||||
* Introducing [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp)
|
* Introducing [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp/)
|
||||||
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound
|
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound
|
||||||
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
|
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
|
||||||
* Add [VLESS server](/configuration/inbound/vless) and [vision](/configuration/outbound/vless#flow) support
|
* Add [VLESS server](/configuration/inbound/vless/) and [vision](/configuration/outbound/vless#flow) support
|
||||||
* Add [reality TLS](/configuration/shared/tls) support
|
* Add [reality TLS](/configuration/shared/tls/) support
|
||||||
* Add [NTP service](/configuration/ntp)
|
* Add [NTP service](/configuration/ntp/)
|
||||||
* Add [DHCP DNS server](/configuration/dns/server) support
|
* Add [DHCP DNS server](/configuration/dns/server/) support
|
||||||
* Add SSH [host key validation](/configuration/outbound/ssh) support
|
* Add SSH [host key validation](/configuration/outbound/ssh/) support
|
||||||
* Add [query_type](/configuration/dns/rule) DNS rule item
|
* Add [query_type](/configuration/dns/rule/) DNS rule item
|
||||||
* Add fallback support for v2ray transport
|
* Add fallback support for v2ray transport
|
||||||
* Add custom TLS server support for http based v2ray transports
|
* Add custom TLS server support for http based v2ray transports
|
||||||
* Add health check support for http-based v2ray transports
|
* Add health check support for http-based v2ray transports
|
||||||
@@ -381,7 +936,7 @@ name.
|
|||||||
|
|
||||||
#### 1.2-beta9
|
#### 1.2-beta9
|
||||||
|
|
||||||
* Introducing the [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp)
|
* Introducing the [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp/)
|
||||||
* Add health check support for http-based v2ray transports
|
* Add health check support for http-based v2ray transports
|
||||||
* Remove length limit on short_id for reality TLS config
|
* Remove length limit on short_id for reality TLS config
|
||||||
* Fix bugs and update dependencies
|
* Fix bugs and update dependencies
|
||||||
@@ -398,7 +953,7 @@ name.
|
|||||||
|
|
||||||
#### 1.2-beta6
|
#### 1.2-beta6
|
||||||
|
|
||||||
* Introducing our [new iOS client application](/installation/clients/sfi)
|
* Introducing our [new iOS client application](/installation/clients/sfi/)
|
||||||
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound
|
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound
|
||||||
* Add custom TLS server support for http based v2ray transports
|
* Add custom TLS server support for http based v2ray transports
|
||||||
* Add generate commands
|
* Add generate commands
|
||||||
@@ -411,8 +966,8 @@ name.
|
|||||||
|
|
||||||
#### 1.2-beta5
|
#### 1.2-beta5
|
||||||
|
|
||||||
* Add [VLESS server](/configuration/inbound/vless) and [vision](/configuration/outbound/vless#flow) support
|
* Add [VLESS server](/configuration/inbound/vless/) and [vision](/configuration/outbound/vless#flow) support
|
||||||
* Add [reality TLS](/configuration/shared/tls) support
|
* Add [reality TLS](/configuration/shared/tls/) support
|
||||||
* Fix match private address
|
* Fix match private address
|
||||||
|
|
||||||
#### 1.1.6
|
#### 1.1.6
|
||||||
@@ -427,7 +982,7 @@ name.
|
|||||||
|
|
||||||
#### 1.2-beta4
|
#### 1.2-beta4
|
||||||
|
|
||||||
* Add [NTP service](/configuration/ntp)
|
* Add [NTP service](/configuration/ntp/)
|
||||||
* Add Add multiple server names and multi-user support for shadowtls
|
* Add Add multiple server names and multi-user support for shadowtls
|
||||||
* Add strict mode support for shadowtls v3
|
* Add strict mode support for shadowtls v3
|
||||||
* Add uTLS support for shadowtls v3
|
* Add uTLS support for shadowtls v3
|
||||||
@@ -447,9 +1002,9 @@ name.
|
|||||||
|
|
||||||
#### 1.2-beta1
|
#### 1.2-beta1
|
||||||
|
|
||||||
* Add [DHCP DNS server](/configuration/dns/server) support
|
* Add [DHCP DNS server](/configuration/dns/server/) support
|
||||||
* Add SSH [host key validation](/configuration/outbound/ssh) support
|
* Add SSH [host key validation](/configuration/outbound/ssh/) support
|
||||||
* Add [query_type](/configuration/dns/rule) DNS rule item
|
* Add [query_type](/configuration/dns/rule/) DNS rule item
|
||||||
* Add v2ray [user stats](/configuration/experimental#statsusers) api
|
* Add v2ray [user stats](/configuration/experimental#statsusers) api
|
||||||
* Add new clash DNS query api
|
* Add new clash DNS query api
|
||||||
* Improve vmess request
|
* Improve vmess request
|
||||||
@@ -678,7 +1233,7 @@ and [ShadowTLS outbound](/configuration/outbound/shadowtls#version)
|
|||||||
|
|
||||||
#### 1.1-beta6
|
#### 1.1-beta6
|
||||||
|
|
||||||
* Add [URLTest outbound](/configuration/outbound/urltest)
|
* Add [URLTest outbound](/configuration/outbound/urltest/)
|
||||||
* Fix bugs in 1.1-beta5
|
* Fix bugs in 1.1-beta5
|
||||||
|
|
||||||
#### 1.1-beta5
|
#### 1.1-beta5
|
||||||
@@ -710,8 +1265,8 @@ The default tun stack is changed to system.
|
|||||||
#### 1.1-beta4
|
#### 1.1-beta4
|
||||||
|
|
||||||
* Add internal simple-obfs and v2ray-plugin [Shadowsocks plugins](/configuration/outbound/shadowsocks#plugin)
|
* Add internal simple-obfs and v2ray-plugin [Shadowsocks plugins](/configuration/outbound/shadowsocks#plugin)
|
||||||
* Add [ShadowsocksR outbound](/configuration/outbound/shadowsocksr)
|
* Add [ShadowsocksR outbound](/configuration/outbound/shadowsocksr/)
|
||||||
* Add [VLESS outbound and XUDP](/configuration/outbound/vless)
|
* Add [VLESS outbound and XUDP](/configuration/outbound/vless/)
|
||||||
* Skip wait for hysteria tcp handshake response
|
* Skip wait for hysteria tcp handshake response
|
||||||
* Fix socks4 client
|
* Fix socks4 client
|
||||||
* Fix hysteria inbound
|
* Fix hysteria inbound
|
||||||
@@ -738,7 +1293,7 @@ The default tun stack is changed to system.
|
|||||||
*1*:
|
*1*:
|
||||||
|
|
||||||
Switching modes using the Clash API, and `store-selected` are now supported,
|
Switching modes using the Clash API, and `store-selected` are now supported,
|
||||||
see [Experimental](/configuration/experimental).
|
see [Experimental](/configuration/experimental/).
|
||||||
|
|
||||||
*2*:
|
*2*:
|
||||||
|
|
||||||
@@ -819,15 +1374,15 @@ and [Listen Fields](/configuration/shared/listen#udp_fragment).
|
|||||||
* Fix write trojan udp
|
* Fix write trojan udp
|
||||||
* Fix DNS routing
|
* Fix DNS routing
|
||||||
* Add attribute support for geosite
|
* Add attribute support for geosite
|
||||||
* Update documentation for [Dial Fields](/configuration/shared/dial)
|
* Update documentation for [Dial Fields](/configuration/shared/dial/)
|
||||||
|
|
||||||
#### 1.0-beta3
|
#### 1.0-beta3
|
||||||
|
|
||||||
* Add [chained inbound](/configuration/shared/listen#detour) support
|
* Add [chained inbound](/configuration/shared/listen#detour) support
|
||||||
* Add process_path rule item
|
* Add process_path rule item
|
||||||
* Add macOS redirect support
|
* Add macOS redirect support
|
||||||
* Add ShadowTLS [Inbound](/configuration/inbound/shadowtls), [Outbound](/configuration/outbound/shadowtls)
|
* Add ShadowTLS [Inbound](/configuration/inbound/shadowtls/), [Outbound](/configuration/outbound/shadowtls/)
|
||||||
and [Examples](/examples/shadowtls)
|
and [Examples](/examples/shadowtls/)
|
||||||
* Fix search android package in non-owner users
|
* Fix search android package in non-owner users
|
||||||
* Fix socksaddr type condition
|
* Fix socksaddr type condition
|
||||||
* Fix smux session status
|
* Fix smux session status
|
||||||
@@ -871,7 +1426,7 @@ and [Listen Fields](/configuration/shared/listen#udp_fragment).
|
|||||||
|
|
||||||
##### 2022/08/23
|
##### 2022/08/23
|
||||||
|
|
||||||
* Add [V2Ray Transport](/configuration/shared/v2ray-transport) support for VMess and Trojan
|
* Add [V2Ray Transport](/configuration/shared/v2ray-transport/) support for VMess and Trojan
|
||||||
* Allow plain http request in Naive inbound (It can now be used with nginx)
|
* Allow plain http request in Naive inbound (It can now be used with nginx)
|
||||||
* Add proxy protocol support
|
* Add proxy protocol support
|
||||||
* Free memory after start
|
* Free memory after start
|
||||||
@@ -880,13 +1435,13 @@ and [Listen Fields](/configuration/shared/listen#udp_fragment).
|
|||||||
|
|
||||||
##### 2022/08/22
|
##### 2022/08/22
|
||||||
|
|
||||||
* Add strategy setting for each [DNS server](/configuration/dns/server)
|
* Add strategy setting for each [DNS server](/configuration/dns/server/)
|
||||||
* Add bind address to outbound options
|
* Add bind address to outbound options
|
||||||
|
|
||||||
##### 2022/08/21
|
##### 2022/08/21
|
||||||
|
|
||||||
* Add [Tor outbound](/configuration/outbound/tor)
|
* Add [Tor outbound](/configuration/outbound/tor/)
|
||||||
* Add [SSH outbound](/configuration/outbound/ssh)
|
* Add [SSH outbound](/configuration/outbound/ssh/)
|
||||||
|
|
||||||
##### 2022/08/20
|
##### 2022/08/20
|
||||||
|
|
||||||
@@ -900,8 +1455,8 @@ and [Listen Fields](/configuration/shared/listen#udp_fragment).
|
|||||||
|
|
||||||
##### 2022/08/19
|
##### 2022/08/19
|
||||||
|
|
||||||
* Add Hysteria [Inbound](/configuration/inbound/hysteria) and [Outbund](/configuration/outbound/hysteria)
|
* Add Hysteria [Inbound](/configuration/inbound/hysteria/) and [Outbund](/configuration/outbound/hysteria/)
|
||||||
* Add [ACME TLS certificate issuer](/configuration/shared/tls)
|
* Add [ACME TLS certificate issuer](/configuration/shared/tls/)
|
||||||
* Allow read config from stdin (-c stdin)
|
* Allow read config from stdin (-c stdin)
|
||||||
* Update gVisor to 20220815.0
|
* Update gVisor to 20220815.0
|
||||||
|
|
||||||
@@ -919,11 +1474,11 @@ and [Listen Fields](/configuration/shared/listen#udp_fragment).
|
|||||||
##### 2022/08/16
|
##### 2022/08/16
|
||||||
|
|
||||||
* Add ip_version (route/dns) rule item
|
* Add ip_version (route/dns) rule item
|
||||||
* Add [WireGuard](/configuration/outbound/wireguard) outbound
|
* Add [WireGuard](/configuration/outbound/wireguard/) outbound
|
||||||
|
|
||||||
##### 2022/08/15
|
##### 2022/08/15
|
||||||
|
|
||||||
* Add uid, android user and package rules support in [Tun](/configuration/inbound/tun) routing.
|
* Add uid, android user and package rules support in [Tun](/configuration/inbound/tun/) routing.
|
||||||
|
|
||||||
##### 2022/08/13
|
##### 2022/08/13
|
||||||
|
|
||||||
@@ -932,15 +1487,15 @@ and [Listen Fields](/configuration/shared/listen#udp_fragment).
|
|||||||
##### 2022/08/12
|
##### 2022/08/12
|
||||||
|
|
||||||
* Performance improvements
|
* Performance improvements
|
||||||
* Add UoT option for [SOCKS](/configuration/outbound/socks) outbound
|
* Add UoT option for [SOCKS](/configuration/outbound/socks/) outbound
|
||||||
|
|
||||||
##### 2022/08/11
|
##### 2022/08/11
|
||||||
|
|
||||||
* Add UoT option for [Shadowsocks](/configuration/outbound/shadowsocks) outbound, UoT support for all inbounds
|
* Add UoT option for [Shadowsocks](/configuration/outbound/shadowsocks/) outbound, UoT support for all inbounds
|
||||||
|
|
||||||
##### 2022/08/10
|
##### 2022/08/10
|
||||||
|
|
||||||
* Add full-featured [Naive](/configuration/inbound/naive) inbound
|
* Add full-featured [Naive](/configuration/inbound/naive/) inbound
|
||||||
* Fix default dns server option [#9] by iKirby
|
* Fix default dns server option [#9] by iKirby
|
||||||
|
|
||||||
##### 2022/08/09
|
##### 2022/08/09
|
||||||
|
|||||||
64
docs/clients/android/features.md
Normal file
64
docs/clients/android/features.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# :material-decagram: Features
|
||||||
|
|
||||||
|
#### UI options
|
||||||
|
|
||||||
|
* Display realtime network speed in the notification
|
||||||
|
|
||||||
|
#### Service
|
||||||
|
|
||||||
|
SFA allows you to run sing-box through ForegroundService or VpnService (when TUN is required).
|
||||||
|
|
||||||
|
#### TUN
|
||||||
|
|
||||||
|
SFA provides an unprivileged TUN implementation through Android VpnService.
|
||||||
|
|
||||||
|
| TUN inbound option | Available | Note |
|
||||||
|
|-------------------------------|------------------|--------------------|
|
||||||
|
| `interface_name` | :material-close: | Managed by Android |
|
||||||
|
| `inet4_address` | :material-check: | / |
|
||||||
|
| `inet6_address` | :material-check: | / |
|
||||||
|
| `mtu` | :material-check: | / |
|
||||||
|
| `auto_route` | :material-check: | / |
|
||||||
|
| `strict_route` | :material-close: | Not implemented |
|
||||||
|
| `inet4_route_address` | :material-check: | / |
|
||||||
|
| `inet6_route_address` | :material-check: | / |
|
||||||
|
| `inet4_route_exclude_address` | :material-check: | / |
|
||||||
|
| `inet6_route_exclude_address` | :material-check: | / |
|
||||||
|
| `endpoint_independent_nat` | :material-check: | / |
|
||||||
|
| `stack` | :material-check: | / |
|
||||||
|
| `include_interface` | :material-close: | No permission |
|
||||||
|
| `exclude_interface` | :material-close: | No permission |
|
||||||
|
| `include_uid` | :material-close: | No permission |
|
||||||
|
| `exclude_uid` | :material-close: | No permission |
|
||||||
|
| `include_android_user` | :material-close: | No permission |
|
||||||
|
| `include_package` | :material-check: | / |
|
||||||
|
| `exclude_package` | :material-check: | / |
|
||||||
|
| `platform` | :material-check: | / |
|
||||||
|
|
||||||
|
| Route/DNS rule option | Available | Note |
|
||||||
|
|-----------------------|------------------|-----------------------------------|
|
||||||
|
| `process_name` | :material-close: | No permission |
|
||||||
|
| `process_path` | :material-close: | No permission |
|
||||||
|
| `package_name` | :material-check: | / |
|
||||||
|
| `user` | :material-close: | Use `package_name` instead |
|
||||||
|
| `user_id` | :material-close: | Use `package_name` instead |
|
||||||
|
| `wifi_ssid` | :material-check: | Fine location permission required |
|
||||||
|
| `wifi_bssid` | :material-check: | Fine location permission required |
|
||||||
|
|
||||||
|
### Override
|
||||||
|
|
||||||
|
Overrides profile configuration items with platform-specific values.
|
||||||
|
|
||||||
|
#### Per-app proxy
|
||||||
|
|
||||||
|
SFA allows you to select a list of Android apps that require proxying or bypassing in the graphical interface to
|
||||||
|
override the `include_package` and `exclude_package` configuration items.
|
||||||
|
|
||||||
|
In particular, the selector also provides the “China apps” scanning feature, providing Chinese users with an excellent
|
||||||
|
experience to bypass apps that do not require a proxy. Specifically, by scanning China application or SDK
|
||||||
|
characteristics through dex class path and other means, there will be almost no missed reports.
|
||||||
|
|
||||||
|
### Chore
|
||||||
|
|
||||||
|
* The working directory is located at `/sdcard/Android/data/io.nekohasekai.sfa/files` (External files directory)
|
||||||
|
* Crash logs is located in `$working_directory/stderr.log`
|
||||||
22
docs/clients/android/index.md
Normal file
22
docs/clients/android/index.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
icon: material/android
|
||||||
|
---
|
||||||
|
|
||||||
|
# sing-box for Android
|
||||||
|
|
||||||
|
SFA allows users to manage and run local or remote sing-box configuration files, and provides
|
||||||
|
platform-specific function implementation, such as TUN transparent proxy implementation.
|
||||||
|
|
||||||
|
## :material-graph: Requirements
|
||||||
|
|
||||||
|
* Android 5.0+
|
||||||
|
|
||||||
|
## :material-download: Download
|
||||||
|
|
||||||
|
* [Play Store](https://play.google.com/store/apps/details?id=io.nekohasekai.sfa)
|
||||||
|
* [Play Store (Beta)](https://play.google.com/apps/testing/io.nekohasekai.sfa)
|
||||||
|
* [GitHub Releases](https://github.com/SagerNet/sing-box/releases)
|
||||||
|
|
||||||
|
## :material-source-repository: Source code
|
||||||
|
|
||||||
|
* [GitHub](https://github.com/SagerNet/sing-box-for-android)
|
||||||
52
docs/clients/apple/features.md
Normal file
52
docs/clients/apple/features.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# :material-decagram: Features
|
||||||
|
|
||||||
|
#### UI options
|
||||||
|
|
||||||
|
* Always On
|
||||||
|
* Include All Networks (Proxy traffic for LAN and cellular services)
|
||||||
|
* (Apple tvOS) Import profile from iPhone/iPad
|
||||||
|
|
||||||
|
#### Service
|
||||||
|
|
||||||
|
SFI/SFM/SFT allows you to run sing-box through NetworkExtension with Application Extension or System Extension.
|
||||||
|
|
||||||
|
#### TUN
|
||||||
|
|
||||||
|
SFI/SFM/SFT provides an unprivileged TUN implementation through NetworkExtension.
|
||||||
|
|
||||||
|
| TUN inbound option | Available | Note |
|
||||||
|
|-------------------------------|-----------|-------------------|
|
||||||
|
| `interface_name` | ✖️ | Managed by Darwin |
|
||||||
|
| `inet4_address` | ✔️ | / |
|
||||||
|
| `inet6_address` | ✔️ | / |
|
||||||
|
| `mtu` | ✔️ | / |
|
||||||
|
| `auto_route` | ✔️ | / |
|
||||||
|
| `strict_route` | ✖️ | Not implemented |
|
||||||
|
| `inet4_route_address` | ✔️ | / |
|
||||||
|
| `inet6_route_address` | ✔️ | / |
|
||||||
|
| `inet4_route_exclude_address` | ✔️ | / |
|
||||||
|
| `inet6_route_exclude_address` | ✔️ | / |
|
||||||
|
| `endpoint_independent_nat` | ✔️ | / |
|
||||||
|
| `stack` | ✔️ | / |
|
||||||
|
| `include_interface` | ✖️ | Not implemented |
|
||||||
|
| `exclude_interface` | ✖️ | Not implemented |
|
||||||
|
| `include_uid` | ✖️ | Not implemented |
|
||||||
|
| `exclude_uid` | ✖️ | Not implemented |
|
||||||
|
| `include_android_user` | ✖️ | Not implemented |
|
||||||
|
| `include_package` | ✖️ | Not implemented |
|
||||||
|
| `exclude_package` | ✖️ | Not implemented |
|
||||||
|
| `platform` | ✔️ | / |
|
||||||
|
|
||||||
|
| Route/DNS rule option | Available | Note |
|
||||||
|
|-----------------------|------------------|-----------------------|
|
||||||
|
| `process_name` | :material-close: | No permission |
|
||||||
|
| `process_path` | :material-close: | No permission |
|
||||||
|
| `package_name` | :material-close: | / |
|
||||||
|
| `user` | :material-close: | No permission |
|
||||||
|
| `user_id` | :material-close: | No permission |
|
||||||
|
| `wifi_ssid` | :material-alert: | Only supported on iOS |
|
||||||
|
| `wifi_bssid` | :material-alert: | Only supported on iOS |
|
||||||
|
|
||||||
|
### Chore
|
||||||
|
|
||||||
|
* Crash logs is located in `Settings` -> `View Service Log`
|
||||||
32
docs/clients/apple/index.md
Normal file
32
docs/clients/apple/index.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
icon: material/apple
|
||||||
|
---
|
||||||
|
|
||||||
|
# sing-box for Apple platforms
|
||||||
|
|
||||||
|
SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides
|
||||||
|
platform-specific function implementation, such as TUN transparent proxy implementation.
|
||||||
|
|
||||||
|
## :material-graph: Requirements
|
||||||
|
|
||||||
|
* iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+
|
||||||
|
* An Apple account outside of mainland China
|
||||||
|
|
||||||
|
## :material-download: Download
|
||||||
|
|
||||||
|
* [App Store](https://apps.apple.com/us/app/sing-box/id6451272673)
|
||||||
|
* [TestFlight (Beta)](https://testflight.apple.com/join/AcqO44FH)
|
||||||
|
|
||||||
|
## :material-file-download: Download (macOS standalone version)
|
||||||
|
|
||||||
|
* [Homebrew Cask](https://formulae.brew.sh/cask/sfm)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install sfm
|
||||||
|
```
|
||||||
|
|
||||||
|
* [GitHub Releases](https://github.com/SagerNet/sing-box/releases)
|
||||||
|
|
||||||
|
## :material-source-repository: Source code
|
||||||
|
|
||||||
|
* [GitHub](https://github.com/SagerNet/sing-box-for-apple)
|
||||||
63
docs/clients/general.md
Normal file
63
docs/clients/general.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
icon: material/pencil-ruler
|
||||||
|
---
|
||||||
|
|
||||||
|
# General
|
||||||
|
|
||||||
|
Describes and explains the functions implemented uniformly by sing-box graphical clients.
|
||||||
|
|
||||||
|
### Profile
|
||||||
|
|
||||||
|
Profile describes a sing-box configuration file and its state.
|
||||||
|
|
||||||
|
#### Local
|
||||||
|
|
||||||
|
* Local Profile represents a local sing-box configuration with minimal state
|
||||||
|
* The graphical client must provide an editor to modify configuration content
|
||||||
|
|
||||||
|
#### iCloud (on iOS and macOS)
|
||||||
|
|
||||||
|
* iCloud Profile represents a remote sing-box configuration with iCloud as the update source
|
||||||
|
* The configuration file is stored in the sing-box folder under iCloud
|
||||||
|
* The graphical client must provide an editor to modify configuration content
|
||||||
|
|
||||||
|
#### Remote
|
||||||
|
|
||||||
|
* Remote Profile represents a remote sing-box configuration with a URL as the update source.
|
||||||
|
* The graphical client should provide a configuration content viewer
|
||||||
|
* The graphical client must implement automatic profile update (default interval is 60 minutes) and HTTP Basic
|
||||||
|
authorization.
|
||||||
|
|
||||||
|
At the same time, the graphical client must provide support for importing remote profiles
|
||||||
|
through a specific URL Scheme. The URL is defined as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
sing-box://import-remote-profile?url=urlEncodedURL#urlEncodedName
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dashboard
|
||||||
|
|
||||||
|
While the sing-box service is running, the graphical client should provide a Dashboard interface to manage the service.
|
||||||
|
|
||||||
|
#### Status
|
||||||
|
|
||||||
|
Dashboard should display status information such as memory, connection, and traffic.
|
||||||
|
|
||||||
|
#### Mode
|
||||||
|
|
||||||
|
Dashboard should provide a Mode selector for switching when the configuration uses at least two `clash_mode` values.
|
||||||
|
|
||||||
|
#### Groups
|
||||||
|
|
||||||
|
When the configuration includes group outbounds (specifically, Selector or URLTest),
|
||||||
|
the dashboard should provide a Group selector for status display or switching.
|
||||||
|
|
||||||
|
### Chore
|
||||||
|
|
||||||
|
#### Core
|
||||||
|
|
||||||
|
Graphical clients should provide a Core region:
|
||||||
|
|
||||||
|
* Display the current sing-box version
|
||||||
|
* Provides a button to clean the working directory
|
||||||
|
* Provides a memory limiter switch
|
||||||
13
docs/clients/index.md
Normal file
13
docs/clients/index.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# :material-cellphone-link: Graphical Clients
|
||||||
|
|
||||||
|
Maintained by Project S to provide a unified experience and platform-specific functionality.
|
||||||
|
|
||||||
|
| Platform | Client |
|
||||||
|
|---------------------------------------|------------------------------------------|
|
||||||
|
| :material-android: Android | [sing-box for Android](./android/) |
|
||||||
|
| :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple/) |
|
||||||
|
| :material-laptop: Desktop | Working in progress |
|
||||||
|
|
||||||
|
Some third-party projects that claim to use sing-box or use sing-box as a selling point are not listed here. The core
|
||||||
|
motivation of the maintainers of such projects is to acquire more users, and even though they provide friendly VPN
|
||||||
|
client features, the code is usually of poor quality and contains ads.
|
||||||
12
docs/clients/index.zh.md
Normal file
12
docs/clients/index.zh.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# :material-cellphone-link: 图形界面客户端
|
||||||
|
|
||||||
|
由 Project S 维护,提供统一的体验与平台特定的功能。
|
||||||
|
|
||||||
|
| 平台 | 客户端 |
|
||||||
|
|---------------------------------------|-----------------------------------------|
|
||||||
|
| :material-android: Android | [sing-box for Android](./android/) |
|
||||||
|
| :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple/) |
|
||||||
|
| :material-laptop: Desktop | 施工中 |
|
||||||
|
|
||||||
|
此处没有列出一些声称使用或以 sing-box 为卖点的第三方项目。此类项目维护者的动机是获得更多用户,即使它们提供友好的商业
|
||||||
|
VPN 客户端功能, 但代码质量很差且包含广告。
|
||||||
8
docs/clients/privacy.md
Normal file
8
docs/clients/privacy.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
icon: material/security
|
||||||
|
---
|
||||||
|
|
||||||
|
# Privacy policy
|
||||||
|
|
||||||
|
sing-box and official graphics clients do not collect or share personal data,
|
||||||
|
and the data generated by the software is always on your device.
|
||||||
@@ -23,9 +23,9 @@
|
|||||||
|
|
||||||
| Key | Format |
|
| Key | Format |
|
||||||
|----------|--------------------------------|
|
|----------|--------------------------------|
|
||||||
| `server` | List of [DNS Server](./server) |
|
| `server` | List of [DNS Server](./server/) |
|
||||||
| `rules` | List of [DNS Rule](./rule) |
|
| `rules` | List of [DNS Rule](./rule/) |
|
||||||
| `fakeip` | [FakeIP](./fakeip) |
|
| `fakeip` | [FakeIP](./fakeip/) |
|
||||||
|
|
||||||
#### final
|
#### final
|
||||||
|
|
||||||
@@ -62,4 +62,4 @@ problematic in environments such as macOS, where DNS is proxied and cached by th
|
|||||||
|
|
||||||
#### fakeip
|
#### fakeip
|
||||||
|
|
||||||
[FakeIP](./fakeip) settings.
|
[FakeIP](./fakeip/) settings.
|
||||||
|
|||||||
@@ -21,10 +21,10 @@
|
|||||||
|
|
||||||
### 字段
|
### 字段
|
||||||
|
|
||||||
| 键 | 格式 |
|
| 键 | 格式 |
|
||||||
|----------|------------------------|
|
|----------|-------------------------|
|
||||||
| `server` | 一组 [DNS 服务器](./server) |
|
| `server` | 一组 [DNS 服务器](./server/) |
|
||||||
| `rules` | 一组 [DNS 规则](./rule) |
|
| `rules` | 一组 [DNS 规则](./rule/) |
|
||||||
|
|
||||||
#### final
|
#### final
|
||||||
|
|
||||||
@@ -60,4 +60,4 @@
|
|||||||
|
|
||||||
#### fakeip
|
#### fakeip
|
||||||
|
|
||||||
[FakeIP](./fakeip) 设置。
|
[FakeIP](./fakeip/) 设置。
|
||||||
|
|||||||
@@ -79,6 +79,12 @@
|
|||||||
1000
|
1000
|
||||||
],
|
],
|
||||||
"clash_mode": "direct",
|
"clash_mode": "direct",
|
||||||
|
"wifi_ssid": [
|
||||||
|
"My WIFI"
|
||||||
|
],
|
||||||
|
"wifi_bssid": [
|
||||||
|
"00:00:00:00:00:00"
|
||||||
|
],
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"outbound": [
|
"outbound": [
|
||||||
"direct"
|
"direct"
|
||||||
@@ -118,7 +124,7 @@
|
|||||||
|
|
||||||
#### inbound
|
#### inbound
|
||||||
|
|
||||||
Tags of [Inbound](/configuration/inbound).
|
Tags of [Inbound](/configuration/inbound/).
|
||||||
|
|
||||||
#### ip_version
|
#### ip_version
|
||||||
|
|
||||||
@@ -188,7 +194,7 @@ Match port range.
|
|||||||
|
|
||||||
#### process_name
|
#### process_name
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported on Linux, Windows, and macOS.
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
@@ -196,7 +202,7 @@ Match process name.
|
|||||||
|
|
||||||
#### process_path
|
#### process_path
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported on Linux, Windows, and macOS.
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
@@ -208,7 +214,7 @@ Match android package name.
|
|||||||
|
|
||||||
#### user
|
#### user
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported on Linux.
|
Only supported on Linux.
|
||||||
|
|
||||||
@@ -216,7 +222,7 @@ Match user name.
|
|||||||
|
|
||||||
#### user_id
|
#### user_id
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported on Linux.
|
Only supported on Linux.
|
||||||
|
|
||||||
@@ -226,6 +232,24 @@ Match user id.
|
|||||||
|
|
||||||
Match Clash mode.
|
Match Clash mode.
|
||||||
|
|
||||||
|
#### wifi_ssid
|
||||||
|
|
||||||
|
<!-- md:version 1.7.0-beta.4 -->
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported in graphical clients on Android and iOS.
|
||||||
|
|
||||||
|
Match WiFi SSID.
|
||||||
|
|
||||||
|
#### wifi_bssid
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported in graphical clients on Android and iOS.
|
||||||
|
|
||||||
|
Match WiFi BSSID.
|
||||||
|
|
||||||
#### invert
|
#### invert
|
||||||
|
|
||||||
Invert match result.
|
Invert match result.
|
||||||
|
|||||||
@@ -78,6 +78,12 @@
|
|||||||
1000
|
1000
|
||||||
],
|
],
|
||||||
"clash_mode": "direct",
|
"clash_mode": "direct",
|
||||||
|
"wifi_ssid": [
|
||||||
|
"My WIFI"
|
||||||
|
],
|
||||||
|
"wifi_bssid": [
|
||||||
|
"00:00:00:00:00:00"
|
||||||
|
],
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"outbound": [
|
"outbound": [
|
||||||
"direct"
|
"direct"
|
||||||
@@ -115,7 +121,7 @@
|
|||||||
|
|
||||||
#### inbound
|
#### inbound
|
||||||
|
|
||||||
[入站](/zh/configuration/inbound) 标签.
|
[入站](/zh/configuration/inbound/) 标签.
|
||||||
|
|
||||||
#### ip_version
|
#### ip_version
|
||||||
|
|
||||||
@@ -185,7 +191,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
#### process_name
|
#### process_name
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
仅支持 Linux、Windows 和 macOS.
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
@@ -193,7 +199,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
#### process_path
|
#### process_path
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
仅支持 Linux、Windows 和 macOS.
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
@@ -205,7 +211,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
#### user
|
#### user
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
仅支持 Linux。
|
仅支持 Linux。
|
||||||
|
|
||||||
@@ -213,7 +219,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
#### user_id
|
#### user_id
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
仅支持 Linux。
|
仅支持 Linux。
|
||||||
|
|
||||||
@@ -223,6 +229,22 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
匹配 Clash 模式。
|
匹配 Clash 模式。
|
||||||
|
|
||||||
|
#### wifi_ssid
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅在 Android 与 iOS 的图形客户端中支持。
|
||||||
|
|
||||||
|
匹配 WiFi SSID。
|
||||||
|
|
||||||
|
#### wifi_bssid
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅在 Android 与 iOS 的图形客户端中支持。
|
||||||
|
|
||||||
|
匹配 WiFi BSSID。
|
||||||
|
|
||||||
#### invert
|
#### invert
|
||||||
|
|
||||||
反选匹配结果。
|
反选匹配结果。
|
||||||
|
|||||||
@@ -30,18 +30,18 @@ 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` |
|
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
|
||||||
| [FakeIP](/configuration/dns/fakeip) | `fakeip` |
|
| [FakeIP](/configuration/dns/fakeip/) | `fakeip` |
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ The address of the dns server.
|
|||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
QUIC and HTTP3 transport is not included by default, see [Installation](/#installation).
|
QUIC and HTTP3 transport is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
!!! info ""
|
!!! info ""
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ The address of the dns server.
|
|||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
DHCP transport is not included by default, see [Installation](/#installation).
|
DHCP transport is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
| RCode | Description |
|
| RCode | Description |
|
||||||
|-------------------|-----------------------|
|
|-------------------|-----------------------|
|
||||||
|
|||||||
@@ -30,18 +30,18 @@ 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` |
|
| `DHCP` | `dhcp://auto` 或 `dhcp://en0` |
|
||||||
| [FakeIP](/configuration/dns/fakeip) | `fakeip` |
|
| [FakeIP](/configuration/dns/fakeip/) | `fakeip` |
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
|
|||||||
@@ -44,9 +44,9 @@
|
|||||||
|
|
||||||
### Clash API Fields
|
### Clash API Fields
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
Clash API is not included by default, see [Installation](/#installation).
|
Clash API is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
#### external_controller
|
#### external_controller
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ ALWAYS set a secret if RESTful API is listening on 0.0.0.0
|
|||||||
|
|
||||||
#### default_mode
|
#### default_mode
|
||||||
|
|
||||||
Default mode in clash, `rule` will be used if empty.
|
Default mode in clash, `Rule` will be used if empty.
|
||||||
|
|
||||||
This setting has no direct effect, but can be used in routing and DNS rules via the `clash_mode` rule item.
|
This setting has no direct effect, but can be used in routing and DNS rules via the `clash_mode` rule item.
|
||||||
|
|
||||||
@@ -110,9 +110,9 @@ If not empty, `store_selected` will use a separate store keyed by it.
|
|||||||
|
|
||||||
### V2Ray API Fields
|
### V2Ray API Fields
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
V2Ray API is not included by default, see [Installation](/#installation).
|
V2Ray API is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
#### listen
|
#### listen
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
### Clash API 字段
|
### Clash API 字段
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
默认安装不包含 Clash API,参阅 [安装](/zh/#_2)。
|
默认安装不包含 Clash API,参阅 [安装](/zh/#_2)。
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ RESTful API 的密钥(可选)
|
|||||||
|
|
||||||
#### default_mode
|
#### default_mode
|
||||||
|
|
||||||
Clash 中的默认模式,默认使用 `rule`。
|
Clash 中的默认模式,默认使用 `Rule`。
|
||||||
|
|
||||||
此设置没有直接影响,但可以通过 `clash_mode` 规则项在路由和 DNS 规则中使用。
|
此设置没有直接影响,但可以通过 `clash_mode` 规则项在路由和 DNS 规则中使用。
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ Clash 中的默认模式,默认使用 `rule`。
|
|||||||
|
|
||||||
### V2Ray API 字段
|
### V2Ray API 字段
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
默认安装不包含 V2Ray API,参阅 [安装](/zh/#_2)。
|
默认安装不包含 V2Ray API,参阅 [安装](/zh/#_2)。
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
### Listen Fields
|
### Listen Fields
|
||||||
|
|
||||||
See [Listen Fields](/configuration/shared/listen) for details.
|
See [Listen Fields](/configuration/shared/listen/) for details.
|
||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
### Listen Fields
|
### Listen Fields
|
||||||
|
|
||||||
See [Listen Fields](/configuration/shared/listen) for details.
|
See [Listen Fields](/configuration/shared/listen/) for details.
|
||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ No authentication required if empty.
|
|||||||
|
|
||||||
#### set_system_proxy
|
#### set_system_proxy
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported on Linux, Android, Windows, and macOS.
|
Only supported on Linux, Android, Windows, and macOS.
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ HTTP 用户
|
|||||||
|
|
||||||
#### set_system_proxy
|
#### set_system_proxy
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
仅支持 Linux、Android、Windows 和 macOS。
|
仅支持 Linux、Android、Windows 和 macOS。
|
||||||
|
|
||||||
|
|||||||
@@ -31,11 +31,11 @@
|
|||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
QUIC, which is required by hysteria is not included by default, see [Installation](/#installation).
|
QUIC, which is required by hysteria is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
### Listen Fields
|
### Listen Fields
|
||||||
|
|
||||||
See [Listen Fields](/configuration/shared/listen) for details.
|
See [Listen Fields](/configuration/shared/listen/) for details.
|
||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
|
|||||||
97
docs/configuration/inbound/hysteria2.md
Normal file
97
docs/configuration/inbound/hysteria2.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "hysteria2",
|
||||||
|
"tag": "hy2-in",
|
||||||
|
...
|
||||||
|
// Listen Fields
|
||||||
|
|
||||||
|
"up_mbps": 100,
|
||||||
|
"down_mbps": 100,
|
||||||
|
"obfs": {
|
||||||
|
"type": "salamander",
|
||||||
|
"password": "cry_me_a_r1ver"
|
||||||
|
},
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "tobyxdd",
|
||||||
|
"password": "goofy_ahh_password"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ignore_client_bandwidth": false,
|
||||||
|
"tls": {},
|
||||||
|
"masquerade": "",
|
||||||
|
"brutal_debug": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
QUIC, which is required by Hysteria2 is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
|
!!! warning "Difference from official Hysteria2"
|
||||||
|
|
||||||
|
The official program supports an authentication method called **userpass**,
|
||||||
|
which essentially uses a combination of `<username>:<password>` as the actual password,
|
||||||
|
while sing-box does not provide this alias.
|
||||||
|
To use sing-box with the official program, you need to fill in that combination as the actual password.
|
||||||
|
|
||||||
|
### Listen Fields
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen/) for details.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### up_mbps, down_mbps
|
||||||
|
|
||||||
|
Max bandwidth, in Mbps.
|
||||||
|
|
||||||
|
Not limited if empty.
|
||||||
|
|
||||||
|
Conflict with `ignore_client_bandwidth`.
|
||||||
|
|
||||||
|
#### obfs.type
|
||||||
|
|
||||||
|
QUIC traffic obfuscator type, only available with `salamander`.
|
||||||
|
|
||||||
|
Disabled if empty.
|
||||||
|
|
||||||
|
#### obfs.password
|
||||||
|
|
||||||
|
QUIC traffic obfuscator password.
|
||||||
|
|
||||||
|
#### users
|
||||||
|
|
||||||
|
Hysteria2 users
|
||||||
|
|
||||||
|
#### users.password
|
||||||
|
|
||||||
|
Authentication password
|
||||||
|
|
||||||
|
#### ignore_client_bandwidth
|
||||||
|
|
||||||
|
Commands the client to use the BBR flow control algorithm instead of Hysteria CC.
|
||||||
|
|
||||||
|
Conflict with `up_mbps` and `down_mbps`.
|
||||||
|
|
||||||
|
#### tls
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||||
|
|
||||||
|
#### masquerade
|
||||||
|
|
||||||
|
HTTP3 server behavior when authentication fails.
|
||||||
|
|
||||||
|
| Scheme | Example | Description |
|
||||||
|
|--------------|-------------------------|--------------------|
|
||||||
|
| `file` | `file:///var/www` | As a file server |
|
||||||
|
| `http/https` | `http://127.0.0.1:8080` | As a reverse proxy |
|
||||||
|
|
||||||
|
A 404 page will be returned if empty.
|
||||||
|
|
||||||
|
#### brutal_debug
|
||||||
|
|
||||||
|
Enable debug information logging for Hysteria Brutal CC.
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user