Compare commits

..
Author SHA1 Message Date
世界 e839711d43 documentation: Bump version 2023-12-05 19:09:33 +08:00
世界 41c324d78c Make type check strict 2023-12-05 19:09:33 +08:00
世界 29ab114767 Remove comparable limit for Listable 2023-12-05 19:09:26 +08:00
世界 7e2dc5fc5c Avoid opening log output before start &
Replace tracing logs with task monitor
2023-12-05 19:09:26 +08:00
世界 90d9efe62c Update documentation 2023-12-05 19:09:26 +08:00
世界 e07b5059c0 Add idle_timeout for URLTest outbound 2023-12-05 19:09:26 +08:00
世界 822c4fa498 Skip internal fake-ip queries 2023-12-05 19:09:26 +08:00
世界 76b26fa76d Migrate contentjson and badjson to library &
Add omitempty in format
2023-12-05 19:09:26 +08:00
世界 e35f99ec8a Update documentation 2023-12-05 19:09:26 +08:00
世界 88c88f3279 Independent source_ip_is_private and ip_is_private rules 2023-12-05 19:09:26 +08:00
世界 e4162b28f1 Add rule-set 2023-12-05 19:09:26 +08:00
世界 2e4c3067f2 Update buffer usage 2023-12-05 19:00:54 +08:00
世界 097fc344a5 Allow nested logical rules 2023-12-05 18:32:57 +08:00
世界 166245f44f Migrate to independent cache file 2023-12-05 18:32:57 +08:00
342 changed files with 4895 additions and 9337 deletions
-1
View File
@@ -1 +0,0 @@
github: nekohasekai
+15 -24
View File
@@ -44,7 +44,13 @@ body:
attributes:
label: Version
description: If you are using the original command line program, please provide the output of the `sing-box version` command.
render: shell
value: |-
<details>
```console
# Replace this line with the output
```
</details>
- type: textarea
attributes:
label: Description
@@ -61,28 +67,13 @@ body:
attributes:
label: Logs
description: |-
In addition, if you encounter a crash with the graphical client, please also 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 the Android client, please check the `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` file for crash logs.
render: shell
- type: checkboxes
id: supporter
attributes:
label: Supporter
options:
- label: I am a [sponsor](https://github.com/sponsors/nekohasekai/)
- type: checkboxes
attributes:
label: Integrity requirements
description: |-
Please check all of the following options to prove that you have read and understood the requirements, otherwise this issue will be closed.
Sing-box is not a project aimed to please users who can't make any meaningful contributions and gain unethical influence. If you deceive here to deliberately waste the time of the developers, you will be permanently blocked.
options:
- label: I confirm that I have read the documentation, understand the meaning of all the configuration items I wrote, and did not pile up seemingly useful options or default values.
required: true
- label: I confirm that I have provided the server and client configuration files and process that can be reproduced locally, instead of a complicated client configuration file that has been stripped of sensitive data.
required: true
- label: I confirm that I have provided the simplest configuration that can be used to reproduce the error I reported, instead of depending on remote servers, TUN, graphical interface clients, or other closed-source software.
required: true
- label: I confirm that I have provided the complete configuration files and logs, rather than just providing parts I think are useful out of confidence in my own intelligence.
required: true
value: |-
<details>
```console
# Replace this line with logs
```
</details>
+15 -24
View File
@@ -44,7 +44,13 @@ body:
attributes:
label: 版本
description: 如果您使用原始命令行程序,请提供 `sing-box version` 命令的输出。
render: shell
value: |-
<details>
```console
# 使用输出内容覆盖此行
```
</details>
- type: textarea
attributes:
label: 描述
@@ -61,28 +67,13 @@ body:
attributes:
label: 日志
description: |-
此外,如果您遭遇图形界面应用程序崩溃,请附加提供崩溃日志。
如果您遭遇图形界面应用程序崩溃,请提供崩溃日志。
对于 Apple 平台图形客户端程序,请检查 `Settings - View Service Log` 以导出崩溃日志。
对于 Android 图形客户端程序,请检查 `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` 文件以导出崩溃日志。
render: shell
- type: checkboxes
id: supporter
attributes:
label: 支持我们
options:
- label: 我已经 [赞助](https://github.com/sponsors/nekohasekai/)
- type: checkboxes
attributes:
label: 完整性要求
description: |-
请勾选以下所有选项以证明您已经阅读并理解了以下要求,否则该 issue 将被关闭。
sing-box 不是讨好无法作出任何意义上的贡献的最终用户并获取非道德影响力的项目,如果您在此处欺骗以故意浪费开发者的时间,您将被永久封锁。
options:
- label: 我保证阅读了文档,了解所有我编写的配置文件项的含义,而不是大量堆砌看似有用的选项或默认值。
required: true
- label: 我保证提供了可以在本地重现该问题的服务器、客户端配置文件与流程,而不是一个脱敏的复杂客户端配置文件。
required: true
- label: 我保证提供了可用于重现我报告的错误的最简配置,而不是依赖远程服务器、TUN、图形界面客户端或者其他闭源软件。
required: true
- label: 我保证提供了完整的配置文件与日志,而不是出于对自身智力的自信而仅提供了部分认为有用的部分。
required: true
value: |-
<details>
```console
# 使用日志内容覆盖此行
```
</details>
-14
View File
@@ -1,14 +0,0 @@
#!/usr/bin/env bash
PROJECTS=$(dirname "$0")/../..
function updateClient() {
pushd clients/$1
git fetch
git reset FETCH_HEAD --hard
popd
git add clients/$1
}
updateClient "apple"
updateClient "android"
+55 -33
View File
@@ -22,55 +22,67 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
fetch-depth: 0
- name: Get latest go version
id: version
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
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: ^1.22
go-version: ${{ steps.version.outputs.go_version }}
- name: Add cache to Go proxy
run: |
version=`git rev-parse HEAD`
mkdir build
pushd build
go mod init build
go get -v github.com/sagernet/sing-box@$version
popd
continue-on-error: true
- name: Run Test
run: |
go test -v ./...
build_go118:
name: Debug build (Go 1.18)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: 1.18.10
- name: Cache go module
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go118-${{ hashFiles('**/go.sum') }}
- name: Run Test
run: make ci_build_go118
build_go120:
name: Debug build (Go 1.20)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: ~1.20
go-version: 1.20.7
- name: Cache go module
uses: actions/cache@v4
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go120-${{ hashFiles('**/go.sum') }}
- name: Run Test
run: make ci_build_go120
build_go121:
name: Debug build (Go 1.21)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.21
- name: Cache go module
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
key: go121-${{ hashFiles('**/go.sum') }}
key: go118-${{ hashFiles('**/go.sum') }}
- name: Run Test
run: make ci_build
cross:
@@ -176,7 +188,8 @@ jobs:
- name: freebsd-arm64
goos: freebsd
goarch: arm64
fail-fast: true
fail-fast: false
runs-on: ubuntu-latest
env:
GOOS: ${{ matrix.goos }}
@@ -188,13 +201,22 @@ jobs:
TAGS: with_clash_api,with_quic
steps:
- name: Checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
fetch-depth: 0
- name: Get latest go version
id: version
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
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: ^1.21
go-version: ${{ steps.version.outputs.go_version }}
- name: Build
id: build
run: make
run: make
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: sing-box-${{ matrix.name }}
path: sing-box*
+13 -28
View File
@@ -1,9 +1,5 @@
name: Build Docker Images
on:
release:
types:
- released
workflow_dispatch:
inputs:
tag:
@@ -12,27 +8,8 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Get commit to build
id: ref
run: |-
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
ref="${{ github.ref_name }}"
else
ref="${{ github.event.inputs.tag }}"
fi
echo "ref=$ref"
echo "ref=$ref" >> $GITHUB_OUTPUT
if [[ $ref == *"-"* ]]; then
latest=latest-beta
else
latest=latest
fi
echo "latest=$latest"
echo "latest=$latest" >> $GITHUB_OUTPUT
- name: Checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
with:
ref: ${{ steps.ref.outputs.ref }}
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Setup QEMU for Docker Buildx
@@ -48,15 +25,23 @@ jobs:
uses: docker/metadata-action@v5
with:
images: ghcr.io/sagernet/sing-box
- name: Get tag to build
id: tag
run: |
echo "latest=ghcr.io/sagernet/sing-box:latest" >> $GITHUB_OUTPUT
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
echo "versioned=ghcr.io/sagernet/sing-box:${{ github.ref_name }}" >> $GITHUB_OUTPUT
else
echo "versioned=ghcr.io/sagernet/sing-box:${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
fi
- name: Build and release Docker images
uses: docker/build-push-action@v6
uses: docker/build-push-action@v5
with:
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
context: .
target: dist
build-args: |
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
tags: |
ghcr.io/sagernet/sing-box:${{ steps.ref.outputs.latest }}
ghcr.io/sagernet/sing-box:${{ steps.ref.outputs.ref }}
${{ steps.tag.outputs.latest }}
${{ steps.tag.outputs.versioned }}
push: true
+8 -4
View File
@@ -22,15 +22,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
fetch-depth: 0
- name: Get latest go version
id: version
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
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: ^1.22
go-version: ${{ steps.version.outputs.go_version }}
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
uses: golangci/golangci-lint-action@v3
with:
version: latest
args: --timeout=30m
-39
View File
@@ -1,39 +0,0 @@
name: Release to Linux repository
on:
release:
types:
- published
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.22
- name: Extract signing key
run: |-
mkdir -p $HOME/.gnupg
cat > $HOME/.gnupg/sagernet.key <<EOF
${{ secrets.GPG_KEY }}
echo "HOME=$HOME" >> "$GITHUB_ENV"
EOF
echo "HOME=$HOME" >> "$GITHUB_ENV"
- name: Publish release
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: latest
args: release -f .goreleaser.fury.yaml --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
+1 -1
View File
@@ -8,7 +8,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
- uses: actions/stale@v8
with:
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
days-before-stale: 60
-2
View File
@@ -14,5 +14,3 @@
/*.xcframework/
.DS_Store
/config.d/
/venv/
-6
View File
@@ -1,6 +0,0 @@
[submodule "clients/apple"]
path = clients/apple
url = https://github.com/SagerNet/sing-box-for-apple.git
[submodule "clients/android"]
path = clients/android
url = https://github.com/SagerNet/sing-box-for-android.git
-86
View File
@@ -1,86 +0,0 @@
project_name: sing-box
builds:
- id: main
main: ./cmd/sing-box
flags:
- -v
- -trimpath
ldflags:
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_ech
- with_utls
- with_reality_server
- with_acme
- with_clash_api
env:
- CGO_ENABLED=0
targets:
- linux_386
- linux_amd64_v1
- linux_arm64
- linux_arm_7
- linux_s390x
- linux_riscv64
mod_timestamp: '{{ .CommitTimestamp }}'
snapshot:
name_template: "{{ .Version }}.{{ .ShortCommit }}"
nfpms:
- &template
id: package
package_name: sing-box
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
builds:
- main
homepage: https://sing-box.sagernet.org/
maintainer: nekohasekai <contact-git@sekai.icu>
description: The universal proxy platform.
license: GPLv3 or later
formats:
- deb
- rpm
priority: extra
contents:
- src: release/config/config.json
dst: /etc/sing-box/config.json
type: config
- src: release/config/sing-box.service
dst: /usr/lib/systemd/system/sing-box.service
- src: release/config/sing-box@.service
dst: /usr/lib/systemd/system/sing-box@.service
- src: LICENSE
dst: /usr/share/licenses/sing-box/LICENSE
deb:
signature:
key_file: "{{ .Env.NFPM_KEY_PATH }}"
fields:
Bugs: https://github.com/SagerNet/sing-box/issues
rpm:
signature:
key_file: "{{ .Env.NFPM_KEY_PATH }}"
conflicts:
- sing-box-beta
- id: package_beta
<<: *template
package_name: sing-box-beta
file_name_template: '{{ .ProjectName }}-beta_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
formats:
- deb
- rpm
conflicts:
- sing-box
release:
disable: true
furies:
- account: sagernet
ids:
- package
disable: "{{ not (not .Prerelease) }}"
- account: sagernet
ids:
- package_beta
disable: "{{ not .Prerelease }}"
+58 -40
View File
@@ -1,11 +1,14 @@
project_name: sing-box
builds:
- &template
id: main
- id: main
main: ./cmd/sing-box
flags:
- -v
- -trimpath
asmflags:
- all=-trimpath={{.Env.GOPATH}}
gcflags:
- all=-trimpath={{.Env.GOPATH}}
ldflags:
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
tags:
@@ -27,35 +30,65 @@ builds:
- linux_arm64
- linux_arm_7
- linux_s390x
- linux_riscv64
- windows_amd64_v1
- windows_amd64_v3
- windows_386
- windows_arm64
- darwin_amd64_v1
- darwin_amd64_v3
- darwin_arm64
mod_timestamp: '{{ .CommitTimestamp }}'
- id: legacy
<<: *template
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={{ .Env.GOPATH }}/go1.20.14
gobinary: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
- 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
<<: *template
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=1
overrides:
@@ -63,8 +96,8 @@ builds:
goarch: arm
goarm: 7
env:
- CC=armv7a-linux-androideabi21-clang
- CXX=armv7a-linux-androideabi21-clang++
- CC=armv7a-linux-androideabi19-clang
- CXX=armv7a-linux-androideabi19-clang++
- goos: android
goarch: arm64
env:
@@ -73,8 +106,8 @@ builds:
- goos: android
goarch: 386
env:
- CC=i686-linux-android21-clang
- CXX=i686-linux-android21-clang++
- CC=i686-linux-android19-clang
- CXX=i686-linux-android19-clang++
- goos: android
goarch: amd64
goamd64: v1
@@ -86,11 +119,11 @@ builds:
- android_arm64
- android_386
- android_amd64
mod_timestamp: '{{ .CommitTimestamp }}'
snapshot:
name_template: "{{ .Version }}.{{ .ShortCommit }}"
archives:
- &template
id: archive
- id: archive
builds:
- main
- android
@@ -103,16 +136,21 @@ archives:
- LICENSE
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
- id: archive-legacy
<<: *template
builds:
- legacy
format: tar.gz
format_overrides:
- goos: windows
format: zip
wrap_in_directory: true
files:
- LICENSE
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
nfpms:
- id: package
package_name: sing-box
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
builds:
- main
vendor: sagernet
homepage: https://sing-box.sagernet.org/
maintainer: nekohasekai <contact-git@sekai.icu>
description: The universal proxy platform.
@@ -127,27 +165,11 @@ nfpms:
dst: /etc/sing-box/config.json
type: config
- src: release/config/sing-box.service
dst: /usr/lib/systemd/system/sing-box.service
dst: /etc/systemd/system/sing-box.service
- src: release/config/sing-box@.service
dst: /usr/lib/systemd/system/sing-box@.service
dst: /etc/systemd/system/sing-box@.service
- src: LICENSE
dst: /usr/share/licenses/sing-box/LICENSE
deb:
signature:
key_file: "{{ .Env.NFPM_KEY_PATH }}"
fields:
Bugs: https://github.com/SagerNet/sing-box/issues
rpm:
signature:
key_file: "{{ .Env.NFPM_KEY_PATH }}"
overrides:
deb:
conflicts:
- sing-box-beta
rpm:
conflicts:
- sing-box-beta
source:
enabled: false
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
@@ -161,10 +183,6 @@ release:
github:
owner: SagerNet
name: sing-box
name_template: '{{ if .IsSnapshot }}{{ nightly }}{{ else }}{{ .Version }}{{ end }}'
draft: true
prerelease: auto
mode: replace
ids:
- archive
- package
skip_upload: true
mode: replace
+1 -1
View File
@@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS builder
FROM --platform=$BUILDPLATFORM golang:1.21-alpine AS builder
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
COPY . /go/src/github.com/sagernet/sing-box
WORKDIR /go/src/github.com/sagernet/sing-box
+16 -27
View File
@@ -1,8 +1,8 @@
NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD)
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls
TAGS_GO121 = with_ech
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121)
TAGS_GO118 = with_gvisor,with_dhcp,with_wireguard,with_utls,with_reality_server,with_clash_api
TAGS_GO120 = with_quic,with_ech
TAGS ?= $(TAGS_GO118),$(TAGS_GO120)
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
GOHOSTOS = $(shell go env GOHOSTOS)
@@ -14,14 +14,14 @@ MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
MAIN = ./cmd/sing-box
PREFIX ?= $(shell go env GOPATH)
.PHONY: test release docs build
.PHONY: test release docs
build:
go build $(MAIN_PARAMS) $(MAIN)
ci_build_go120:
ci_build_go118:
go build $(PARAMS) $(MAIN)
go build $(PARAMS) -tags "$(TAGS_GO120)" $(MAIN)
go build $(PARAMS) -tags "$(TAGS_GO118)" $(MAIN)
ci_build:
go build $(PARAMS) $(MAIN)
@@ -59,35 +59,25 @@ proto_install:
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
release:
go run ./cmd/internal/build goreleaser release --clean --skip publish
go run ./cmd/internal/build goreleaser release --clean --skip-publish || exit 1
mkdir dist/release
mv dist/*.tar.gz \
dist/*.zip \
dist/*.deb \
dist/*.rpm \
dist/*_amd64.pkg.tar.zst \
dist/*_amd64v3.pkg.tar.zst \
dist/*_arm64.pkg.tar.zst \
dist/release
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
rm -r dist/release
release_repo:
go run ./cmd/internal/build goreleaser release -f .goreleaser.fury.yaml --clean
release_install:
go install -v github.com/goreleaser/goreleaser@latest
go install -v github.com/tcnksm/ghr@latest
update_android_version:
go run ./cmd/internal/update_android_version
build_android:
cd ../sing-box-for-android && ./gradlew :app:clean :app:assemblePlayRelease :app:assembleOtherRelease && ./gradlew --stop
cd ../sing-box-for-android && ./gradlew :app:assemblePlayRelease && ./gradlew --stop
upload_android:
mkdir -p dist/release_android
cp ../sing-box-for-android/app/build/outputs/apk/play/release/*.apk dist/release_android
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*-universal.apk dist/release_android
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release_android
rm -rf dist/release_android
@@ -188,19 +178,18 @@ lib:
go run ./cmd/internal/build_libbox -target ios
lib_install:
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.3
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.3
go get -v -d
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-20230915142329-c6740b6d2950
docs:
venv/bin/mkdocs serve
mkdocs serve
publish_docs:
venv/bin/mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
docs_install:
python -m venv venv
source ./venv/bin/activate && pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
clean:
rm -rf bin dist sing-box
rm -f $(shell go env GOPATH)/sing-box
+12 -9
View File
@@ -4,13 +4,13 @@ import (
"bytes"
"context"
"encoding/binary"
"io"
"net"
"time"
"github.com/sagernet/sing-box/common/urltest"
"github.com/sagernet/sing-dns"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/varbin"
"github.com/sagernet/sing/common/rw"
)
type ClashServer interface {
@@ -30,9 +30,6 @@ type CacheFile interface {
StoreFakeIP() bool
FakeIPStorage
StoreRDRC() bool
dns.RDRCStore
LoadMode() string
StoreMode(mode string) error
LoadSelected(group string) string
@@ -55,15 +52,16 @@ func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
if err != nil {
return nil, err
}
err = varbin.Write(&buffer, binary.BigEndian, s.Content)
err = rw.WriteUVariant(&buffer, uint64(len(s.Content)))
if err != nil {
return nil, err
}
buffer.Write(s.Content)
err = binary.Write(&buffer, binary.BigEndian, s.LastUpdated.Unix())
if err != nil {
return nil, err
}
err = varbin.Write(&buffer, binary.BigEndian, s.LastEtag)
err = rw.WriteVString(&buffer, s.LastEtag)
if err != nil {
return nil, err
}
@@ -77,7 +75,12 @@ func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
if err != nil {
return err
}
err = varbin.Read(reader, binary.BigEndian, &s.Content)
contentLen, err := rw.ReadUVariant(reader)
if err != nil {
return err
}
s.Content = make([]byte, contentLen)
_, err = io.ReadFull(reader, s.Content)
if err != nil {
return err
}
@@ -87,7 +90,7 @@ func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
return err
}
s.LastUpdated = time.Unix(lastUpdated, 0)
err = varbin.Read(reader, binary.BigEndian, &s.LastEtag)
s.LastEtag, err = rw.ReadVString(reader)
if err != nil {
return err
}
+5 -20
View File
@@ -51,25 +51,19 @@ type InboundContext struct {
// rule cache
IPCIDRMatchSource bool
IPCIDRAcceptEmpty bool
SourceAddressMatch bool
SourcePortMatch bool
DestinationAddressMatch bool
DestinationPortMatch bool
DidMatch bool
IgnoreDestinationIPCIDRMatch bool
IPCIDRMatchSource bool
SourceAddressMatch bool
SourcePortMatch bool
DestinationAddressMatch bool
DestinationPortMatch bool
}
func (c *InboundContext) ResetRuleCache() {
c.IPCIDRMatchSource = false
c.IPCIDRAcceptEmpty = false
c.SourceAddressMatch = false
c.SourcePortMatch = false
c.DestinationAddressMatch = false
c.DestinationPortMatch = false
c.DidMatch = false
}
type inboundContextKey struct{}
@@ -102,12 +96,3 @@ func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
}
return WithContext(ctx, &newMetadata), &newMetadata
}
func OverrideContext(ctx context.Context) context.Context {
if metadata := ContextFrom(ctx); metadata != nil {
var newMetadata InboundContext
newMetadata = *metadata
return WithContext(ctx, &newMetadata)
}
return ctx
}
+2 -23
View File
@@ -10,18 +10,14 @@ import (
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list"
"github.com/sagernet/sing/service"
mdns "github.com/miekg/dns"
"go4.org/netipx"
)
type Router interface {
Service
PreStarter
PostStarter
Cleanup() error
Outbounds() []Outbound
Outbound(tag string) (Outbound, bool)
@@ -36,8 +32,6 @@ type Router interface {
RuleSet(tag string) (RuleSet, bool)
NeedWIFIState() bool
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
@@ -48,9 +42,7 @@ type Router interface {
DefaultInterface() string
AutoDetectInterface() bool
AutoDetectInterfaceFunc() control.Func
DefaultMark() uint32
RegisterAutoRedirectOutputMark(mark uint32) error
AutoRedirectOutputMark() uint32
DefaultMark() int
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager
@@ -76,7 +68,6 @@ func RouterFromContext(ctx context.Context) Router {
type HeadlessRule interface {
Match(metadata *InboundContext) bool
String() string
}
type Rule interface {
@@ -85,38 +76,26 @@ type Rule interface {
Type() string
UpdateGeosite() error
Outbound() string
String() string
}
type DNSRule interface {
Rule
DisableCache() bool
RewriteTTL() *uint32
ClientSubnet() *netip.Prefix
WithAddressLimit() bool
MatchAddressLimit(metadata *InboundContext) bool
}
type RuleSet interface {
Name() string
StartContext(ctx context.Context, startContext RuleSetStartContext) error
PostStart() error
Metadata() RuleSetMetadata
ExtractIPSet() []*netipx.IPSet
IncRef()
DecRef()
Cleanup()
RegisterCallback(callback RuleSetUpdateCallback) *list.Element[RuleSetUpdateCallback]
UnregisterCallback(element *list.Element[RuleSetUpdateCallback])
Close() error
HeadlessRule
}
type RuleSetUpdateCallback func(it RuleSet)
type RuleSetMetadata struct {
ContainsProcessRule bool
ContainsWIFIRule bool
ContainsIPCIDRRule bool
}
type RuleSetStartContext interface {
+14 -35
View File
@@ -55,7 +55,7 @@ func New(options Options) (*Box, error) {
ctx = context.Background()
}
ctx = service.ContextWithDefaultRegistry(ctx)
ctx = pause.WithDefaultManager(ctx)
ctx = pause.ContextWithDefaultManager(ctx)
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
var needCacheFile bool
@@ -111,7 +111,6 @@ func New(options Options) (*Box, error) {
ctx,
router,
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
tag,
inboundOptions,
options.PlatformInterface,
)
@@ -158,12 +157,9 @@ func New(options Options) (*Box, error) {
preServices2 := make(map[string]adapter.Service)
postServices := make(map[string]adapter.Service)
if needCacheFile {
cacheFile := service.FromContext[adapter.CacheFile](ctx)
if cacheFile == nil {
cacheFile = cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
}
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
preServices1["cache file"] = cacheFile
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
}
if needClashAPI {
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
@@ -204,7 +200,7 @@ func (s *Box) PreStart() error {
defer func() {
v := recover()
if v != nil {
println(err.Error())
log.Error(E.Cause(err, "origin error"))
debug.PrintStack()
panic("panic on early close: " + fmt.Sprint(v))
}
@@ -223,9 +219,9 @@ func (s *Box) Start() error {
defer func() {
v := recover()
if v != nil {
println(err.Error())
log.Error(E.Cause(err, "origin error"))
debug.PrintStack()
println("panic on early start: " + fmt.Sprint(v))
panic("panic on early close: " + fmt.Sprint(v))
}
}()
s.Close()
@@ -236,7 +232,7 @@ func (s *Box) Start() error {
}
func (s *Box) preStart() error {
monitor := taskmonitor.New(s.logger, C.StartTimeout)
monitor := taskmonitor.New(s.logger, C.DefaultStartTimeout)
monitor.Start("start logger")
err := s.logFactory.Start()
monitor.Finish()
@@ -263,10 +259,6 @@ func (s *Box) preStart() error {
}
}
}
err = s.router.PreStart()
if err != nil {
return E.Cause(err, "pre-start router")
}
err = s.startOutbounds()
if err != nil {
return err
@@ -303,11 +295,7 @@ func (s *Box) start() error {
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
}
}
err = s.postStart()
if err != nil {
return err
}
return s.router.Cleanup()
return s.postStart()
}
func (s *Box) postStart() error {
@@ -317,28 +305,19 @@ func (s *Box) postStart() error {
return E.Cause(err, "start ", serviceName)
}
}
// TODO: reorganize ALL start order
for _, out := range s.outbounds {
if lateOutbound, isLateOutbound := out.(adapter.PostStarter); isLateOutbound {
for _, outbound := range s.outbounds {
if lateOutbound, isLateOutbound := outbound.(adapter.PostStarter); isLateOutbound {
err := lateOutbound.PostStart()
if err != nil {
return E.Cause(err, "post-start outbound/", out.Tag())
return E.Cause(err, "post-start outbound/", outbound.Tag())
}
}
}
err := s.router.PostStart()
if err != nil {
return err
return E.Cause(err, "post-start router")
}
for _, in := range s.inbounds {
if lateInbound, isLateInbound := in.(adapter.PostStarter); isLateInbound {
err = lateInbound.PostStart()
if err != nil {
return E.Cause(err, "post-start inbound/", in.Tag())
}
}
}
return nil
return s.router.PostStart()
}
func (s *Box) Close() error {
@@ -348,7 +327,7 @@ func (s *Box) Close() error {
default:
close(s.done)
}
monitor := taskmonitor.New(s.logger, C.StopTimeout)
monitor := taskmonitor.New(s.logger, C.DefaultStopTimeout)
var errors error
for serviceName, service := range s.postServices {
monitor.Start("close ", serviceName)
+2 -4
View File
@@ -12,7 +12,7 @@ import (
)
func (s *Box) startOutbounds() error {
monitor := taskmonitor.New(s.logger, C.StartTimeout)
monitor := taskmonitor.New(s.logger, C.DefaultStartTimeout)
outboundTags := make(map[adapter.Outbound]string)
outbounds := make(map[string]adapter.Outbound)
for i, outboundToStart := range s.outbounds {
@@ -45,9 +45,7 @@ func (s *Box) startOutbounds() error {
}
started[outboundTag] = true
canContinue = true
if starter, isStarter := outboundToStart.(interface {
Start() error
}); isStarter {
if starter, isStarter := outboundToStart.(common.Starter); isStarter {
monitor.Start("initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
err := starter.Start()
monitor.Finish()
Submodule clients/android deleted from 00e3a80875
Submodule clients/apple deleted from 0cbe335cbb
+6 -6
View File
@@ -7,7 +7,7 @@ import (
"path/filepath"
"strings"
_ "github.com/sagernet/gomobile"
_ "github.com/sagernet/gomobile/event/key"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/rw"
@@ -46,13 +46,13 @@ var (
func init() {
sharedFlags = append(sharedFlags, "-trimpath")
sharedFlags = append(sharedFlags, "-buildvcs=false")
sharedFlags = append(sharedFlags, "-ldflags")
currentTag, err := build_shared.ReadTag()
if err != nil {
currentTag = "unknown"
}
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
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)
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")
@@ -93,7 +93,7 @@ func buildAndroid() {
const name = "libbox.aar"
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
if rw.IsDir(copyPath) {
if rw.FileExists(copyPath) {
copyPath, _ = filepath.Abs(copyPath)
err = rw.CopyFile(name, filepath.Join(copyPath, name))
if err != nil {
@@ -134,7 +134,7 @@ func buildiOS() {
}
copyPath := filepath.Join("..", "sing-box-for-apple")
if rw.IsDir(copyPath) {
if rw.FileExists(copyPath) {
targetDir := filepath.Join(copyPath, "Libbox.xcframework")
targetDir, _ = filepath.Abs(targetDir)
os.RemoveAll(targetDir)
+8 -28
View File
@@ -11,9 +11,7 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/shell"
)
var (
@@ -30,7 +28,7 @@ func FindSDK() {
}
for _, path := range searchPath {
path = os.ExpandEnv(path)
if rw.IsFile(filepath.Join(path, "licenses", "android-sdk-license")) {
if rw.FileExists(path + "/licenses/android-sdk-license") {
androidSDKPath = path
break
}
@@ -42,14 +40,6 @@ func FindSDK() {
log.Fatal("android NDK not found")
}
javaVersion, err := shell.Exec("java", "--version").ReadOutput()
if err != nil {
log.Fatal(E.Cause(err, "check java version"))
}
if !strings.Contains(javaVersion, "openjdk 17") {
log.Fatal("java version should be openjdk 17")
}
os.Setenv("ANDROID_HOME", androidSDKPath)
os.Setenv("ANDROID_SDK_HOME", androidSDKPath)
os.Setenv("ANDROID_NDK_HOME", androidNDKPath)
@@ -58,13 +48,11 @@ func FindSDK() {
}
func findNDK() bool {
const fixedVersion = "26.2.11394342"
const versionFile = "source.properties"
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
androidNDKPath = fixedPath
if rw.FileExists(androidSDKPath + "/ndk/25.1.8937393") {
androidNDKPath = androidSDKPath + "/ndk/25.1.8937393"
return true
}
ndkVersions, err := os.ReadDir(filepath.Join(androidSDKPath, "ndk"))
ndkVersions, err := os.ReadDir(androidSDKPath + "/ndk")
if err != nil {
return false
}
@@ -85,10 +73,8 @@ func findNDK() bool {
return true
})
for _, versionName := range versionNames {
currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName)
if rw.IsFile(filepath.Join(androidSDKPath, versionFile)) {
androidNDKPath = currentNDKPath
log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion)
if rw.FileExists(androidSDKPath + "/ndk/" + versionName) {
androidNDKPath = androidSDKPath + "/ndk/" + versionName
return true
}
}
@@ -99,14 +85,8 @@ var GoBinPath string
func FindMobile() {
goBin := filepath.Join(build.Default.GOPATH, "bin")
if runtime.GOOS == "windows" {
if !rw.IsFile(filepath.Join(goBin, "gobind.exe")) {
log.Fatal("missing gomobile installation")
}
} else {
if !rw.IsFile(filepath.Join(goBin, "gobind")) {
log.Fatal("missing gomobile installation")
}
if !rw.FileExists(goBin + "/" + "gobind") {
log.Fatal("missing gomobile installation")
}
GoBinPath = goBin
}
+10 -23
View File
@@ -3,7 +3,6 @@ package main
import (
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
@@ -19,46 +18,34 @@ func main() {
log.Fatal(err)
}
common.Must(os.Chdir(androidPath))
localProps := common.Must1(os.ReadFile("version.properties"))
localProps := common.Must1(os.ReadFile("local.properties"))
var propsList [][]string
for _, propLine := range strings.Split(string(localProps), "\n") {
propsList = append(propsList, strings.Split(propLine, "="))
}
var (
versionUpdated bool
goVersionUpdated bool
)
for _, propPair := range propsList {
switch propPair[0] {
case "VERSION_NAME":
if propPair[1] != newVersion.String() {
versionUpdated = true
propPair[1] = newVersion.String()
log.Info("updated version to ", newVersion.String())
}
case "GO_VERSION":
if propPair[1] != runtime.Version() {
goVersionUpdated = true
propPair[1] = runtime.Version()
log.Info("updated Go version to ", runtime.Version())
if propPair[0] == "VERSION_NAME" {
if propPair[1] == newVersion.String() {
log.Info("version not changed")
return
}
propPair[1] = newVersion.String()
log.Info("updated version to ", newVersion.String())
}
}
if !(versionUpdated || goVersionUpdated) {
log.Info("version not changed")
return
}
for _, propPair := range propsList {
switch propPair[0] {
case "VERSION_CODE":
versionCode := common.Must1(strconv.ParseInt(propPair[1], 10, 64))
propPair[1] = strconv.Itoa(int(versionCode + 1))
log.Info("updated version code to ", propPair[1])
case "RELEASE_NOTES":
propPair[1] = "sing-box " + newVersion.String()
}
}
var newProps []string
for _, propPair := range propsList {
newProps = append(newProps, strings.Join(propPair, "="))
}
common.Must(os.WriteFile("version.properties", []byte(strings.Join(newProps, "\n")), 0o644))
common.Must(os.WriteFile("local.properties", []byte(strings.Join(newProps, "\n")), 0o644))
}
+1 -1
View File
@@ -1,6 +1,7 @@
package main
import (
"encoding/json"
"io"
"os"
@@ -8,7 +9,6 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/json"
"github.com/spf13/cobra"
)
+38 -18
View File
@@ -54,11 +54,7 @@ func merge(outputPath string) error {
return nil
}
}
err = rw.MkdirParent(outputPath)
if err != nil {
return err
}
err = os.WriteFile(outputPath, buffer.Bytes(), 0o644)
err = rw.WriteFile(outputPath, buffer.Bytes())
if err != nil {
return err
}
@@ -69,26 +65,50 @@ func merge(outputPath string) error {
func mergePathResources(options *option.Options) error {
for index, inbound := range options.Inbounds {
rawOptions, err := inbound.RawOptions()
if err != nil {
return err
}
if tlsOptions, containsTLSOptions := rawOptions.(option.InboundTLSOptionsWrapper); containsTLSOptions {
tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
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 {
rawOptions, err := outbound.RawOptions()
if err != nil {
return err
}
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)
}
if tlsOptions, containsTLSOptions := rawOptions.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
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
}
+1 -1
View File
@@ -6,7 +6,7 @@ import (
var commandRuleSet = &cobra.Command{
Use: "rule-set",
Short: "Manage rule-sets",
Short: "Manage rule sets",
}
func init() {
+5 -9
View File
@@ -47,18 +47,14 @@ func compileRuleSet(sourcePath string) error {
return err
}
}
content, err := io.ReadAll(reader)
if err != nil {
return err
}
plainRuleSet, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content)
if err != nil {
return err
}
ruleSet, err := plainRuleSet.Upgrade()
decoder := json.NewDecoder(json.NewCommentFilter(reader))
decoder.DisallowUnknownFields()
var plainRuleSet option.PlainRuleSetCompat
err = decoder.Decode(&plainRuleSet)
if err != nil {
return err
}
ruleSet := plainRuleSet.Upgrade()
var outputPath string
if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput {
if strings.HasSuffix(sourcePath, ".json") {
+6 -2
View File
@@ -50,14 +50,18 @@ func formatRuleSet(sourcePath string) error {
if err != nil {
return err
}
plainRuleSet, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content)
decoder := json.NewDecoder(json.NewCommentFilter(bytes.NewReader(content)))
decoder.DisallowUnknownFields()
var plainRuleSet option.PlainRuleSetCompat
err = decoder.Decode(&plainRuleSet)
if err != nil {
return err
}
ruleSet := plainRuleSet.Upgrade()
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(plainRuleSet)
err = encoder.Encode(ruleSet)
if err != nil {
return E.Cause(err, "encode config")
}
-90
View File
@@ -1,90 +0,0 @@
package main
import (
"bytes"
"io"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/srs"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/route"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json"
"github.com/spf13/cobra"
)
var flagRuleSetMatchFormat string
var commandRuleSetMatch = &cobra.Command{
Use: "match <rule-set path> <domain>",
Short: "Check if a domain matches the rule-set",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
err := ruleSetMatch(args[0], args[1])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandRuleSetMatch.Flags().StringVarP(&flagRuleSetMatchFormat, "format", "f", "source", "rule-set format")
commandRuleSet.AddCommand(commandRuleSetMatch)
}
func ruleSetMatch(sourcePath string, domain string) error {
var (
reader io.Reader
err error
)
if sourcePath == "stdin" {
reader = os.Stdin
} else {
reader, err = os.Open(sourcePath)
if err != nil {
return E.Cause(err, "read rule-set")
}
}
content, err := io.ReadAll(reader)
if err != nil {
return E.Cause(err, "read rule-set")
}
var plainRuleSet option.PlainRuleSet
switch flagRuleSetMatchFormat {
case C.RuleSetFormatSource:
var compat option.PlainRuleSetCompat
compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
if err != nil {
return err
}
plainRuleSet, err = compat.Upgrade()
if err != nil {
return err
}
case C.RuleSetFormatBinary:
plainRuleSet, err = srs.Read(bytes.NewReader(content), false)
if err != nil {
return err
}
default:
return E.New("unknown rule-set format: ", flagRuleSetMatchFormat)
}
for i, ruleOptions := range plainRuleSet.Rules {
var currentRule adapter.HeadlessRule
currentRule, err = route.NewHeadlessRule(nil, ruleOptions)
if err != nil {
return E.Cause(err, "parse rule_set.rules.[", i, "]")
}
if currentRule.Match(&adapter.InboundContext{
Domain: domain,
}) {
println(F.ToString("match rules.[", i, "]: ", currentRule))
}
}
return nil
}
+6 -11
View File
@@ -17,7 +17,6 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/spf13/cobra"
@@ -57,7 +56,8 @@ func readConfigAt(path string) (*OptionsEntry, error) {
if err != nil {
return nil, E.Cause(err, "read config at ", path)
}
options, err := json.UnmarshalExtended[option.Options](configContent)
var options option.Options
err = options.UnmarshalJSON(configContent)
if err != nil {
return nil, E.Cause(err, "decode config at ", path)
}
@@ -107,18 +107,13 @@ func readConfigAndMerge() (option.Options, error) {
if len(optionsList) == 1 {
return optionsList[0].options, nil
}
var mergedMessage json.RawMessage
var mergedOptions option.Options
for _, options := range optionsList {
mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage, false)
mergedOptions, err = badjson.Merge(options.options, mergedOptions)
if err != nil {
return option.Options{}, E.Cause(err, "merge config at ", options.path)
}
}
var mergedOptions option.Options
err = mergedOptions.UnmarshalJSON(mergedMessage)
if err != nil {
return option.Options{}, E.Cause(err, "unmarshal merged config")
}
return mergedOptions, nil
}
@@ -133,7 +128,7 @@ func create() (*box.Box, context.CancelFunc, error) {
}
options.Log.DisableColor = true
}
ctx, cancel := context.WithCancel(globalCtx)
ctx, cancel := context.WithCancel(context.Background())
instance, err := box.New(box.Options{
Context: ctx,
Options: options,
@@ -199,7 +194,7 @@ func run() error {
}
func closeMonitor(ctx context.Context) {
time.Sleep(C.FatalStopTimeout)
time.Sleep(C.DefaultStopFatalTimeout)
select {
case <-ctx.Done():
return
+4 -28
View File
@@ -9,10 +9,8 @@ import (
"net/url"
"os"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
"github.com/spf13/cobra"
@@ -34,10 +32,7 @@ func init() {
commandTools.AddCommand(commandFetch)
}
var (
httpClient *http.Client
http3Client *http.Client
)
var httpClient *http.Client
func fetch(args []string) error {
instance, err := createPreStartedClient()
@@ -58,16 +53,8 @@ func fetch(args []string) error {
},
}
defer httpClient.CloseIdleConnections()
if C.WithQUIC {
err = initializeHTTP3Client(instance)
if err != nil {
return err
}
defer http3Client.CloseIdleConnections()
}
for _, urlString := range args {
var parsedURL *url.URL
parsedURL, err = url.Parse(urlString)
parsedURL, err := url.Parse(urlString)
if err != nil {
return err
}
@@ -76,27 +63,16 @@ func fetch(args []string) error {
parsedURL.Scheme = "http"
fallthrough
case "http", "https":
err = fetchHTTP(httpClient, parsedURL)
err = fetchHTTP(parsedURL)
if err != nil {
return err
}
case "http3":
if !C.WithQUIC {
return C.ErrQUICNotIncluded
}
parsedURL.Scheme = "https"
err = fetchHTTP(http3Client, parsedURL)
if err != nil {
return err
}
default:
return E.New("unsupported scheme: ", parsedURL.Scheme)
}
}
return nil
}
func fetchHTTP(httpClient *http.Client, parsedURL *url.URL) error {
func fetchHTTP(parsedURL *url.URL) error {
request, err := http.NewRequest("GET", parsedURL.String(), nil)
if err != nil {
return err
-36
View File
@@ -1,36 +0,0 @@
//go:build with_quic
package main
import (
"context"
"crypto/tls"
"net/http"
"github.com/sagernet/quic-go"
"github.com/sagernet/quic-go/http3"
box "github.com/sagernet/sing-box"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func initializeHTTP3Client(instance *box.Box) error {
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
if err != nil {
return err
}
http3Client = &http.Client{
Transport: &http3.RoundTripper{
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
destination := M.ParseSocksaddr(addr)
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
if dErr != nil {
return nil, dErr
}
return quic.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), tlsCfg, cfg)
},
},
}
return nil
}
@@ -1,18 +0,0 @@
//go:build !with_quic
package main
import (
"net/url"
"os"
box "github.com/sagernet/sing-box"
)
func initializeHTTP3Client(instance *box.Box) error {
return os.ErrInvalid
}
func fetchHTTP3(parsedURL *url.URL) error {
return os.ErrInvalid
}
+2 -21
View File
@@ -3,19 +3,15 @@ package main
import (
"context"
"os"
"os/user"
"strconv"
"time"
_ "github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/service/filemanager"
"github.com/spf13/cobra"
)
var (
globalCtx context.Context
configPaths []string
configDirectories []string
workingDir string
@@ -41,30 +37,15 @@ func main() {
}
func preRun(cmd *cobra.Command, args []string) {
globalCtx = context.Background()
sudoUser := os.Getenv("SUDO_USER")
sudoUID, _ := strconv.Atoi(os.Getenv("SUDO_UID"))
sudoGID, _ := strconv.Atoi(os.Getenv("SUDO_GID"))
if sudoUID == 0 && sudoGID == 0 && sudoUser != "" {
sudoUserObject, _ := user.Lookup(sudoUser)
if sudoUserObject != nil {
sudoUID, _ = strconv.Atoi(sudoUserObject.Uid)
sudoGID, _ = strconv.Atoi(sudoUserObject.Gid)
}
}
if sudoUID > 0 && sudoGID > 0 {
globalCtx = filemanager.WithDefault(globalCtx, "", "", sudoUID, sudoGID)
}
if disableColor {
log.SetStdLogger(log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, "", nil, false).Logger())
}
if workingDir != "" {
_, err := os.Stat(workingDir)
if err != nil {
filemanager.MkdirAll(globalCtx, workingDir, 0o777)
os.MkdirAll(workingDir, 0o777)
}
err = os.Chdir(workingDir)
if err != nil {
if err := os.Chdir(workingDir); err != nil {
log.Fatal(err)
}
}
+233
View File
@@ -0,0 +1,233 @@
//go:build go1.20 && !go1.21
package badtls
import (
"crypto/cipher"
"crypto/rand"
"crypto/tls"
"encoding/binary"
"io"
"net"
"reflect"
"sync"
"sync/atomic"
"unsafe"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
aTLS "github.com/sagernet/sing/common/tls"
)
type Conn struct {
*tls.Conn
writer N.ExtendedWriter
isHandshakeComplete *atomic.Bool
activeCall *atomic.Int32
closeNotifySent *bool
version *uint16
rand io.Reader
halfAccess *sync.Mutex
halfError *error
cipher cipher.AEAD
explicitNonceLen int
halfPtr uintptr
halfSeq []byte
halfScratchBuf []byte
}
func TryCreate(conn aTLS.Conn) aTLS.Conn {
tlsConn, ok := conn.(*tls.Conn)
if !ok {
return conn
}
badConn, err := Create(tlsConn)
if err != nil {
log.Warn("initialize badtls: ", err)
return conn
}
return badConn
}
func Create(conn *tls.Conn) (aTLS.Conn, error) {
rawConn := reflect.Indirect(reflect.ValueOf(conn))
rawIsHandshakeComplete := rawConn.FieldByName("isHandshakeComplete")
if !rawIsHandshakeComplete.IsValid() || rawIsHandshakeComplete.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid isHandshakeComplete")
}
isHandshakeComplete := (*atomic.Bool)(unsafe.Pointer(rawIsHandshakeComplete.UnsafeAddr()))
if !isHandshakeComplete.Load() {
return nil, E.New("handshake not finished")
}
rawActiveCall := rawConn.FieldByName("activeCall")
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid active call")
}
activeCall := (*atomic.Int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
rawHalfConn := rawConn.FieldByName("out")
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid half conn")
}
rawVersion := rawConn.FieldByName("vers")
if !rawVersion.IsValid() || rawVersion.Kind() != reflect.Uint16 {
return nil, E.New("badtls: invalid version")
}
version := (*uint16)(unsafe.Pointer(rawVersion.UnsafeAddr()))
rawCloseNotifySent := rawConn.FieldByName("closeNotifySent")
if !rawCloseNotifySent.IsValid() || rawCloseNotifySent.Kind() != reflect.Bool {
return nil, E.New("badtls: invalid notify")
}
closeNotifySent := (*bool)(unsafe.Pointer(rawCloseNotifySent.UnsafeAddr()))
rawConfig := reflect.Indirect(rawConn.FieldByName("config"))
if !rawConfig.IsValid() || rawConfig.Kind() != reflect.Struct {
return nil, E.New("badtls: bad config")
}
config := (*tls.Config)(unsafe.Pointer(rawConfig.UnsafeAddr()))
randReader := config.Rand
if randReader == nil {
randReader = rand.Reader
}
rawHalfMutex := rawHalfConn.FieldByName("Mutex")
if !rawHalfMutex.IsValid() || rawHalfMutex.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid half mutex")
}
halfAccess := (*sync.Mutex)(unsafe.Pointer(rawHalfMutex.UnsafeAddr()))
rawHalfError := rawHalfConn.FieldByName("err")
if !rawHalfError.IsValid() || rawHalfError.Kind() != reflect.Interface {
return nil, E.New("badtls: invalid half error")
}
halfError := (*error)(unsafe.Pointer(rawHalfError.UnsafeAddr()))
rawHalfCipherInterface := rawHalfConn.FieldByName("cipher")
if !rawHalfCipherInterface.IsValid() || rawHalfCipherInterface.Kind() != reflect.Interface {
return nil, E.New("badtls: invalid cipher interface")
}
rawHalfCipher := rawHalfCipherInterface.Elem()
aeadCipher, loaded := valueInterface(rawHalfCipher, false).(cipher.AEAD)
if !loaded {
return nil, E.New("badtls: invalid AEAD cipher")
}
var explicitNonceLen int
switch cipherName := reflect.Indirect(rawHalfCipher).Type().String(); cipherName {
case "tls.prefixNonceAEAD":
explicitNonceLen = aeadCipher.NonceSize()
case "tls.xorNonceAEAD":
default:
return nil, E.New("badtls: unknown cipher type: ", cipherName)
}
rawHalfSeq := rawHalfConn.FieldByName("seq")
if !rawHalfSeq.IsValid() || rawHalfSeq.Kind() != reflect.Array {
return nil, E.New("badtls: invalid seq")
}
halfSeq := rawHalfSeq.Bytes()
rawHalfScratchBuf := rawHalfConn.FieldByName("scratchBuf")
if !rawHalfScratchBuf.IsValid() || rawHalfScratchBuf.Kind() != reflect.Array {
return nil, E.New("badtls: invalid scratchBuf")
}
halfScratchBuf := rawHalfScratchBuf.Bytes()
return &Conn{
Conn: conn,
writer: bufio.NewExtendedWriter(conn.NetConn()),
isHandshakeComplete: isHandshakeComplete,
activeCall: activeCall,
closeNotifySent: closeNotifySent,
version: version,
halfAccess: halfAccess,
halfError: halfError,
cipher: aeadCipher,
explicitNonceLen: explicitNonceLen,
rand: randReader,
halfPtr: rawHalfConn.UnsafeAddr(),
halfSeq: halfSeq,
halfScratchBuf: halfScratchBuf,
}, nil
}
func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
if buffer.Len() > maxPlaintext {
defer buffer.Release()
return common.Error(c.Write(buffer.Bytes()))
}
for {
x := c.activeCall.Load()
if x&1 != 0 {
return net.ErrClosed
}
if c.activeCall.CompareAndSwap(x, x+2) {
break
}
}
defer c.activeCall.Add(-2)
c.halfAccess.Lock()
defer c.halfAccess.Unlock()
if err := *c.halfError; err != nil {
return err
}
if *c.closeNotifySent {
return errShutdown
}
dataLen := buffer.Len()
dataBytes := buffer.Bytes()
outBuf := buffer.ExtendHeader(recordHeaderLen + c.explicitNonceLen)
outBuf[0] = 23
version := *c.version
if version == 0 {
version = tls.VersionTLS10
} else if version == tls.VersionTLS13 {
version = tls.VersionTLS12
}
binary.BigEndian.PutUint16(outBuf[1:], version)
var nonce []byte
if c.explicitNonceLen > 0 {
nonce = outBuf[5 : 5+c.explicitNonceLen]
if c.explicitNonceLen < 16 {
copy(nonce, c.halfSeq)
} else {
if _, err := io.ReadFull(c.rand, nonce); err != nil {
return err
}
}
}
if len(nonce) == 0 {
nonce = c.halfSeq
}
if *c.version == tls.VersionTLS13 {
buffer.FreeBytes()[0] = 23
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+1+c.cipher.Overhead()))
c.cipher.Seal(outBuf, nonce, outBuf[recordHeaderLen:recordHeaderLen+c.explicitNonceLen+dataLen+1], outBuf[:recordHeaderLen])
buffer.Extend(1 + c.cipher.Overhead())
} else {
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen))
additionalData := append(c.halfScratchBuf[:0], c.halfSeq...)
additionalData = append(additionalData, outBuf[:recordHeaderLen]...)
c.cipher.Seal(outBuf, nonce, dataBytes, additionalData)
buffer.Extend(c.cipher.Overhead())
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+c.explicitNonceLen+c.cipher.Overhead()))
}
incSeq(c.halfPtr)
log.Trace("badtls write ", buffer.Len())
return c.writer.WriteBuffer(buffer)
}
func (c *Conn) FrontHeadroom() int {
return recordHeaderLen + c.explicitNonceLen
}
func (c *Conn) RearHeadroom() int {
return 1 + c.cipher.Overhead()
}
func (c *Conn) WriterMTU() int {
return maxPlaintext
}
func (c *Conn) Upstream() any {
return c.Conn
}
func (c *Conn) UpstreamWriter() any {
return c.NetConn()
}
+14
View File
@@ -0,0 +1,14 @@
//go:build !go1.19 || go1.21
package badtls
import (
"crypto/tls"
"os"
aTLS "github.com/sagernet/sing/common/tls"
)
func Create(conn *tls.Conn) (aTLS.Conn, error) {
return nil, os.ErrInvalid
}
+22
View File
@@ -0,0 +1,22 @@
//go:build go1.20 && !go.1.21
package badtls
import (
"reflect"
_ "unsafe"
)
const (
maxPlaintext = 16384 // maximum plaintext payload length
recordHeaderLen = 5 // record header length
)
//go:linkname errShutdown crypto/tls.errShutdown
var errShutdown error
//go:linkname incSeq crypto/tls.(*halfConn).incSeq
func incSeq(conn uintptr)
//go:linkname valueInterface reflect.valueInterface
func valueInterface(v reflect.Value, safe bool) any
-151
View File
@@ -1,151 +0,0 @@
//go:build go1.21 && !without_badtls
package badtls
import (
"bytes"
"context"
"net"
"os"
"reflect"
"sync"
"unsafe"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/tls"
)
var _ N.ReadWaiter = (*ReadWaitConn)(nil)
type ReadWaitConn struct {
tls.Conn
halfAccess *sync.Mutex
rawInput *bytes.Buffer
input *bytes.Reader
hand *bytes.Buffer
readWaitOptions N.ReadWaitOptions
tlsReadRecord func() error
tlsHandlePostHandshakeMessage func() error
}
func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
var (
loaded bool
tlsReadRecord func() error
tlsHandlePostHandshakeMessage func() error
)
for _, tlsCreator := range tlsRegistry {
loaded, tlsReadRecord, tlsHandlePostHandshakeMessage = tlsCreator(conn)
if loaded {
break
}
}
if !loaded {
return nil, os.ErrInvalid
}
rawConn := reflect.Indirect(reflect.ValueOf(conn))
rawHalfConn := rawConn.FieldByName("in")
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid half conn")
}
rawHalfMutex := rawHalfConn.FieldByName("Mutex")
if !rawHalfMutex.IsValid() || rawHalfMutex.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid half mutex")
}
halfAccess := (*sync.Mutex)(unsafe.Pointer(rawHalfMutex.UnsafeAddr()))
rawRawInput := rawConn.FieldByName("rawInput")
if !rawRawInput.IsValid() || rawRawInput.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid raw input")
}
rawInput := (*bytes.Buffer)(unsafe.Pointer(rawRawInput.UnsafeAddr()))
rawInput0 := rawConn.FieldByName("input")
if !rawInput0.IsValid() || rawInput0.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid input")
}
input := (*bytes.Reader)(unsafe.Pointer(rawInput0.UnsafeAddr()))
rawHand := rawConn.FieldByName("hand")
if !rawHand.IsValid() || rawHand.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid hand")
}
hand := (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr()))
return &ReadWaitConn{
Conn: conn,
halfAccess: halfAccess,
rawInput: rawInput,
input: input,
hand: hand,
tlsReadRecord: tlsReadRecord,
tlsHandlePostHandshakeMessage: tlsHandlePostHandshakeMessage,
}, nil
}
func (c *ReadWaitConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) {
c.readWaitOptions = options
return false
}
func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
err = c.HandshakeContext(context.Background())
if err != nil {
return
}
c.halfAccess.Lock()
defer c.halfAccess.Unlock()
for c.input.Len() == 0 {
err = c.tlsReadRecord()
if err != nil {
return
}
for c.hand.Len() > 0 {
err = c.tlsHandlePostHandshakeMessage()
if err != nil {
return
}
}
}
buffer = c.readWaitOptions.NewBuffer()
n, err := c.input.Read(buffer.FreeBytes())
if err != nil {
buffer.Release()
return
}
buffer.Truncate(n)
if n != 0 && c.input.Len() == 0 && c.rawInput.Len() > 0 &&
// recordType(c.rawInput.Bytes()[0]) == recordTypeAlert {
c.rawInput.Bytes()[0] == 21 {
_ = c.tlsReadRecord()
// return n, err // will be io.EOF on closeNotify
}
c.readWaitOptions.PostReturn(buffer)
return
}
func (c *ReadWaitConn) Upstream() any {
return c.Conn
}
var tlsRegistry []func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error)
func init() {
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
tlsConn, loaded := conn.(*tls.STDConn)
if !loaded {
return
}
return true, func() error {
return stdTLSReadRecord(tlsConn)
}, func() error {
return stdTLSHandlePostHandshakeMessage(tlsConn)
}
})
}
//go:linkname stdTLSReadRecord crypto/tls.(*Conn).readRecord
func stdTLSReadRecord(c *tls.STDConn) error
//go:linkname stdTLSHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage
func stdTLSHandlePostHandshakeMessage(c *tls.STDConn) error
-31
View File
@@ -1,31 +0,0 @@
//go:build go1.21 && !without_badtls && with_ech
package badtls
import (
"net"
_ "unsafe"
"github.com/sagernet/cloudflare-tls"
"github.com/sagernet/sing/common"
)
func init() {
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
tlsConn, loaded := common.Cast[*tls.Conn](conn)
if !loaded {
return
}
return true, func() error {
return echReadRecord(tlsConn)
}, func() error {
return echHandlePostHandshakeMessage(tlsConn)
}
})
}
//go:linkname echReadRecord github.com/sagernet/cloudflare-tls.(*Conn).readRecord
func echReadRecord(c *tls.Conn) error
//go:linkname echHandlePostHandshakeMessage github.com/sagernet/cloudflare-tls.(*Conn).handlePostHandshakeMessage
func echHandlePostHandshakeMessage(c *tls.Conn) error
-13
View File
@@ -1,13 +0,0 @@
//go:build !go1.21 || without_badtls
package badtls
import (
"os"
"github.com/sagernet/sing/common/tls"
)
func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
return nil, os.ErrInvalid
}
-31
View File
@@ -1,31 +0,0 @@
//go:build go1.21 && !without_badtls && with_utls
package badtls
import (
"net"
_ "unsafe"
"github.com/sagernet/sing/common"
"github.com/sagernet/utls"
)
func init() {
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
tlsConn, loaded := common.Cast[*tls.UConn](conn)
if !loaded {
return
}
return true, func() error {
return utlsReadRecord(tlsConn.Conn)
}, func() error {
return utlsHandlePostHandshakeMessage(tlsConn.Conn)
}
})
}
//go:linkname utlsReadRecord github.com/sagernet/utls.(*Conn).readRecord
func utlsReadRecord(c *tls.Conn) error
//go:linkname utlsHandlePostHandshakeMessage github.com/sagernet/utls.(*Conn).handlePostHandshakeMessage
func utlsHandlePostHandshakeMessage(c *tls.Conn) error
+12 -48
View File
@@ -15,61 +15,38 @@ import (
N "github.com/sagernet/sing/common/network"
)
var _ WireGuardListener = (*DefaultDialer)(nil)
type DefaultDialer struct {
dialer4 tcpDialer
dialer6 tcpDialer
udpDialer4 net.Dialer
udpDialer6 net.Dialer
udpListener net.ListenConfig
udpAddr4 string
udpAddr6 string
isWireGuardListener bool
dialer4 tcpDialer
dialer6 tcpDialer
udpDialer4 net.Dialer
udpDialer6 net.Dialer
udpListener net.ListenConfig
udpAddr4 string
udpAddr6 string
}
func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDialer, error) {
var dialer net.Dialer
var listener net.ListenConfig
if options.BindInterface != "" {
var interfaceFinder control.InterfaceFinder
if router != nil {
interfaceFinder = router.InterfaceFinder()
} else {
interfaceFinder = control.NewDefaultInterfaceFinder()
}
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
bindFunc := control.BindToInterface(router.InterfaceFinder(), options.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if router != nil && router.AutoDetectInterface() {
} else if router.AutoDetectInterface() {
bindFunc := router.AutoDetectInterfaceFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if router != nil && router.DefaultInterface() != "" {
} else if router.DefaultInterface() != "" {
bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
var autoRedirectOutputMark uint32
if router != nil {
autoRedirectOutputMark = router.AutoRedirectOutputMark()
}
if autoRedirectOutputMark > 0 {
dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark))
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
}
if options.RoutingMark > 0 {
if options.RoutingMark != 0 {
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
if autoRedirectOutputMark > 0 {
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `routing_mark`")
}
} else if router != nil && router.DefaultMark() > 0 {
} else if router.DefaultMark() != 0 {
dialer.Control = control.Append(dialer.Control, control.RoutingMark(router.DefaultMark()))
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
if autoRedirectOutputMark > 0 {
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `default_mark`")
}
}
if options.ReuseAddr {
listener.Control = control.Append(listener.Control, control.ReuseAddr())
@@ -83,9 +60,6 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
} else {
dialer.Timeout = C.TCPTimeout
}
// TODO: Add an option to customize the keep alive period
dialer.KeepAlive = C.TCPKeepAliveInitial
dialer.Control = control.Append(dialer.Control, control.SetKeepAlivePeriod(C.TCPKeepAliveInitial, C.TCPKeepAliveInterval))
var udpFragment bool
if options.UDPFragment != nil {
udpFragment = *options.UDPFragment
@@ -124,11 +98,6 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
}
setMultiPathTCP(&dialer4)
}
if options.IsWireGuardListener {
for _, controlFn := range wgControlFns {
listener.Control = control.Append(listener.Control, controlFn)
}
}
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
if err != nil {
return nil, err
@@ -145,7 +114,6 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
listener,
udpAddr4,
udpAddr6,
options.IsWireGuardListener,
}, nil
}
@@ -178,10 +146,6 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
}
}
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
return trackPacketConn(d.udpListener.ListenPacket(context.Background(), network, address))
}
func trackConn(conn net.Conn, err error) (net.Conn, error) {
if !conntrack.Enabled || err != nil {
return conn, err
+5 -6
View File
@@ -6,16 +6,15 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common"
N "github.com/sagernet/sing/common/network"
)
func MustNew(router adapter.Router, options option.DialerOptions) N.Dialer {
return common.Must1(New(router, options))
}
func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) {
if options.IsWireGuardListener {
return NewDefault(router, options)
}
if router == nil {
return NewDefault(nil, options)
}
var (
dialer N.Dialer
err error
-1
View File
@@ -80,7 +80,6 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
c.conn = nil
c.err = E.Cause(err, "dial tcp fast open")
}
n = len(b)
close(c.create)
return
}
-9
View File
@@ -1,9 +0,0 @@
package dialer
import (
"net"
)
type WireGuardListener interface {
ListenPacketCompat(network, address string) (net.PacketConn, error)
}
-11
View File
@@ -1,11 +0,0 @@
//go:build with_wireguard
package dialer
import (
"github.com/sagernet/wireguard-go/conn"
)
var _ WireGuardListener = (conn.Listener)(nil)
var wgControlFns = conn.ControlFns
-9
View File
@@ -1,9 +0,0 @@
//go:build !with_wireguard
package dialer
import (
"github.com/sagernet/sing/common/control"
)
var wgControlFns []control.Func
-4
View File
@@ -32,7 +32,3 @@ func (r *Reader) Lookup(addr netip.Addr) string {
}
return "unknown"
}
func (r *Reader) Close() error {
return r.reader.Close()
}
+42 -38
View File
@@ -1,15 +1,11 @@
package geosite
import (
"bufio"
"encoding/binary"
"io"
"os"
"sync/atomic"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/varbin"
"github.com/sagernet/sing/common/rw"
)
type Reader struct {
@@ -38,36 +34,45 @@ func Open(path string) (*Reader, []string, error) {
return reader, codes, nil
}
type geositeMetadata struct {
Code string
Index uint64
Length uint64
}
func (r *Reader) readMetadata() error {
reader := bufio.NewReader(r.reader)
version, err := reader.ReadByte()
version, err := rw.ReadByte(r.reader)
if err != nil {
return err
}
if version != 0 {
return E.New("unknown version")
}
metadataEntries, err := varbin.ReadValue[[]geositeMetadata](reader, binary.BigEndian)
entryLength, err := rw.ReadUVariant(r.reader)
if err != nil {
return err
}
keys := make([]string, entryLength)
domainIndex := make(map[string]int)
domainLength := make(map[string]int)
for _, entry := range metadataEntries {
domainIndex[entry.Code] = int(entry.Index)
domainLength[entry.Code] = int(entry.Length)
for i := 0; i < int(entryLength); i++ {
var (
code string
codeIndex uint64
codeLength uint64
)
code, err = rw.ReadVString(r.reader)
if err != nil {
return err
}
keys[i] = code
codeIndex, err = rw.ReadUVariant(r.reader)
if err != nil {
return err
}
codeLength, err = rw.ReadUVariant(r.reader)
if err != nil {
return err
}
domainIndex[code] = int(codeIndex)
domainLength[code] = int(codeLength)
}
r.domainIndex = domainIndex
r.domainLength = domainLength
if reader.Buffered() > 0 {
return common.Error(r.reader.Seek(int64(-reader.Buffered()), io.SeekCurrent))
}
return nil
}
@@ -80,28 +85,27 @@ func (r *Reader) Read(code string) ([]Item, error) {
if err != nil {
return nil, err
}
counter := &readCounter{Reader: r.reader}
domain, err := varbin.ReadValue[[]Item](bufio.NewReader(counter), binary.BigEndian)
if err != nil {
return nil, err
counter := &rw.ReadCounter{Reader: r.reader}
domain := make([]Item, r.domainLength[code])
for i := range domain {
var (
item Item
err error
)
item.Type, err = rw.ReadByte(counter)
if err != nil {
return nil, err
}
item.Value, err = rw.ReadVString(counter)
if err != nil {
return nil, err
}
domain[i] = item
}
_, err = r.reader.Seek(int64(-index)-counter.count, io.SeekCurrent)
_, err = r.reader.Seek(int64(-index)-counter.Count(), io.SeekCurrent)
return domain, err
}
func (r *Reader) Upstream() any {
return r.reader
}
type readCounter struct {
io.Reader
count int64
}
func (r *readCounter) Read(p []byte) (n int, err error) {
n, err = r.Reader.Read(p)
if n > 0 {
atomic.AddInt64(&r.count, int64(n))
}
return
}
+31 -20
View File
@@ -2,14 +2,13 @@ package geosite
import (
"bytes"
"encoding/binary"
"io"
"sort"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/varbin"
"github.com/sagernet/sing/common/rw"
)
func Write(writer varbin.Writer, domains map[string][]Item) error {
func Write(writer io.Writer, domains map[string][]Item) error {
keys := make([]string, 0, len(domains))
for code := range domains {
keys = append(keys, code)
@@ -20,26 +19,38 @@ func Write(writer varbin.Writer, domains map[string][]Item) error {
index := make(map[string]int)
for _, code := range keys {
index[code] = content.Len()
err := varbin.Write(content, binary.BigEndian, domains[code])
for _, domain := range domains[code] {
content.WriteByte(domain.Type)
err := rw.WriteVString(content, domain.Value)
if err != nil {
return err
}
}
}
err := rw.WriteByte(writer, 0)
if err != nil {
return err
}
err = rw.WriteUVariant(writer, uint64(len(keys)))
if err != nil {
return err
}
for _, code := range keys {
err = rw.WriteVString(writer, code)
if err != nil {
return err
}
}
err := writer.WriteByte(0)
if err != nil {
return err
}
err = varbin.Write(writer, binary.BigEndian, common.Map(keys, func(it string) *geositeMetadata {
return &geositeMetadata{
Code: it,
Index: uint64(index[it]),
Length: uint64(len(domains[it])),
err = rw.WriteUVariant(writer, uint64(index[code]))
if err != nil {
return err
}
err = rw.WriteUVariant(writer, uint64(len(domains[code])))
if err != nil {
return err
}
}))
if err != nil {
return err
}
_, err = writer.Write(content.Bytes())
+1 -18
View File
@@ -1,16 +1,11 @@
package mux
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-mux"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
@@ -35,7 +30,7 @@ func NewClientWithOptions(dialer N.Dialer, logger logger.Logger, options option.
}
}
return mux.NewClient(mux.Options{
Dialer: &clientDialer{dialer},
Dialer: dialer,
Logger: logger,
Protocol: options.Protocol,
MaxConnections: options.MaxConnections,
@@ -45,15 +40,3 @@ func NewClientWithOptions(dialer N.Dialer, logger logger.Logger, options option.
Brutal: brutalOptions,
})
}
type clientDialer struct {
N.Dialer
}
func (d *clientDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
return d.Dialer.DialContext(adapter.OverrideContext(ctx), network, destination)
}
func (d *clientDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return d.Dialer.ListenPacket(adapter.OverrideContext(ctx), destination)
}
+1 -1
View File
@@ -223,7 +223,7 @@ func getExecPathFromPID(pid uint32) (string, error) {
r1, _, err := syscall.SyscallN(
procQueryFullProcessImageNameW.Addr(),
uintptr(h),
uintptr(0),
uintptr(1),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&size)),
)
+19 -29
View File
@@ -16,40 +16,30 @@ import (
)
type LinuxSystemProxy struct {
hasGSettings bool
kWriteConfigCmd string
sudoUser string
serverAddr M.Socksaddr
supportSOCKS bool
isEnabled bool
hasGSettings bool
hasKWriteConfig5 bool
sudoUser string
serverAddr M.Socksaddr
supportSOCKS bool
isEnabled bool
}
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*LinuxSystemProxy, error) {
hasGSettings := common.Error(exec.LookPath("gsettings")) == nil
kWriteConfigCmds := []string{
"kwriteconfig5",
"kwriteconfig6",
}
var kWriteConfigCmd string
for _, cmd := range kWriteConfigCmds {
if common.Error(exec.LookPath(cmd)) == nil {
kWriteConfigCmd = cmd
break
}
}
hasKWriteConfig5 := common.Error(exec.LookPath("kwriteconfig5")) == nil
var sudoUser string
if os.Getuid() == 0 {
sudoUser = os.Getenv("SUDO_USER")
}
if !hasGSettings && kWriteConfigCmd == "" {
if !hasGSettings && !hasKWriteConfig5 {
return nil, E.New("unsupported desktop environment")
}
return &LinuxSystemProxy{
hasGSettings: hasGSettings,
kWriteConfigCmd: kWriteConfigCmd,
sudoUser: sudoUser,
serverAddr: serverAddr,
supportSOCKS: supportSOCKS,
hasGSettings: hasGSettings,
hasKWriteConfig5: hasKWriteConfig5,
sudoUser: sudoUser,
serverAddr: serverAddr,
supportSOCKS: supportSOCKS,
}, nil
}
@@ -80,8 +70,8 @@ func (p *LinuxSystemProxy) Enable() error {
return err
}
}
if p.kWriteConfigCmd != "" {
err := p.runAsUser(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "1")
if p.hasKWriteConfig5 {
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "1")
if err != nil {
return err
}
@@ -93,7 +83,7 @@ func (p *LinuxSystemProxy) Enable() error {
if err != nil {
return err
}
err = p.runAsUser(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "Authmode", "0")
err = p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "Authmode", "0")
if err != nil {
return err
}
@@ -113,8 +103,8 @@ func (p *LinuxSystemProxy) Disable() error {
return err
}
}
if p.kWriteConfigCmd != "" {
err := p.runAsUser(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "0")
if p.hasKWriteConfig5 {
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "0")
if err != nil {
return err
}
@@ -160,7 +150,7 @@ func (p *LinuxSystemProxy) setKDEProxy(proxyTypes ...string) error {
proxyUrl = "http://" + p.serverAddr.String()
}
err := p.runAsUser(
p.kWriteConfigCmd,
"kwriteconfig5",
"--file",
"kioslaverc",
"--group",
-113
View File
@@ -1,113 +0,0 @@
package sniff
import (
"bytes"
"context"
"encoding/binary"
"io"
"os"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
)
const (
trackerConnectFlag = iota
trackerAnnounceFlag
trackerScrapeFlag
trackerProtocolID = 0x41727101980
trackerConnectMinSize = 16
trackerAnnounceMinSize = 20
trackerScrapeMinSize = 8
)
// BitTorrent detects if the stream is a BitTorrent connection.
// For the BitTorrent protocol specification, see https://www.bittorrent.org/beps/bep_0003.html
func BitTorrent(_ context.Context, reader io.Reader) (*adapter.InboundContext, error) {
var first byte
err := binary.Read(reader, binary.BigEndian, &first)
if err != nil {
return nil, err
}
if first != 19 {
return nil, os.ErrInvalid
}
var protocol [19]byte
_, err = reader.Read(protocol[:])
if err != nil {
return nil, err
}
if string(protocol[:]) != "BitTorrent protocol" {
return nil, os.ErrInvalid
}
return &adapter.InboundContext{
Protocol: C.ProtocolBitTorrent,
}, nil
}
// UTP detects if the packet is a uTP connection packet.
// For the uTP protocol specification, see
// 1. https://www.bittorrent.org/beps/bep_0029.html
// 2. https://github.com/bittorrent/libutp/blob/2b364cbb0650bdab64a5de2abb4518f9f228ec44/utp_internal.cpp#L112
func UTP(_ context.Context, packet []byte) (*adapter.InboundContext, error) {
// A valid uTP packet must be at least 20 bytes long.
if len(packet) < 20 {
return nil, os.ErrInvalid
}
version := packet[0] & 0x0F
ty := packet[0] >> 4
if version != 1 || ty > 4 {
return nil, os.ErrInvalid
}
// Validate the extensions
extension := packet[1]
reader := bytes.NewReader(packet[20:])
for extension != 0 {
err := binary.Read(reader, binary.BigEndian, &extension)
if err != nil {
return nil, err
}
var length byte
err = binary.Read(reader, binary.BigEndian, &length)
if err != nil {
return nil, err
}
_, err = reader.Seek(int64(length), io.SeekCurrent)
if err != nil {
return nil, err
}
}
return &adapter.InboundContext{
Protocol: C.ProtocolBitTorrent,
}, nil
}
// UDPTracker detects if the packet is a UDP Tracker Protocol packet.
// For the UDP Tracker Protocol specification, see https://www.bittorrent.org/beps/bep_0015.html
func UDPTracker(_ context.Context, packet []byte) (*adapter.InboundContext, error) {
switch {
case len(packet) >= trackerConnectMinSize &&
binary.BigEndian.Uint64(packet[:8]) == trackerProtocolID &&
binary.BigEndian.Uint32(packet[8:12]) == trackerConnectFlag:
fallthrough
case len(packet) >= trackerAnnounceMinSize &&
binary.BigEndian.Uint32(packet[8:12]) == trackerAnnounceFlag:
fallthrough
case len(packet) >= trackerScrapeMinSize &&
binary.BigEndian.Uint32(packet[8:12]) == trackerScrapeFlag:
return &adapter.InboundContext{
Protocol: C.ProtocolBitTorrent,
}, nil
default:
return nil, os.ErrInvalid
}
}
-81
View File
@@ -1,81 +0,0 @@
package sniff_test
import (
"bytes"
"context"
"encoding/hex"
"testing"
"github.com/sagernet/sing-box/common/sniff"
C "github.com/sagernet/sing-box/constant"
"github.com/stretchr/testify/require"
)
func TestSniffBittorrent(t *testing.T) {
t.Parallel()
packets := []string{
"13426974546f7272656e742070726f746f636f6c0000000000100000e21ea9569b69bab33c97851d0298bdfa89bc90922d5554313631302dea812fcd6a3563e3be40c1d1",
"13426974546f7272656e742070726f746f636f6c00000000001000052aa4f5a7e209e54b32803d43670971c4c8caaa052d5452333030302d653369733079647675763638",
"13426974546f7272656e742070726f746f636f6c00000000001000052aa4f5a7e209e54b32803d43670971c4c8caaa052d5452343035302d6f7a316c6e79377931716130",
}
for _, pkt := range packets {
pkt, err := hex.DecodeString(pkt)
require.NoError(t, err)
metadata, err := sniff.BitTorrent(context.TODO(), bytes.NewReader(pkt))
require.NoError(t, err)
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
}
}
func TestSniffUTP(t *testing.T) {
t.Parallel()
packets := []string{
"010041a282d7ee7b583afb160004000006d8318da776968f92d666f7963f32dae23ba0d2c810d8b8209cc4939f54fde9eeaa521c2c20c9ba7f43f4fb0375f28de06643b5e3ca4685ab7ac76adca99783be72ef05ed59ef4234f5712b75b4c7c0d7bee8fe2ca20ad626ba5bb0ffcc16bf06790896f888048cf72716419a07db1a3dca4550fbcea75b53e97235168a221cf3e553dfbb723961bd719fab038d86e0ecb74747f5a2cd669de1c4b9ad375f3a492d09d98cdfad745435625401315bbba98d35d32086299801377b93495a63a9efddb8d05f5b37a5c5b1c0a25e917f12007bb5e05013ada8aff544fab8cadf61d80ddb0b60f12741e44515a109d144fd53ef845acb4b5ccf0d6fc302d7003d76df3fc3423bb0237301c9e88f900c2d392a8e0fdb36d143cf7527a93fd0a2638b746e72f6699fffcd4fd15348fce780d4caa04382fd9faf1ca0ae377ca805da7536662b84f5ee18dd3ae38fcb095a7543e55f9069ae92c8cf54ae44e97b558d35e2545c66601ed2149cbc32bd6df199a2be7cf0da8b2ff137e0d23e776bc87248425013876d3a3cc31a83b424b752bd0346437f24b532978005d8f5b1b0be1a37a2489c32a18a9ad3118e3f9d30eb299bffae18e1f0677c2a5c185e62519093fe6bc2b7339299ea50a587989f726ca6443a75dd5bb936f6367c6355d80fae53ff529d740b2e5576e3eefdf1fdbfc69c3c8d8ac750512635de63e054bee1d3b689bc1b2bc3d2601e42a00b5c89066d173d4ae7ffedfd2274e5cf6d868fbe640aedb69b8246142f00b32d459974287537ddd5373460dcbc92f5cfdd7a3ed6020822ae922d947893752ca1983d0d32977374c384ac8f5ab566859019b7351526b9f13e932037a55bb052d9deb3b3c23317e0784fdc51a64f2159bfea3b069cf5caf02ee2c3c1a6b6b427bb16165713e8802d95b5c8ed77953690e994bd38c9ae113fedaf6ee7fc2b96c032ceafc2a530ad0422e84546b9c6ad8ef6ea02fa508abddd1805c38a7b42e9b7c971b1b636865ebec06ed754bb404cd6b4e6cc8cb77bd4a0c43410d5cd5ef8fe853a66d49b3b9e06cb141236cdbfdd5761601dc54d1250b86c660e0f898fe62526fdd9acf0eab60a3bbbb2151970461f28f10b31689594bea646c4b03ee197d63bdef4e5a7c22716b3bb9494a83b78ecd81b338b80ac6c09c43485b1b09ba41c74343832c78f0520c1d659ac9eb1502094141e82fb9e5e620970ebc0655514c43c294a7714cbf9a499d277daf089f556398a01589a77494bec8bfb60a108f3813b55368672b88c1af40f6b3c8b513f7c70c3e0efce85228b8b9ec67ba0393f9f7305024d8e2da6a26cf85613d14f249170ce1000089df4c9c260df7f8292aa2ecb5d5bac97656d59aa248caedea2d198e51ce87baece338716d114b458de02d65c9ff808ca5b5b73723b4d1e962d9ac2d98176544dc9984cf8554d07820ef3dd0861cfe57b478328046380de589adad94ee44743ffac73bb7361feca5d56f07cf8ce75080e261282ae30350d7882679b15cab9e7e53ddf93310b33f7390ae5d318bb53f387e6af5d0ef4f947fc9cb8e7e38b52c7f8d772ece6156b38d88796ea19df02c53723b44df7c76315a0de9462f27287e682d2b4cda1a68fe00d7e48c51ee981be44e1ca940fb5190c12655edb4a83c3a4f33e48a015692df4f0b3d61656e362aca657b5ae8c12db5a0db3db1e45135ee918b66918f40e53c4f83e9da0cddfe63f736ae751ab3837a30ae3220d8e8e311487093a7b90c7e7e40dd54ca750e19452f9193aa892aa6a6229ab493dadae988b1724f7898ee69c36d3eb7364c4adbeca811cfe2065873e78c2b6dfdf1595f7a7831c07e03cda82e4f86f76438dfb2b07c13638ce7b509cfa71b88b5102b39a203b423202088e1c2103319cb32c13c1e546ff8612fa194c95a7808ab767c265a1bd5fa0efed5c8ec1701876a00ec8",
"01001ecb68176f215d04326300100000dbcf30292d14b54e9ee2d115ee5b8ebc7fad3e882d4fcdd0c14c6b917c11cb4c6a9f410b52a33ae97c2ac77c7a2b122b8955e09af3c5c595f1b2e79ca57cfe44c44e069610773b9bc9ba223d7f6b383e3adddd03fb88a8476028e30979c2ef321ffc97c5c132bcf9ac5b410bbb5ec6cefca3c7209202a14c5ae922b6b157b0a80249d13ffe5b996af0bc8e54ba576d148372494303e7ead0602b05b9c8fc97d48508a028a04d63a1fd28b0edfcd5c51715f63188b53eefede98a76912dca98518551a8856567307a56a702cbfcc115ea0c755b418bc2c7b57721239b82f09fb24328a4b0ce0f109bcb2a64e04b8aadb1f8487585425acdf8fc4ec8ea93cfcec5ac098bb29d42ddef6e46b03f34a5de28316726699b7cb5195c33e5c48abe87d591d63f9991c84c30819d186d6e0e95fd83c8dff07aa669c4430989bcaccfeacb9bcadbdb4d8f1964dbeb9687745656edd30b21c66cc0a1d742a78717d134a19a7f02d285a4973b1a198c00cfdff4676608dc4f3e817e3463c3b4e2c80d3e8d4fbac541a58a2fb7ad6939f607f8144eff6c8b0adc28ee5609ea158987519892fb",
"21001ecb6817f2805d044fd700100000dbd03029",
"410277ef0b1fb1f60000000000040000c233000000080000000000000000",
}
for _, pkt := range packets {
pkt, err := hex.DecodeString(pkt)
require.NoError(t, err)
metadata, err := sniff.UTP(context.TODO(), pkt)
require.NoError(t, err)
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
}
}
func TestSniffUDPTracker(t *testing.T) {
t.Parallel()
connectPackets := []string{
// connect packets
"00000417271019800000000078e90560",
"00000417271019800000000022c5d64d",
"000004172710198000000000b3863541",
// announce packets
"3d7592ead4b8c9e300000001b871a3820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002092f616e6e6f756e6365",
"3d7592ead4b8c9e30000000188deed1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002092f616e6e6f756e6365",
"3d7592ead4b8c9e300000001ceb948ad0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a3362cdb7020ff920e5aa642c3d4066950dd1f01f4d00000000000000000000000000000000000000000000000000000000000000000000000000000000000002092f616e6e6f756e6365",
// scrape packets
"3d7592ead4b8c9e300000002d2f4bba5a94a8fe5ccb19ba61c4c0873d391e987982fbbd3",
"3d7592ead4b8c9e300000002441243292aae6c35c94fcfb415dbe95f408b9ce91ee846ed",
"3d7592ead4b8c9e300000002b2aa461b1ad1fa9661cf3fe45fb2504ad52ec6c67758e294",
}
for _, pkt := range connectPackets {
pkt, err := hex.DecodeString(pkt)
require.NoError(t, err)
metadata, err := sniff.UDPTracker(context.TODO(), pkt)
require.NoError(t, err)
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
}
}
+74 -36
View File
@@ -1,7 +1,6 @@
package srs
import (
"bufio"
"compress/zlib"
"encoding/binary"
"io"
@@ -12,7 +11,7 @@ import (
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/domain"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/varbin"
"github.com/sagernet/sing/common/rw"
"go4.org/netipx"
)
@@ -46,7 +45,7 @@ func Read(reader io.Reader, recovery bool) (ruleSet option.PlainRuleSet, err err
return
}
if magicBytes != MagicBytes {
err = E.New("invalid sing-box rule-set file")
err = E.New("invalid sing-box rule set file")
return
}
var version uint8
@@ -61,14 +60,13 @@ func Read(reader io.Reader, recovery bool) (ruleSet option.PlainRuleSet, err err
if err != nil {
return
}
bReader := bufio.NewReader(zReader)
length, err := binary.ReadUvarint(bReader)
length, err := rw.ReadUVariant(zReader)
if err != nil {
return
}
ruleSet.Rules = make([]option.HeadlessRule, length)
for i := uint64(0); i < length; i++ {
ruleSet.Rules[i], err = readRule(bReader, recovery)
ruleSet.Rules[i], err = readRule(zReader, recovery)
if err != nil {
err = E.Cause(err, "read rule[", i, "]")
return
@@ -90,13 +88,12 @@ func Write(writer io.Writer, ruleSet option.PlainRuleSet) error {
if err != nil {
return err
}
bWriter := bufio.NewWriter(zWriter)
_, err = varbin.WriteUvarint(bWriter, uint64(len(ruleSet.Rules)))
err = rw.WriteUVariant(zWriter, uint64(len(ruleSet.Rules)))
if err != nil {
return err
}
for _, rule := range ruleSet.Rules {
err = writeRule(bWriter, rule)
err = writeRule(zWriter, rule)
if err != nil {
return err
}
@@ -104,7 +101,7 @@ func Write(writer io.Writer, ruleSet option.PlainRuleSet) error {
return zWriter.Close()
}
func readRule(reader varbin.Reader, recovery bool) (rule option.HeadlessRule, err error) {
func readRule(reader io.Reader, recovery bool) (rule option.HeadlessRule, err error) {
var ruleType uint8
err = binary.Read(reader, binary.BigEndian, &ruleType)
if err != nil {
@@ -112,10 +109,8 @@ func readRule(reader varbin.Reader, recovery bool) (rule option.HeadlessRule, er
}
switch ruleType {
case 0:
rule.Type = C.RuleTypeDefault
rule.DefaultOptions, err = readDefaultRule(reader, recovery)
case 1:
rule.Type = C.RuleTypeLogical
rule.LogicalOptions, err = readLogicalRule(reader, recovery)
default:
err = E.New("unknown rule type: ", ruleType)
@@ -123,7 +118,7 @@ func readRule(reader varbin.Reader, recovery bool) (rule option.HeadlessRule, er
return
}
func writeRule(writer varbin.Writer, rule option.HeadlessRule) error {
func writeRule(writer io.Writer, rule option.HeadlessRule) error {
switch rule.Type {
case C.RuleTypeDefault:
return writeDefaultRule(writer, rule.DefaultOptions)
@@ -134,7 +129,7 @@ func writeRule(writer varbin.Writer, rule option.HeadlessRule) error {
}
}
func readDefaultRule(reader varbin.Reader, recovery bool) (rule option.DefaultHeadlessRule, err error) {
func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadlessRule, err error) {
var lastItemType uint8
for {
var itemType uint8
@@ -212,7 +207,7 @@ func readDefaultRule(reader varbin.Reader, recovery bool) (rule option.DefaultHe
}
}
func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule) error {
func writeDefaultRule(writer io.Writer, rule option.DefaultHeadlessRule) error {
err := binary.Write(writer, binary.BigEndian, uint8(0))
if err != nil {
return err
@@ -256,7 +251,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule) err
if len(rule.SourceIPCIDR) > 0 {
err = writeRuleItemCIDR(writer, ruleItemSourceIPCIDR, rule.SourceIPCIDR)
if err != nil {
return E.Cause(err, "source_ip_cidr")
return E.Cause(err, "source_ipcidr")
}
}
if len(rule.IPCIDR) > 0 {
@@ -330,31 +325,73 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule) err
return nil
}
func readRuleItemString(reader varbin.Reader) ([]string, error) {
return varbin.ReadValue[[]string](reader, binary.BigEndian)
func readRuleItemString(reader io.Reader) ([]string, error) {
length, err := rw.ReadUVariant(reader)
if err != nil {
return nil, err
}
value := make([]string, length)
for i := uint64(0); i < length; i++ {
value[i], err = rw.ReadVString(reader)
if err != nil {
return nil, err
}
}
return value, nil
}
func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) error {
err := writer.WriteByte(itemType)
func writeRuleItemString(writer io.Writer, itemType uint8, value []string) error {
err := binary.Write(writer, binary.BigEndian, itemType)
if err != nil {
return err
}
return varbin.Write(writer, binary.BigEndian, value)
}
func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) {
return varbin.ReadValue[[]uint16](reader, binary.BigEndian)
}
func writeRuleItemUint16(writer varbin.Writer, itemType uint8, value []uint16) error {
err := writer.WriteByte(itemType)
err = rw.WriteUVariant(writer, uint64(len(value)))
if err != nil {
return err
}
return varbin.Write(writer, binary.BigEndian, value)
for _, item := range value {
err = rw.WriteVString(writer, item)
if err != nil {
return err
}
}
return nil
}
func writeRuleItemCIDR(writer varbin.Writer, itemType uint8, value []string) error {
func readRuleItemUint16(reader io.Reader) ([]uint16, error) {
length, err := rw.ReadUVariant(reader)
if err != nil {
return nil, err
}
value := make([]uint16, length)
for i := uint64(0); i < length; i++ {
err = binary.Read(reader, binary.BigEndian, &value[i])
if err != nil {
return nil, err
}
}
return value, nil
}
func writeRuleItemUint16(writer io.Writer, itemType uint8, value []uint16) error {
err := binary.Write(writer, binary.BigEndian, itemType)
if err != nil {
return err
}
err = rw.WriteUVariant(writer, uint64(len(value)))
if err != nil {
return err
}
for _, item := range value {
err = binary.Write(writer, binary.BigEndian, item)
if err != nil {
return err
}
}
return nil
}
func writeRuleItemCIDR(writer io.Writer, itemType uint8, value []string) error {
var builder netipx.IPSetBuilder
for i, prefixString := range value {
prefix, err := netip.ParsePrefix(prefixString)
@@ -380,8 +417,9 @@ func writeRuleItemCIDR(writer varbin.Writer, itemType uint8, value []string) err
return writeIPSet(writer, ipSet)
}
func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.LogicalHeadlessRule, err error) {
mode, err := reader.ReadByte()
func readLogicalRule(reader io.Reader, recovery bool) (logicalRule option.LogicalHeadlessRule, err error) {
var mode uint8
err = binary.Read(reader, binary.BigEndian, &mode)
if err != nil {
return
}
@@ -394,7 +432,7 @@ func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.Lo
err = E.New("unknown logical mode: ", mode)
return
}
length, err := binary.ReadUvarint(reader)
length, err := rw.ReadUVariant(reader)
if err != nil {
return
}
@@ -413,7 +451,7 @@ func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.Lo
return
}
func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule) error {
func writeLogicalRule(writer io.Writer, logicalRule option.LogicalHeadlessRule) error {
err := binary.Write(writer, binary.BigEndian, uint8(1))
if err != nil {
return err
@@ -429,7 +467,7 @@ func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRu
if err != nil {
return err
}
_, err = varbin.WriteUvarint(writer, uint64(len(logicalRule.Rules)))
err = rw.WriteUVariant(writer, uint64(len(logicalRule.Rules)))
if err != nil {
return err
}
+72 -37
View File
@@ -2,13 +2,11 @@ package srs
import (
"encoding/binary"
"io"
"net/netip"
"os"
"unsafe"
"github.com/sagernet/sing/common"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/varbin"
"github.com/sagernet/sing/common/rw"
"go4.org/netipx"
)
@@ -22,57 +20,94 @@ type myIPRange struct {
to netip.Addr
}
type myIPRangeData struct {
From []byte
To []byte
}
func readIPSet(reader varbin.Reader) (*netipx.IPSet, error) {
version, err := reader.ReadByte()
func readIPSet(reader io.Reader) (*netipx.IPSet, error) {
var version uint8
err := binary.Read(reader, binary.BigEndian, &version)
if err != nil {
return nil, err
}
if version != 1 {
return nil, os.ErrInvalid
}
// WTF why using uint64 here
var length uint64
err = binary.Read(reader, binary.BigEndian, &length)
if err != nil {
return nil, err
}
ranges := make([]myIPRangeData, length)
err = varbin.Read(reader, binary.BigEndian, &ranges)
if err != nil {
return nil, err
}
mySet := &myIPSet{
rr: make([]myIPRange, len(ranges)),
rr: make([]myIPRange, length),
}
for i, rangeData := range ranges {
mySet.rr[i].from = M.AddrFromIP(rangeData.From)
mySet.rr[i].to = M.AddrFromIP(rangeData.To)
for i := uint64(0); i < length; i++ {
var (
fromLen uint64
toLen uint64
fromAddr netip.Addr
toAddr netip.Addr
)
fromLen, err = rw.ReadUVariant(reader)
if err != nil {
return nil, err
}
fromBytes := make([]byte, fromLen)
_, err = io.ReadFull(reader, fromBytes)
if err != nil {
return nil, err
}
err = fromAddr.UnmarshalBinary(fromBytes)
if err != nil {
return nil, err
}
toLen, err = rw.ReadUVariant(reader)
if err != nil {
return nil, err
}
toBytes := make([]byte, toLen)
_, err = io.ReadFull(reader, toBytes)
if err != nil {
return nil, err
}
err = toAddr.UnmarshalBinary(toBytes)
if err != nil {
return nil, err
}
mySet.rr[i] = myIPRange{fromAddr, toAddr}
}
return (*netipx.IPSet)(unsafe.Pointer(mySet)), nil
}
func writeIPSet(writer varbin.Writer, set *netipx.IPSet) error {
err := writer.WriteByte(1)
func writeIPSet(writer io.Writer, set *netipx.IPSet) error {
err := binary.Write(writer, binary.BigEndian, uint8(1))
if err != nil {
return err
}
dataList := common.Map((*myIPSet)(unsafe.Pointer(set)).rr, func(rr myIPRange) myIPRangeData {
return myIPRangeData{
From: rr.from.AsSlice(),
To: rr.to.AsSlice(),
mySet := (*myIPSet)(unsafe.Pointer(set))
err = binary.Write(writer, binary.BigEndian, uint64(len(mySet.rr)))
if err != nil {
return err
}
for _, rr := range mySet.rr {
var (
fromBinary []byte
toBinary []byte
)
fromBinary, err = rr.from.MarshalBinary()
if err != nil {
return err
}
})
err = binary.Write(writer, binary.BigEndian, uint64(len(dataList)))
if err != nil {
return err
}
for _, data := range dataList {
err = varbin.Write(writer, binary.BigEndian, data)
err = rw.WriteUVariant(writer, uint64(len(fromBinary)))
if err != nil {
return err
}
_, err = writer.Write(fromBinary)
if err != nil {
return err
}
toBinary, err = rr.to.MarshalBinary()
if err != nil {
return err
}
err = rw.WriteUVariant(writer, uint64(len(toBinary)))
if err != nil {
return err
}
_, err = writer.Write(toBinary)
if err != nil {
return err
}
+1 -12
View File
@@ -105,16 +105,5 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
},
})
config = certmagic.New(cache, *config)
var tlsConfig *tls.Config
if acmeConfig.DisableTLSALPNChallenge || acmeConfig.DNS01Solver != nil {
tlsConfig = &tls.Config{
GetCertificate: config.GetCertificate,
}
} else {
tlsConfig = &tls.Config{
GetCertificate: config.GetCertificate,
NextProtos: []string{ACMETLS1Protocol},
}
}
return tlsConfig, &acmeWrapper{ctx: ctx, cfg: config, cache: cache, domain: options.Domain}, nil
return config.TLSConfig(), &acmeWrapper{ctx: ctx, cfg: config, cache: cache, domain: options.Domain}, nil
}
-3
View File
@@ -1,3 +0,0 @@
package tls
const ACMETLS1Protocol = "acme-tls/1"
+1 -12
View File
@@ -6,7 +6,6 @@ import (
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/badtls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
M "github.com/sagernet/sing/common/metadata"
@@ -43,17 +42,7 @@ func NewClient(ctx context.Context, serverAddress string, options option.Outboun
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
defer cancel()
tlsConn, err := aTLS.ClientHandshake(ctx, conn, config)
if err != nil {
return nil, err
}
readWaitConn, err := badtls.NewReadWaitConn(tlsConn)
if err == nil {
return readWaitConn, nil
} else if err != os.ErrInvalid {
return nil, err
}
return tlsConn, nil
return aTLS.ClientHandshake(ctx, conn, config)
}
type Dialer struct {
+3 -2
View File
@@ -27,10 +27,11 @@ func (c *echClientConfig) DialEarly(ctx context.Context, conn net.PacketConn, ad
return quic.DialEarly(ctx, conn, addr, c.config, config)
}
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config) http.RoundTripper {
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,
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 {
+138 -69
View File
@@ -11,11 +11,12 @@ import (
"strings"
cftls "github.com/sagernet/cloudflare-tls"
"github.com/sagernet/fswatch"
"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 {
@@ -25,8 +26,9 @@ type echServerConfig struct {
key []byte
certificatePath string
keyPath string
watcher *fsnotify.Watcher
echKeyPath string
watcher *fswatch.Watcher
echWatcher *fsnotify.Watcher
}
func (c *echServerConfig) ServerName() string {
@@ -64,84 +66,146 @@ func (c *echServerConfig) Clone() Config {
}
func (c *echServerConfig) Start() error {
err := c.startWatcher()
if err != nil {
c.logger.Warn("create credentials watcher: ", err)
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 {
var watchPath []string
if c.certificatePath != "" {
watchPath = append(watchPath, c.certificatePath)
}
if c.keyPath != "" {
watchPath = append(watchPath, c.keyPath)
}
if c.echKeyPath != "" {
watchPath = append(watchPath, c.echKeyPath)
}
if len(watchPath) == 0 {
return nil
}
watcher, err := fswatch.NewWatcher(fswatch.Options{
Path: watchPath,
Callback: func(path string) {
err := c.credentialsUpdated(path)
if err != nil {
c.logger.Error(E.Cause(err, "reload credentials from ", path))
}
},
})
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
c.watcher = watcher
return nil
}
func (c *echServerConfig) credentialsUpdated(path string) error {
if path == c.certificatePath || path == c.keyPath {
if path == c.certificatePath {
certificate, err := os.ReadFile(c.certificatePath)
if err != nil {
return err
}
c.certificate = certificate
} else {
key, err := os.ReadFile(c.keyPath)
if err != nil {
return err
}
c.key = key
}
keyPair, err := cftls.X509KeyPair(c.certificate, c.key)
if err != nil {
return E.Cause(err, "parse key pair")
}
c.config.Certificates = []cftls.Certificate{keyPair}
c.logger.Info("reloaded TLS certificate")
} else {
echKeyContent, err := os.ReadFile(c.echKeyPath)
if c.certificatePath != "" {
err = watcher.Add(c.certificatePath)
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")
}
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
}
@@ -149,7 +213,12 @@ 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 credentials watcher")
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
+3 -13
View File
@@ -7,7 +7,6 @@ import (
"context"
"crypto/aes"
"crypto/cipher"
"crypto/ecdh"
"crypto/ed25519"
"crypto/hmac"
"crypto/sha256"
@@ -138,21 +137,12 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
hello.SessionId[2] = 1
binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))
copy(hello.SessionId[8:], e.shortID[:])
if debug.Enabled {
fmt.Printf("REALITY hello.sessionId[:16]: %v\n", hello.SessionId[:16])
}
publicKey, err := ecdh.X25519().NewPublicKey(e.publicKey)
if err != nil {
return nil, err
}
ecdheKey := uConn.HandshakeState.State13.EcdheKey
if ecdheKey == nil {
return nil, E.New("nil ecdhe_key")
}
authKey, err := ecdheKey.ECDH(publicKey)
if err != nil {
return nil, err
}
authKey := uConn.HandshakeState.State13.EcdheParams.SharedKey(e.publicKey)
if authKey == nil {
return nil, E.New("nil auth_key")
}
+1 -13
View File
@@ -3,9 +3,7 @@ package tls
import (
"context"
"net"
"os"
"github.com/sagernet/sing-box/common/badtls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
@@ -28,15 +26,5 @@ func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLS
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
defer cancel()
tlsConn, err := aTLS.ServerHandshake(ctx, conn, config)
if err != nil {
return nil, err
}
readWaitConn, err := badtls.NewReadWaitConn(tlsConn)
if err == nil {
return readWaitConn, nil
} else if err != os.ErrInvalid {
return nil, err
}
return tlsConn, nil
return aTLS.ServerHandshake(ctx, conn, config)
}
+46 -31
View File
@@ -7,13 +7,14 @@ import (
"os"
"strings"
"github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter"
"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/ntp"
"github.com/fsnotify/fsnotify"
)
var errInsecureUnused = E.New("tls: insecure unused")
@@ -26,7 +27,7 @@ type STDServerConfig struct {
key []byte
certificatePath string
keyPath string
watcher *fswatch.Watcher
watcher *fsnotify.Watcher
}
func (c *STDServerConfig) ServerName() string {
@@ -38,19 +39,11 @@ func (c *STDServerConfig) SetServerName(serverName string) {
}
func (c *STDServerConfig) NextProtos() []string {
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
return c.config.NextProtos[1:]
} else {
return c.config.NextProtos
}
return c.config.NextProtos
}
func (c *STDServerConfig) SetNextProtos(nextProto []string) {
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
c.config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
} else {
c.config.NextProtos = nextProto
}
c.config.NextProtos = nextProto
}
func (c *STDServerConfig) Config() (*STDConfig, error) {
@@ -87,37 +80,59 @@ func (c *STDServerConfig) Start() error {
}
func (c *STDServerConfig) startWatcher() error {
var watchPath []string
if c.certificatePath != "" {
watchPath = append(watchPath, c.certificatePath)
}
if c.keyPath != "" {
watchPath = append(watchPath, c.keyPath)
}
watcher, err := fswatch.NewWatcher(fswatch.Options{
Path: watchPath,
Callback: func(path string) {
err := c.certificateUpdated(path)
if err != nil {
c.logger.Error(err)
}
},
})
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 *STDServerConfig) certificateUpdated(path string) error {
if path == c.certificatePath {
func (c *STDServerConfig) 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 *STDServerConfig) 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
} else if path == c.keyPath {
}
if c.keyPath != "" {
key, err := os.ReadFile(c.keyPath)
if err != nil {
return E.Cause(err, "reload key from ", c.keyPath)
-10
View File
@@ -219,16 +219,6 @@ func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
switch name {
case "chrome", "":
return utls.HelloChrome_Auto, nil
case "chrome_psk":
return utls.HelloChrome_100_PSK, nil
case "chrome_psk_shuffle":
return utls.HelloChrome_112_PSK_Shuf, nil
case "chrome_padding_psk_shuffle":
return utls.HelloChrome_114_Padding_PSK_Shuf, nil
case "chrome_pq":
return utls.HelloChrome_115_PQ, nil
case "chrome_pq_psk":
return utls.HelloChrome_115_PQ_PSK, nil
case "firefox":
return utls.HelloFirefox_Auto, nil
case "edge":
+3 -3
View File
@@ -13,14 +13,14 @@ var resourcePaths []string
func FindPath(name string) (string, bool) {
name = os.ExpandEnv(name)
if rw.IsFile(name) {
if rw.FileExists(name) {
return name, true
}
for _, dir := range resourcePaths {
if path := filepath.Join(dir, dirName, name); rw.IsFile(path) {
if path := filepath.Join(dir, dirName, name); rw.FileExists(path) {
return path, true
}
if path := filepath.Join(dir, name); rw.IsFile(path) {
if path := filepath.Join(dir, name); rw.FileExists(path) {
return path, true
}
}
+5 -6
View File
@@ -1,10 +1,9 @@
package constant
const (
ProtocolTLS = "tls"
ProtocolHTTP = "http"
ProtocolQUIC = "quic"
ProtocolDNS = "dns"
ProtocolSTUN = "stun"
ProtocolBitTorrent = "bittorrent"
ProtocolTLS = "tls"
ProtocolHTTP = "http"
ProtocolQUIC = "quic"
ProtocolDNS = "dns"
ProtocolSTUN = "stun"
)
-8
View File
@@ -32,12 +32,6 @@ const (
func ProxyDisplayName(proxyType string) string {
switch proxyType {
case TypeTun:
return "TUN"
case TypeRedirect:
return "Redirect"
case TypeTProxy:
return "TProxy"
case TypeDirect:
return "Direct"
case TypeBlock:
@@ -48,8 +42,6 @@ func ProxyDisplayName(proxyType string) string {
return "SOCKS"
case TypeHTTP:
return "HTTP"
case TypeMixed:
return "Mixed"
case TypeShadowsocks:
return "Shadowsocks"
case TypeVMess:
-5
View File
@@ -1,5 +0,0 @@
//go:build with_quic
package constant
const WithQUIC = true
-5
View File
@@ -1,5 +0,0 @@
//go:build !with_quic
package constant
const WithQUIC = false
-1
View File
@@ -11,7 +11,6 @@ const (
)
const (
RuleSetTypeInline = "inline"
RuleSetTypeLocal = "local"
RuleSetTypeRemote = "remote"
RuleSetVersion1 = 1
+11 -14
View File
@@ -3,18 +3,15 @@ package constant
import "time"
const (
TCPKeepAliveInitial = 10 * time.Minute
TCPKeepAliveInterval = 75 * time.Second
TCPTimeout = 5 * time.Second
ReadPayloadTimeout = 300 * time.Millisecond
DNSTimeout = 10 * time.Second
QUICTimeout = 30 * time.Second
STUNTimeout = 15 * time.Second
UDPTimeout = 5 * time.Minute
DefaultURLTestInterval = 3 * time.Minute
DefaultURLTestIdleTimeout = 30 * time.Minute
StartTimeout = 10 * time.Second
StopTimeout = 5 * time.Second
FatalStopTimeout = 10 * time.Second
FakeIPMetadataSaveInterval = 10 * time.Second
TCPTimeout = 5 * time.Second
ReadPayloadTimeout = 300 * time.Millisecond
DNSTimeout = 10 * time.Second
QUICTimeout = 30 * time.Second
STUNTimeout = 15 * time.Second
UDPTimeout = 5 * time.Minute
DefaultURLTestInterval = 3 * time.Minute
DefaultURLTestIdleTimeout = 30 * time.Minute
DefaultStartTimeout = 10 * time.Second
DefaultStopTimeout = 5 * time.Second
DefaultStopFatalTimeout = 10 * time.Second
)
+6 -15
View File
@@ -5,7 +5,6 @@ import (
"net/http/pprof"
"runtime"
"runtime/debug"
"strings"
"github.com/sagernet/sing-box/common/humanize"
"github.com/sagernet/sing-box/log"
@@ -48,20 +47,12 @@ func applyDebugListenOption(options option.DebugOptions) {
encoder.SetIndent("", " ")
encoder.Encode(memObject)
})
r.Route("/pprof", func(r chi.Router) {
r.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
if !strings.HasSuffix(request.URL.Path, "/") {
http.Redirect(writer, request, request.URL.Path+"/", http.StatusMovedPermanently)
} else {
pprof.Index(writer, request)
}
})
r.HandleFunc("/*", pprof.Index)
r.HandleFunc("/cmdline", pprof.Cmdline)
r.HandleFunc("/profile", pprof.Profile)
r.HandleFunc("/symbol", pprof.Symbol)
r.HandleFunc("/trace", pprof.Trace)
})
r.HandleFunc("/pprof", pprof.Index)
r.HandleFunc("/pprof/*", pprof.Index)
r.HandleFunc("/pprof/cmdline", pprof.Cmdline)
r.HandleFunc("/pprof/profile", pprof.Profile)
r.HandleFunc("/pprof/symbol", pprof.Symbol)
r.HandleFunc("/pprof/trace", pprof.Trace)
})
debugHTTPServer = &http.Server{
Addr: options.Listen,
-2
View File
@@ -1,5 +1,3 @@
//go:build linux || darwin
package box
import (
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !(linux || darwin)
//go:build !linux
package box
+93 -752
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -18,7 +18,6 @@ SFA provides an unprivileged TUN implementation through Android VpnService.
| `inet4_address` | :material-check: | / |
| `inet6_address` | :material-check: | / |
| `mtu` | :material-check: | / |
| `gso` | :material-close: | No permission |
| `auto_route` | :material-check: | / |
| `strict_route` | :material-close: | Not implemented |
| `inet4_route_address` | :material-check: | / |
-1
View File
@@ -16,7 +16,6 @@ platform-specific function implementation, such as TUN transparent proxy impleme
* [Play Store](https://play.google.com/store/apps/details?id=io.nekohasekai.sfa)
* [Play Store (Beta)](https://play.google.com/apps/testing/io.nekohasekai.sfa)
* [GitHub Releases](https://github.com/SagerNet/sing-box/releases)
* [F-Droid](https://f-droid.org/packages/io.nekohasekai.sfa/) (Unified signature via reproducible builds)
## :material-source-repository: Source code
+22 -23
View File
@@ -14,29 +14,28 @@ SFI/SFM/SFT allows you to run sing-box through NetworkExtension with Application
SFI/SFM/SFT provides an unprivileged TUN implementation through NetworkExtension.
| TUN inbound option | Available | Note |
|-------------------------------|-------------------|-------------------|
| `interface_name` | :material-close:️ | Managed by Darwin |
| `inet4_address` | :material-check: | / |
| `inet6_address` | :material-check: | / |
| `mtu` | :material-check: | / |
| `gso` | :material-close: | Not implemented |
| `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:️ | Not implemented |
| `exclude_interface` | :material-close:️ | Not implemented |
| `include_uid` | :material-close:️ | Not implemented |
| `exclude_uid` | :material-close:️ | Not implemented |
| `include_android_user` | :material-close:️ | Not implemented |
| `include_package` | :material-close:️ | Not implemented |
| `exclude_package` | :material-close:️ | Not implemented |
| `platform` | :material-check: | / |
| 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 |
|-----------------------|------------------|-----------------------|
+1 -6
View File
@@ -15,12 +15,7 @@ platform-specific function implementation, such as TUN transparent proxy impleme
## :material-download: Download
* [App Store](https://apps.apple.com/us/app/sing-box/id6451272673)
* ~~TestFlight (Beta)~~
TestFlight quota is only available to [sponsors](https://github.com/sponsors/nekohasekai)
(one-time sponsorships are accepted).
Once you donate, you can get an invitation by sending us your Apple ID [via email](mailto:contact@sagernet.org),
or join our Telegram group for sponsors from [@yet_another_sponsor_bot](https://t.me/yet_another_sponsor_bot).
* [TestFlight (Beta)](https://testflight.apple.com/join/AcqO44FH)
## :material-file-download: Download (macOS standalone version)
+5 -5
View File
@@ -2,11 +2,11 @@
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 |
| 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
+2 -2
View File
@@ -4,8 +4,8 @@
| 平台 | 客户端 |
|---------------------------------------|-----------------------------------------|
| :material-android: Android | [sing-box for Android](./android/) |
| :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple/) |
| :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 为卖点的第三方项目。此类项目维护者的动机是获得更多用户,即使它们提供友好的商业
-6
View File
@@ -6,9 +6,3 @@ icon: material/security
sing-box and official graphics clients do not collect or share personal data,
and the data generated by the software is always on your device.
## Android
If your configuration contains `wifi_ssid` or `wifi_bssid` routing rules,
sing-box uses the location permission in the background
to get information about the connected Wi-Fi network to make them work.
+7 -22
View File
@@ -1,11 +1,3 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.9.0"
:material-plus: [client_subnet](#client_subnet)
# DNS
### Structure
@@ -21,7 +13,6 @@ icon: material/new-box
"disable_expire": false,
"independent_cache": false,
"reverse_mapping": false,
"client_subnet": "",
"fakeip": {}
}
}
@@ -30,11 +21,11 @@ icon: material/new-box
### Fields
| Key | Format |
|----------|---------------------------------|
| `server` | List of [DNS Server](./server/) |
| `rules` | List of [DNS Rule](./rule/) |
| `fakeip` | [FakeIP](./fakeip/) |
| Key | Format |
|----------|--------------------------------|
| `server` | List of [DNS Server](./server) |
| `rules` | List of [DNS Rule](./rule) |
| `fakeip` | [FakeIP](./fakeip) |
#### final
@@ -69,12 +60,6 @@ Stores a reverse mapping of IP addresses after responding to a DNS query in orde
Since this process relies on the act of resolving domain names by an application before making a request, it can be
problematic in environments such as macOS, where DNS is proxied and cached by the system.
#### client_subnet
#### fakeip
!!! question "Since sing-box 1.9.0"
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
Can be overrides by `servers.[].client_subnet` or `rules.[].client_subnet`.
[FakeIP](./fakeip) settings.
+5 -24
View File
@@ -1,11 +1,3 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.9.0 中的更改"
:material-plus: [client_subnet](#client_subnet)
# DNS
### 结构
@@ -21,7 +13,6 @@ icon: material/new-box
"disable_expire": false,
"independent_cache": false,
"reverse_mapping": false,
"client_subnet": "",
"fakeip": {}
}
}
@@ -30,10 +21,10 @@ icon: material/new-box
### 字段
| 键 | 格式 |
|----------|-------------------------|
| `server` | 一组 [DNS 服务器](./server/) |
| `rules` | 一组 [DNS 规则](./rule/) |
| 键 | 格式 |
|----------|------------------------|
| `server` | 一组 [DNS 服务器](./server) |
| `rules` | 一组 [DNS 规则](./rule) |
#### final
@@ -67,16 +58,6 @@ icon: material/new-box
由于此过程依赖于应用程序在发出请求之前解析域名的行为,因此在 macOS 等 DNS 由系统代理和缓存的环境中可能会出现问题。
#### client_subnet
!!! question "自 sing-box 1.9.0 起"
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
可以被 `servers.[].client_subnet` 或 `rules.[].client_subnet` 覆盖。
#### fakeip
[FakeIP](./fakeip/) 设置。
[FakeIP](./fakeip) 设置。
+10 -100
View File
@@ -1,21 +1,7 @@
---
icon: material/new-box
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.10.0"
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
:material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
!!! quote "Changes in sing-box 1.9.0"
:material-plus: [geoip](#geoip)
:material-plus: [ip_cidr](#ip_cidr)
:material-plus: [ip_is_private](#ip_is_private)
:material-plus: [client_subnet](#client_subnet)
:material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
!!! quote "Changes in sing-box 1.8.0"
:material-plus: [rule_set](#rule_set)
@@ -67,19 +53,11 @@ icon: material/new-box
"source_geoip": [
"private"
],
"geoip": [
"cn"
],
"source_ip_cidr": [
"10.0.0.0/24",
"192.168.0.1"
],
"source_ip_is_private": false,
"ip_cidr": [
"10.0.0.0/24",
"192.168.0.1"
],
"ip_is_private": false,
"source_port": [
12345
],
@@ -123,18 +101,13 @@ icon: material/new-box
"geoip-cn",
"geosite-cn"
],
// deprecated
"rule_set_ipcidr_match_source": false,
"rule_set_ip_cidr_match_source": false,
"rule_set_ip_cidr_accept_empty": false,
"invert": false,
"outbound": [
"direct"
],
"server": "local",
"disable_cache": false,
"rewrite_ttl": 100,
"client_subnet": "127.0.0.1/24"
"rewrite_ttl": 100
},
{
"type": "logical",
@@ -142,8 +115,7 @@ icon: material/new-box
"rules": [],
"server": "local",
"disable_cache": false,
"rewrite_ttl": 100,
"client_subnet": "127.0.0.1/24"
"rewrite_ttl": 100
}
]
}
@@ -162,15 +134,13 @@ icon: material/new-box
The default rule uses the following matching logic:
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) &&
(`port` || `port_range`) &&
(`source_geoip` || `source_ip_cidr` || `source_ip_is_private`) &&
(`source_geoip` || `source_ip_cidr`) &&
(`source_port` || `source_port_range`) &&
`other fields`
Additionally, included rule-sets can be considered merged rather than as a single rule sub-item.
#### inbound
Tags of [Inbound](/configuration/inbound/).
Tags of [Inbound](/configuration/inbound).
#### ip_version
@@ -294,9 +264,11 @@ Match Clash mode.
#### wifi_ssid
<!-- md:version 1.7.0-beta.4 -->
!!! quote ""
Only supported in graphical clients on Android and Apple platforms.
Only supported in graphical clients on Android and iOS.
Match WiFi SSID.
@@ -304,7 +276,7 @@ Match WiFi SSID.
!!! quote ""
Only supported in graphical clients on Android and Apple platforms.
Only supported in graphical clients on Android and iOS.
Match WiFi BSSID.
@@ -312,23 +284,7 @@ Match WiFi BSSID.
!!! question "Since sing-box 1.8.0"
Match [rule-set](/configuration/route/#rule_set).
#### rule_set_ipcidr_match_source
!!! question "Since sing-box 1.9.0"
!!! failure "Deprecated in sing-box 1.10.0"
`rule_set_ipcidr_match_source` is renamed to `rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0.
Make `ip_cidr` rule items in rule-sets match the source IP.
#### rule_set_ip_cidr_match_source
!!! question "Since sing-box 1.10.0"
Make `ip_cidr` rule items in rule-sets match the source IP.
Match [Rule Set](/configuration/route/#rule_set).
#### invert
@@ -354,52 +310,6 @@ Disable cache and save cache in this query.
Rewrite TTL in DNS responses.
#### client_subnet
!!! question "Since sing-box 1.9.0"
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
Will overrides `dns.client_subnet` and `servers.[].client_subnet`.
### Address Filter Fields
Only takes effect for address requests (A/AAAA/HTTPS). When the query results do not match the address filtering rule items, the current rule will be skipped.
!!! info ""
`ip_cidr` items in included rule-sets also takes effect as an address filtering field.
!!! note ""
Enable `experimental.cache_file.store_rdrc` to cache results.
#### geoip
!!! question "Since sing-box 1.9.0"
Match GeoIP with query response.
#### ip_cidr
!!! question "Since sing-box 1.9.0"
Match IP CIDR with query response.
#### ip_is_private
!!! question "Since sing-box 1.9.0"
Match private IP with query response.
#### rule_set_ip_cidr_accept_empty
!!! question "Since sing-box 1.10.0"
Make `ip_cidr` rules in rule-sets accept empty query response.
### Logical Fields
#### type
+9 -102
View File
@@ -1,21 +1,7 @@
---
icon: material/new-box
icon: material/alert-decagram
---
!!! quote "sing-box 1.10.0 中的更改"
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
:material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
!!! quote "sing-box 1.9.0 中的更改"
:material-plus: [geoip](#geoip)
:material-plus: [ip_cidr](#ip_cidr)
:material-plus: [ip_is_private](#ip_is_private)
:material-plus: [client_subnet](#client_subnet)
:material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
!!! quote "sing-box 1.8.0 中的更改"
:material-plus: [rule_set](#rule_set)
@@ -67,19 +53,10 @@ icon: material/new-box
"source_geoip": [
"private"
],
"geoip": [
"cn"
],
"source_ip_cidr": [
"10.0.0.0/24",
"192.168.0.1"
"10.0.0.0/24"
],
"source_ip_is_private": false,
"ip_cidr": [
"10.0.0.0/24",
"192.168.0.1"
],
"ip_is_private": false,
"source_port": [
12345
],
@@ -123,25 +100,19 @@ icon: material/new-box
"geoip-cn",
"geosite-cn"
],
// 已弃用
"rule_set_ipcidr_match_source": false,
"rule_set_ip_cidr_match_source": false,
"rule_set_ip_cidr_accept_empty": false,
"invert": false,
"outbound": [
"direct"
],
"server": "local",
"disable_cache": false,
"client_subnet": "127.0.0.1/24"
"disable_cache": false
},
{
"type": "logical",
"mode": "and",
"rules": [],
"server": "local",
"disable_cache": false,
"client_subnet": "127.0.0.1/24"
"disable_cache": false
}
]
}
@@ -160,15 +131,13 @@ icon: material/new-box
默认规则使用以下匹配逻辑:
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) &&
(`port` || `port_range`) &&
(`source_geoip` || `source_ip_cidr` || `source_ip_is_private`) &&
(`source_geoip` || `source_ip_cidr`) &&
(`source_port` || `source_port_range`) &&
`other fields`
另外,引用的规则集可视为被合并,而不是作为一个单独的规则子项。
#### inbound
[入站](/zh/configuration/inbound/) 标签.
[入站](/zh/configuration/inbound) 标签.
#### ip_version
@@ -294,7 +263,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
!!! quote ""
仅在 Android 与 Apple 平台图形客户端中支持。
仅在 Android 与 iOS 的图形客户端中支持。
匹配 WiFi SSID。
@@ -302,7 +271,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
!!! quote ""
仅在 Android 与 Apple 平台图形客户端中支持。
仅在 Android 与 iOS 的图形客户端中支持。
匹配 WiFi BSSID。
@@ -312,22 +281,6 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
匹配[规则集](/zh/configuration/route/#rule_set)。
#### rule_set_ipcidr_match_source
!!! question "自 sing-box 1.9.0 起"
!!! failure "已在 sing-box 1.10.0 废弃"
`rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 移除。
使规则集中的 `ip_cidr` 规则匹配源 IP。
#### rule_set_ip_cidr_match_source
!!! question "自 sing-box 1.10.0 起"
使规则集中的 `ip_cidr` 规则匹配源 IP。
#### invert
反选匹配结果。
@@ -352,52 +305,6 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
重写 DNS 回应中的 TTL。
#### client_subnet
!!! question "自 sing-box 1.9.0 起"
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。
### 地址筛选字段
仅对地址请求 (A/AAAA/HTTPS) 生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。
!!! info ""
引用的规则集中的 `ip_cidr` 项也作为地址筛选字段生效。
!!! note ""
启用 `experimental.cache_file.store_rdrc` 以缓存结果。
#### geoip
!!! question "自 sing-box 1.9.0 起"
与查询响应匹配 GeoIP。
#### ip_cidr
!!! question "自 sing-box 1.9.0 起"
与查询响应匹配 IP CIDR。
#### ip_is_private
!!! question "自 sing-box 1.9.0 起"
与查询响应匹配非公开 IP。
#### rule_set_ip_cidr_accept_empty
!!! question "自 sing-box 1.10.0 起"
使规则集中的 `ip_cidr` 规则接受空查询响应。
### 逻辑字段
#### type
@@ -410,4 +317,4 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
#### rules
包括的规则。
包括的规则。
+20 -40
View File
@@ -1,11 +1,3 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.9.0"
:material-plus: [client_subnet](#client_subnet)
### Structure
```json
@@ -13,17 +5,17 @@ icon: material/new-box
"dns": {
"servers": [
{
"tag": "",
"address": "",
"address_resolver": "",
"address_strategy": "",
"strategy": "",
"detour": "",
"client_subnet": ""
"tag": "google",
"address": "tls://dns.google",
"address_resolver": "local",
"address_strategy": "prefer_ipv4",
"strategy": "ipv4_only",
"detour": "direct"
}
]
}
}
```
### Fields
@@ -38,18 +30,18 @@ The tag of the dns server.
The address of the dns server.
| Protocol | Format |
|--------------------------------------|-------------------------------|
| `System` | `local` |
| `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
| `TLS` | `tls://dns.google` |
| `HTTPS` | `https://1.1.1.1/dns-query` |
| `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
| [FakeIP](/configuration/dns/fakeip/) | `fakeip` |
| Protocol | Format |
|-------------------------------------|-------------------------------|
| `System` | `local` |
| `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
| `TLS` | `tls://dns.google` |
| `HTTPS` | `https://1.1.1.1/dns-query` |
| `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
| [FakeIP](/configuration/dns/fakeip) | `fakeip` |
!!! warning ""
@@ -88,22 +80,10 @@ Default domain strategy for resolving the domain names.
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
Take no effect if overridden by other settings.
Take no effect if override by other settings.
#### detour
Tag of an outbound for connecting to the dns server.
Default outbound will be used if empty.
#### client_subnet
!!! question "Since sing-box 1.9.0"
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
Can be overrides by `rules.[].client_subnet`.
Will overrides `dns.client_subnet`.
+19 -39
View File
@@ -1,11 +1,3 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.9.0 中的更改"
:material-plus: [client_subnet](#client_subnet)
### 结构
```json
@@ -13,17 +5,17 @@ icon: material/new-box
"dns": {
"servers": [
{
"tag": "",
"address": "",
"address_resolver": "",
"address_strategy": "",
"strategy": "",
"detour": "",
"client_subnet": ""
"tag": "google",
"address": "tls://dns.google",
"address_resolver": "local",
"address_strategy": "prefer_ipv4",
"strategy": "ipv4_only",
"detour": "direct"
}
]
}
}
```
### 字段
@@ -38,18 +30,18 @@ DNS 服务器的标签。
DNS 服务器的地址。
| 协议 | 格式 |
|--------------------------------------|------------------------------|
| `System` | `local` |
| `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
| `TLS` | `tls://dns.google` |
| `HTTPS` | `https://1.1.1.1/dns-query` |
| `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto` 或 `dhcp://en0` |
| [FakeIP](/configuration/dns/fakeip/) | `fakeip` |
| 协议 | 格式 |
|-------------------------------------|------------------------------|
| `System` | `local` |
| `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
| `TLS` | `tls://dns.google` |
| `HTTPS` | `https://1.1.1.1/dns-query` |
| `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto` 或 `dhcp://en0` |
| [FakeIP](/configuration/dns/fakeip) | `fakeip` |
!!! warning ""
@@ -95,15 +87,3 @@ DNS 服务器的地址。
用于连接到 DNS 服务器的出站的标签。
如果为空,将使用默认出站。
#### client_subnet
!!! question "自 sing-box 1.9.0 起"
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
可以被 `rules.[].client_subnet` 覆盖。
将覆盖 `dns.client_subnet`。
+2 -26
View File
@@ -4,11 +4,6 @@ icon: material/new-box
!!! question "Since sing-box 1.8.0"
!!! quote "Changes in sing-box 1.9.0"
:material-plus: [store_rdrc](#store_rdrc)
:material-plus: [rdrc_timeout](#rdrc_timeout)
### Structure
```json
@@ -16,9 +11,7 @@ icon: material/new-box
"enabled": true,
"path": "",
"cache_id": "",
"store_fakeip": false,
"store_rdrc": false,
"rdrc_timeout": ""
"store_fakeip": false
}
```
@@ -36,23 +29,6 @@ Path to the cache file.
#### cache_id
Identifier in the cache file
Identifier in cache file.
If not empty, configuration specified data will use a separate store keyed by it.
#### store_fakeip
Store fakeip in the cache file
#### store_rdrc
Store rejected DNS response cache in the cache file
The check results of [Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields)
will be cached until expiration.
#### rdrc_timeout
Timeout of rejected DNS response cache.
`7d` is used by default.
@@ -4,11 +4,6 @@ icon: material/new-box
!!! question "自 sing-box 1.8.0 起"
!!! quote "sing-box 1.9.0 中的更改"
:material-plus: [store_rdrc](#store_rdrc)
:material-plus: [rdrc_timeout](#rdrc_timeout)
### 结构
```json
@@ -16,9 +11,7 @@ icon: material/new-box
"enabled": true,
"path": "",
"cache_id": "",
"store_fakeip": false,
"store_rdrc": false,
"rdrc_timeout": ""
"store_fakeip": false
}
```
@@ -37,19 +30,3 @@ icon: material/new-box
缓存文件中的标识符。
如果不为空,配置特定的数据将使用由其键控的单独存储。
#### store_fakeip
将 fakeip 存储在缓存文件中。
#### store_rdrc
将拒绝的 DNS 响应缓存存储在缓存文件中。
[地址筛选 DNS 规则项](/zh/configuration/dns/rule/#_3) 的检查结果将被缓存至过期。
#### rdrc_timeout
拒绝的 DNS 响应缓存超时。
默认使用 `7d`。
@@ -1,3 +1,7 @@
---
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.8.0"
:material-delete-alert: [store_mode](#store_mode)
@@ -1,3 +1,7 @@
---
icon: material/alert-decagram
---
!!! quote "sing-box 1.8.0 中的更改"
:material-delete-alert: [store_mode](#store_mode)
+7 -3
View File
@@ -1,3 +1,7 @@
---
icon: material/alert-decagram
---
# Experimental
!!! quote "Changes in sing-box 1.8.0"
@@ -21,6 +25,6 @@
| Key | Format |
|--------------|----------------------------|
| `cache_file` | [Cache File](./cache-file/) |
| `clash_api` | [Clash API](./clash-api/) |
| `v2ray_api` | [V2Ray API](./v2ray-api/) |
| `cache_file` | [Cache File](./cache-file) |
| `clash_api` | [Clash API](./clash-api) |
| `v2ray_api` | [V2Ray API](./v2ray-api) |
+7 -3
View File
@@ -1,3 +1,7 @@
---
icon: material/alert-decagram
---
# 实验性
!!! quote "sing-box 1.8.0 中的更改"
@@ -21,6 +25,6 @@
| 键 | 格式 |
|--------------|--------------------------|
| `cache_file` | [缓存文件](./cache-file/) |
| `clash_api` | [Clash API](./clash-api/) |
| `v2ray_api` | [V2Ray API](./v2ray-api/) |
| `cache_file` | [缓存文件](./cache-file) |
| `clash_api` | [Clash API](./clash-api) |
| `v2ray_api` | [V2Ray API](./v2ray-api) |
+1 -1
View File
@@ -17,7 +17,7 @@
### Listen Fields
See [Listen Fields](/configuration/shared/listen/) for details.
See [Listen Fields](/configuration/shared/listen) for details.
### Fields
+2 -2
View File
@@ -20,7 +20,7 @@
### Listen Fields
See [Listen Fields](/configuration/shared/listen/) for details.
See [Listen Fields](/configuration/shared/listen) for details.
### Fields
@@ -42,6 +42,6 @@ No authentication required if empty.
!!! warning ""
To work on Android and Apple platforms without privileges, use tun.platform.http_proxy instead.
To work on Android and iOS without privileges, use tun.platform.http_proxy instead.
Automatically set system proxy configuration when start and clean up when stop.

Some files were not shown because too many files have changed in this diff Show More