Compare commits
93 Commits
v1.10.0-be
...
dev-next-w
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47736b27ba | ||
|
|
0b2c7ec35c | ||
|
|
e537c56b6b | ||
|
|
b456aff4ac | ||
|
|
4da652ff02 | ||
|
|
262727ec6c | ||
|
|
81dc9e7698 | ||
|
|
d876077281 | ||
|
|
0df81c297b | ||
|
|
b460484e43 | ||
|
|
0e511791e8 | ||
|
|
9585c53e9f | ||
|
|
d66d5cd457 | ||
|
|
8c143feec8 | ||
|
|
419058f466 | ||
|
|
1a6047a61b | ||
|
|
327bb35ddd | ||
|
|
6ed9a06394 | ||
|
|
b80ec55ba0 | ||
|
|
08718112ae | ||
|
|
956ee361df | ||
|
|
e93d0408be | ||
|
|
137832ff3e | ||
|
|
3ede29fb6d | ||
|
|
82ab68b542 | ||
|
|
e55723d84d | ||
|
|
2f4d2d97f9 | ||
|
|
926d6f769e | ||
|
|
846777cd0c | ||
|
|
06533b7a3b | ||
|
|
4a95558c53 | ||
|
|
e39a28ed5a | ||
|
|
b2c708a3e6 | ||
|
|
a9209bb3e5 | ||
|
|
9dc3bb975a | ||
|
|
3a7acaa92a | ||
|
|
6bebe2483b | ||
|
|
93cf134995 | ||
|
|
ff7d8c9ba8 | ||
|
|
50f07b42f6 | ||
|
|
db3a0c636d | ||
|
|
fec38f85cd | ||
|
|
dcb0141646 | ||
|
|
f4f5a3c925 | ||
|
|
9b8d6c1b73 | ||
|
|
2f776168de | ||
|
|
923d3222b0 | ||
|
|
bda93d516b | ||
|
|
7eec3fb57a | ||
|
|
b1d75812c5 | ||
|
|
d44e7d9834 | ||
|
|
369bc7cea3 | ||
|
|
4b7a83da16 | ||
|
|
0f7154afbd | ||
|
|
a06d10c3bc | ||
|
|
63cc6cc76c | ||
|
|
d55c5b5cab | ||
|
|
b624c2dcc7 | ||
|
|
9415444ebd | ||
|
|
95606191d8 | ||
|
|
e586d9e9bc | ||
|
|
8c7eaa4477 | ||
|
|
8464c8cb7c | ||
|
|
39d7127651 | ||
|
|
e2077009c4 | ||
|
|
700a8eb425 | ||
|
|
3b0cba0852 | ||
|
|
f5554dd8b8 | ||
|
|
4d0362d530 | ||
|
|
97ccd2ca04 | ||
|
|
1ed6654ad4 | ||
|
|
5385f75f53 | ||
|
|
ad97d4e11f | ||
|
|
09d4e91b77 | ||
|
|
3dbdda9555 | ||
|
|
1f4ed6ff8f | ||
|
|
6ddbe19bc0 | ||
|
|
d7205ecc60 | ||
|
|
9e243e0ff9 | ||
|
|
02bc3e0a0a | ||
|
|
87be6dc235 | ||
|
|
c1c30976dc | ||
|
|
9bac18bcd1 | ||
|
|
ceda5cc95d | ||
|
|
27d6b63e71 | ||
|
|
b57abcc73c | ||
|
|
f1147965dd | ||
|
|
45f3234c73 | ||
|
|
aae3fded32 | ||
|
|
090494faf5 | ||
|
|
db5719e22f | ||
|
|
064fb9b873 | ||
|
|
f6a1e123fc |
31
.github/workflows/debug.yml
vendored
31
.github/workflows/debug.yml
vendored
@@ -22,14 +22,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.22
|
go-version: ^1.23
|
||||||
continue-on-error: true
|
|
||||||
- name: Run Test
|
- name: Run Test
|
||||||
run: |
|
run: |
|
||||||
go test -v ./...
|
go test -v ./...
|
||||||
@@ -38,7 +37,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -58,7 +57,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -73,6 +72,26 @@ jobs:
|
|||||||
key: go121-${{ hashFiles('**/go.sum') }}
|
key: go121-${{ hashFiles('**/go.sum') }}
|
||||||
- name: Run Test
|
- name: Run Test
|
||||||
run: make ci_build
|
run: make ci_build
|
||||||
|
build_go122:
|
||||||
|
name: Debug build (Go 1.22)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ~1.22
|
||||||
|
- name: Cache go module
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: go122-${{ hashFiles('**/go.sum') }}
|
||||||
|
- name: Run Test
|
||||||
|
run: make ci_build
|
||||||
cross:
|
cross:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -188,7 +207,7 @@ jobs:
|
|||||||
TAGS: with_clash_api,with_quic
|
TAGS: with_clash_api,with_quic
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
|
|||||||
121
.github/workflows/docker.yml
vendored
121
.github/workflows/docker.yml
vendored
@@ -1,16 +1,93 @@
|
|||||||
name: Build Docker Images
|
name: Publish Docker Images
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- released
|
- published
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
description: "The tag version you want to build"
|
description: "The tag version you want to build"
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY_IMAGE: ghcr.io/sagernet/sing-box
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- linux/amd64
|
||||||
|
- linux/arm/v6
|
||||||
|
- linux/arm/v7
|
||||||
|
- linux/arm64
|
||||||
|
- linux/386
|
||||||
|
- linux/ppc64le
|
||||||
|
- linux/riscv64
|
||||||
|
- linux/s390x
|
||||||
|
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
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
|
with:
|
||||||
|
ref: ${{ steps.ref.outputs.ref }}
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
platform=${{ matrix.platform }}
|
||||||
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
- name: Setup QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Setup Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY_IMAGE }}
|
||||||
|
- name: Build and push by digest
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
platforms: ${{ matrix.platform }}
|
||||||
|
context: .
|
||||||
|
build-args: |
|
||||||
|
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
||||||
|
- name: Export digest
|
||||||
|
run: |
|
||||||
|
mkdir -p /tmp/digests
|
||||||
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
|
touch "/tmp/digests/${digest#sha256:}"
|
||||||
|
- name: Upload digest
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: digests-${{ env.PLATFORM_PAIR }}
|
||||||
|
path: /tmp/digests/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
merge:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- build
|
||||||
steps:
|
steps:
|
||||||
- name: Get commit to build
|
- name: Get commit to build
|
||||||
id: ref
|
id: ref
|
||||||
@@ -29,34 +106,28 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "latest=$latest"
|
echo "latest=$latest"
|
||||||
echo "latest=$latest" >> $GITHUB_OUTPUT
|
echo "latest=$latest" >> $GITHUB_OUTPUT
|
||||||
- name: Checkout
|
- name: Download digests
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ steps.ref.outputs.ref }}
|
path: /tmp/digests
|
||||||
- name: Setup Docker Buildx
|
pattern: digests-*
|
||||||
|
merge-multiple: true
|
||||||
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Setup QEMU for Docker Buildx
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Docker metadata
|
- name: Create manifest list and push
|
||||||
id: metadata
|
working-directory: /tmp/digests
|
||||||
uses: docker/metadata-action@v5
|
run: |
|
||||||
with:
|
docker buildx imagetools create \
|
||||||
images: ghcr.io/sagernet/sing-box
|
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}" \
|
||||||
- name: Build and release Docker images
|
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \
|
||||||
uses: docker/build-push-action@v6
|
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
|
||||||
with:
|
- name: Inspect image
|
||||||
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
|
run: |
|
||||||
context: .
|
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}
|
||||||
target: dist
|
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}
|
||||||
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 }}
|
|
||||||
push: true
|
|
||||||
|
|||||||
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@@ -22,13 +22,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.22
|
go-version: ^1.23
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
4
.github/workflows/linux.yml
vendored
4
.github/workflows/linux.yml
vendored
@@ -10,13 +10,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.22
|
go-version: ^1.23
|
||||||
- name: Extract signing key
|
- name: Extract signing key
|
||||||
run: |-
|
run: |-
|
||||||
mkdir -p $HOME/.gnupg
|
mkdir -p $HOME/.gnupg
|
||||||
|
|||||||
3
.github/workflows/stale.yml
vendored
3
.github/workflows/stale.yml
vendored
@@ -12,4 +12,5 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
||||||
days-before-stale: 60
|
days-before-stale: 60
|
||||||
days-before-close: 5
|
days-before-close: 5
|
||||||
|
exempt-issue-labels: 'bug,enhancement'
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ builds:
|
|||||||
- linux_arm_7
|
- linux_arm_7
|
||||||
- linux_s390x
|
- linux_s390x
|
||||||
- linux_riscv64
|
- linux_riscv64
|
||||||
|
- linux_mips64le
|
||||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
snapshot:
|
snapshot:
|
||||||
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
||||||
@@ -48,10 +49,19 @@ nfpms:
|
|||||||
- src: release/config/config.json
|
- src: release/config/config.json
|
||||||
dst: /etc/sing-box/config.json
|
dst: /etc/sing-box/config.json
|
||||||
type: config
|
type: config
|
||||||
|
|
||||||
- src: release/config/sing-box.service
|
- src: release/config/sing-box.service
|
||||||
dst: /usr/lib/systemd/system/sing-box.service
|
dst: /usr/lib/systemd/system/sing-box.service
|
||||||
- src: release/config/sing-box@.service
|
- src: release/config/sing-box@.service
|
||||||
dst: /usr/lib/systemd/system/sing-box@.service
|
dst: /usr/lib/systemd/system/sing-box@.service
|
||||||
|
|
||||||
|
- src: release/completions/sing-box.bash
|
||||||
|
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
- src: release/completions/sing-box.fish
|
||||||
|
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
|
- src: release/completions/sing-box.zsh
|
||||||
|
dst: /usr/share/zsh/site-functions/_sing-box
|
||||||
|
|
||||||
- src: LICENSE
|
- src: LICENSE
|
||||||
dst: /usr/share/licenses/sing-box/LICENSE
|
dst: /usr/share/licenses/sing-box/LICENSE
|
||||||
deb:
|
deb:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
version: 2
|
||||||
project_name: sing-box
|
project_name: sing-box
|
||||||
builds:
|
builds:
|
||||||
- &template
|
- &template
|
||||||
@@ -7,7 +8,9 @@ builds:
|
|||||||
- -v
|
- -v
|
||||||
- -trimpath
|
- -trimpath
|
||||||
ldflags:
|
ldflags:
|
||||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
|
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
||||||
|
- -s
|
||||||
|
- -buildid=
|
||||||
tags:
|
tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
@@ -23,13 +26,13 @@ builds:
|
|||||||
targets:
|
targets:
|
||||||
- linux_386
|
- linux_386
|
||||||
- linux_amd64_v1
|
- linux_amd64_v1
|
||||||
- linux_amd64_v3
|
|
||||||
- linux_arm64
|
- linux_arm64
|
||||||
|
- linux_arm_6
|
||||||
- linux_arm_7
|
- linux_arm_7
|
||||||
- linux_s390x
|
- linux_s390x
|
||||||
- linux_riscv64
|
- linux_riscv64
|
||||||
|
- linux_mips64le
|
||||||
- windows_amd64_v1
|
- windows_amd64_v1
|
||||||
- windows_amd64_v3
|
|
||||||
- windows_386
|
- windows_386
|
||||||
- windows_arm64
|
- windows_arm64
|
||||||
- darwin_amd64_v1
|
- darwin_amd64_v1
|
||||||
@@ -86,8 +89,6 @@ builds:
|
|||||||
- android_arm64
|
- android_arm64
|
||||||
- android_386
|
- android_386
|
||||||
- android_amd64
|
- android_amd64
|
||||||
snapshot:
|
|
||||||
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
|
||||||
archives:
|
archives:
|
||||||
- &template
|
- &template
|
||||||
id: archive
|
id: archive
|
||||||
@@ -101,7 +102,7 @@ archives:
|
|||||||
wrap_in_directory: true
|
wrap_in_directory: true
|
||||||
files:
|
files:
|
||||||
- LICENSE
|
- LICENSE
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||||
- id: archive-legacy
|
- id: archive-legacy
|
||||||
<<: *template
|
<<: *template
|
||||||
builds:
|
builds:
|
||||||
@@ -110,7 +111,7 @@ archives:
|
|||||||
nfpms:
|
nfpms:
|
||||||
- id: package
|
- id: package
|
||||||
package_name: sing-box
|
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 }}'
|
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||||
builds:
|
builds:
|
||||||
- main
|
- main
|
||||||
homepage: https://sing-box.sagernet.org/
|
homepage: https://sing-box.sagernet.org/
|
||||||
@@ -121,15 +122,26 @@ nfpms:
|
|||||||
- deb
|
- deb
|
||||||
- rpm
|
- rpm
|
||||||
- archlinux
|
- archlinux
|
||||||
|
# - apk
|
||||||
|
# - ipk
|
||||||
priority: extra
|
priority: extra
|
||||||
contents:
|
contents:
|
||||||
- src: release/config/config.json
|
- src: release/config/config.json
|
||||||
dst: /etc/sing-box/config.json
|
dst: /etc/sing-box/config.json
|
||||||
type: config
|
type: config
|
||||||
|
|
||||||
- src: release/config/sing-box.service
|
- src: release/config/sing-box.service
|
||||||
dst: /usr/lib/systemd/system/sing-box.service
|
dst: /usr/lib/systemd/system/sing-box.service
|
||||||
- src: release/config/sing-box@.service
|
- src: release/config/sing-box@.service
|
||||||
dst: /usr/lib/systemd/system/sing-box@.service
|
dst: /usr/lib/systemd/system/sing-box@.service
|
||||||
|
|
||||||
|
- src: release/completions/sing-box.bash
|
||||||
|
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
- src: release/completions/sing-box.fish
|
||||||
|
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
|
- src: release/completions/sing-box.zsh
|
||||||
|
dst: /usr/share/zsh/site-functions/_sing-box
|
||||||
|
|
||||||
- src: LICENSE
|
- src: LICENSE
|
||||||
dst: /usr/share/licenses/sing-box/LICENSE
|
dst: /usr/share/licenses/sing-box/LICENSE
|
||||||
deb:
|
deb:
|
||||||
@@ -141,13 +153,34 @@ nfpms:
|
|||||||
signature:
|
signature:
|
||||||
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
||||||
overrides:
|
overrides:
|
||||||
deb:
|
apk:
|
||||||
conflicts:
|
contents:
|
||||||
- sing-box-beta
|
- src: release/config/config.json
|
||||||
rpm:
|
dst: /etc/sing-box/config.json
|
||||||
conflicts:
|
type: config
|
||||||
- sing-box-beta
|
|
||||||
|
|
||||||
|
- src: release/config/sing-box.initd
|
||||||
|
dst: /etc/init.d/sing-box
|
||||||
|
|
||||||
|
- src: release/completions/sing-box.bash
|
||||||
|
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
- src: release/completions/sing-box.fish
|
||||||
|
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
|
- src: release/completions/sing-box.zsh
|
||||||
|
dst: /usr/share/zsh/site-functions/_sing-box
|
||||||
|
|
||||||
|
- src: LICENSE
|
||||||
|
dst: /usr/share/licenses/sing-box/LICENSE
|
||||||
|
ipk:
|
||||||
|
contents:
|
||||||
|
- src: release/config/config.json
|
||||||
|
dst: /etc/sing-box/config.json
|
||||||
|
type: config
|
||||||
|
|
||||||
|
- src: release/config/openwrt.init
|
||||||
|
dst: /etc/init.d/sing-box
|
||||||
|
- src: release/config/openwrt.conf
|
||||||
|
dst: /etc/config/sing-box
|
||||||
source:
|
source:
|
||||||
enabled: false
|
enabled: false
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS builder
|
FROM --platform=$BUILDPLATFORM golang:1.23-alpine AS builder
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
COPY . /go/src/github.com/sagernet/sing-box
|
COPY . /go/src/github.com/sagernet/sing-box
|
||||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||||
@@ -21,7 +21,7 @@ FROM --platform=$TARGETPLATFORM alpine AS dist
|
|||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
&& apk upgrade \
|
&& apk upgrade \
|
||||||
&& apk add bash tzdata ca-certificates \
|
&& apk add bash tzdata ca-certificates nftables \
|
||||||
&& rm -rf /var/cache/apk/*
|
&& rm -rf /var/cache/apk/*
|
||||||
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
|
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
|
||||||
ENTRYPOINT ["sing-box"]
|
ENTRYPOINT ["sing-box"]
|
||||||
|
|||||||
71
Makefile
71
Makefile
@@ -27,6 +27,9 @@ ci_build:
|
|||||||
go build $(PARAMS) $(MAIN)
|
go build $(PARAMS) $(MAIN)
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
|
generate_completions:
|
||||||
|
go run -v --tags generate,generate_completions $(MAIN)
|
||||||
|
|
||||||
install:
|
install:
|
||||||
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
@@ -66,7 +69,6 @@ release:
|
|||||||
dist/*.deb \
|
dist/*.deb \
|
||||||
dist/*.rpm \
|
dist/*.rpm \
|
||||||
dist/*_amd64.pkg.tar.zst \
|
dist/*_amd64.pkg.tar.zst \
|
||||||
dist/*_amd64v3.pkg.tar.zst \
|
|
||||||
dist/*_arm64.pkg.tar.zst \
|
dist/*_arm64.pkg.tar.zst \
|
||||||
dist/release
|
dist/release
|
||||||
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release
|
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release
|
||||||
@@ -99,10 +101,12 @@ publish_android:
|
|||||||
publish_android_appcenter:
|
publish_android_appcenter:
|
||||||
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadPlayRelease
|
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadPlayRelease
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: find why and remove `-destination 'generic/platform=iOS'`
|
||||||
build_ios:
|
build_ios:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFI.xcarchive && \
|
rm -rf build/SFI.xcarchive && \
|
||||||
xcodebuild archive -scheme SFI -configuration Release -archivePath build/SFI.xcarchive
|
xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
upload_ios_app_store:
|
upload_ios_app_store:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
@@ -113,55 +117,70 @@ release_ios: build_ios upload_ios_app_store
|
|||||||
build_macos:
|
build_macos:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFM.xcarchive && \
|
rm -rf build/SFM.xcarchive && \
|
||||||
xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive
|
xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
upload_macos_app_store:
|
upload_macos_app_store:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath build/SFM.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath build/SFM.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
release_macos: build_macos upload_macos_app_store
|
release_macos: build_macos upload_macos_app_store
|
||||||
|
|
||||||
build_macos_independent:
|
build_macos_standalone:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFT.System.xcarchive && \
|
rm -rf build/SFM.System.xcarchive && \
|
||||||
xcodebuild archive -scheme SFM.System -configuration Release -archivePath build/SFM.System.xcarchive
|
xcodebuild archive -scheme SFM.System -configuration Release -archivePath build/SFM.System.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
notarize_macos_independent:
|
build_macos_dmg:
|
||||||
cd ../sing-box-for-apple && \
|
|
||||||
xcodebuild -exportArchive -archivePath "build/SFM.System.xcarchive" -exportOptionsPlist SFM.System/Upload.plist -allowProvisioningUpdates
|
|
||||||
|
|
||||||
wait_notarize_macos_independent:
|
|
||||||
sleep 60
|
|
||||||
|
|
||||||
export_macos_independent:
|
|
||||||
rm -rf dist/SFM
|
rm -rf dist/SFM
|
||||||
mkdir -p dist/SFM
|
mkdir -p dist/SFM
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportNotarizedApp -archivePath build/SFM.System.xcarchive -exportPath "../sing-box/dist/SFM"
|
rm -rf build/SFM.System && \
|
||||||
|
rm -rf build/SFM.dmg && \
|
||||||
|
xcodebuild -exportArchive \
|
||||||
|
-archivePath "build/SFM.System.xcarchive" \
|
||||||
|
-exportOptionsPlist SFM.System/Export.plist -allowProvisioningUpdates \
|
||||||
|
-exportPath "build/SFM.System" && \
|
||||||
|
create-dmg \
|
||||||
|
--volname "sing-box" \
|
||||||
|
--volicon "build/SFM.System/SFM.app/Contents/Resources/AppIcon.icns" \
|
||||||
|
--icon "SFM.app" 0 0 \
|
||||||
|
--hide-extension "SFM.app" \
|
||||||
|
--app-drop-link 0 0 \
|
||||||
|
--skip-jenkins \
|
||||||
|
--notarize "notarytool-password" \
|
||||||
|
"../sing-box/dist/SFM/SFM.dmg" "build/SFM.System/SFM.app"
|
||||||
|
|
||||||
upload_macos_independent:
|
upload_macos_dmg:
|
||||||
cd dist/SFM && \
|
cd dist/SFM && \
|
||||||
rm -f *.zip && \
|
cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \
|
||||||
zip -ry "SFM-${VERSION}-universal.zip" SFM.app && \
|
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dmg"
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" *.zip
|
|
||||||
|
|
||||||
release_macos_independent: build_macos_independent notarize_macos_independent wait_notarize_macos_independent export_macos_independent upload_macos_independent
|
upload_macos_dsyms:
|
||||||
|
pushd ../sing-box-for-apple/build/SFM.System.xcarchive && \
|
||||||
|
zip -r SFM.dSYMs.zip dSYMs && \
|
||||||
|
mv SFM.dSYMs.zip ../../../sing-box/dist/SFM && \
|
||||||
|
popd && \
|
||||||
|
cd dist/SFM && \
|
||||||
|
cp SFM.dSYMs.zip "SFM-${VERSION}-universal.dSYMs.zip" && \
|
||||||
|
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dSYMs.zip"
|
||||||
|
|
||||||
|
release_macos_standalone: build_macos_standalone build_macos_dmg upload_macos_dmg upload_macos_dsyms
|
||||||
|
|
||||||
build_tvos:
|
build_tvos:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFT.xcarchive && \
|
rm -rf build/SFT.xcarchive && \
|
||||||
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive
|
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
upload_tvos_app_store:
|
upload_tvos_app_store:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
release_tvos: build_tvos upload_tvos_app_store
|
release_tvos: build_tvos upload_tvos_app_store
|
||||||
|
|
||||||
update_apple_version:
|
update_apple_version:
|
||||||
go run ./cmd/internal/update_apple_version
|
go run ./cmd/internal/update_apple_version
|
||||||
|
|
||||||
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_independent
|
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_standalone
|
||||||
|
|
||||||
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
||||||
|
|
||||||
@@ -188,8 +207,8 @@ lib:
|
|||||||
go run ./cmd/internal/build_libbox -target ios
|
go run ./cmd/internal/build_libbox -target ios
|
||||||
|
|
||||||
lib_install:
|
lib_install:
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.3
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.4
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.3
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.4
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
venv/bin/mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ The universal proxy platform.
|
|||||||
|
|
||||||
[](https://repology.org/project/sing-box/versions)
|
[](https://repology.org/project/sing-box/versions)
|
||||||
|
|
||||||
## Support
|
## Documentation
|
||||||
|
|
||||||
https://community.sagernet.org/c/sing-box/
|
https://sing-box.sagernet.org
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConnectionRouter interface {
|
|
||||||
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
|
||||||
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRouteHandler(
|
|
||||||
metadata InboundContext,
|
|
||||||
router ConnectionRouter,
|
|
||||||
logger logger.ContextLogger,
|
|
||||||
) UpstreamHandlerAdapter {
|
|
||||||
return &routeHandlerWrapper{
|
|
||||||
metadata: metadata,
|
|
||||||
router: router,
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRouteContextHandler(
|
|
||||||
router ConnectionRouter,
|
|
||||||
logger logger.ContextLogger,
|
|
||||||
) UpstreamHandlerAdapter {
|
|
||||||
return &routeContextHandlerWrapper{
|
|
||||||
router: router,
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
|
|
||||||
|
|
||||||
type routeHandlerWrapper struct {
|
|
||||||
metadata InboundContext
|
|
||||||
router ConnectionRouter
|
|
||||||
logger logger.ContextLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
|
||||||
myMetadata := w.metadata
|
|
||||||
if metadata.Source.IsValid() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
return w.router.RouteConnection(ctx, conn, myMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
|
||||||
myMetadata := w.metadata
|
|
||||||
if metadata.Source.IsValid() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
|
|
||||||
w.logger.ErrorContext(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
|
|
||||||
|
|
||||||
type routeContextHandlerWrapper struct {
|
|
||||||
router ConnectionRouter
|
|
||||||
logger logger.ContextLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
|
||||||
myMetadata := ContextFrom(ctx)
|
|
||||||
if metadata.Source.IsValid() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
return w.router.RouteConnection(ctx, conn, *myMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
|
||||||
myMetadata := ContextFrom(ctx)
|
|
||||||
if metadata.Source.IsValid() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
|
||||||
w.logger.ErrorContext(ctx, err)
|
|
||||||
}
|
|
||||||
@@ -6,27 +6,53 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
type ConnectionHandler interface {
|
type ConnectionHandler interface {
|
||||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConnectionHandlerEx interface {
|
||||||
|
NewConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use PacketHandlerEx instead
|
||||||
type PacketHandler interface {
|
type PacketHandler interface {
|
||||||
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error
|
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PacketHandlerEx interface {
|
||||||
|
NewPacketEx(buffer *buf.Buffer, source M.Socksaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use OOBPacketHandlerEx instead
|
||||||
type OOBPacketHandler interface {
|
type OOBPacketHandler interface {
|
||||||
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error
|
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OOBPacketHandlerEx interface {
|
||||||
|
NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
type PacketConnectionHandler interface {
|
type PacketConnectionHandler interface {
|
||||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PacketConnectionHandlerEx interface {
|
||||||
|
NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
}
|
||||||
|
|
||||||
type UpstreamHandlerAdapter interface {
|
type UpstreamHandlerAdapter interface {
|
||||||
N.TCPConnectionHandler
|
N.TCPConnectionHandler
|
||||||
N.UDPConnectionHandler
|
N.UDPConnectionHandler
|
||||||
E.Handler
|
E.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpstreamHandlerAdapterEx interface {
|
||||||
|
N.TCPConnectionHandlerEx
|
||||||
|
N.UDPConnectionHandlerEx
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,13 +2,12 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/process"
|
"github.com/sagernet/sing-box/common/process"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Inbound interface {
|
type Inbound interface {
|
||||||
@@ -17,11 +16,19 @@ type Inbound interface {
|
|||||||
Tag() string
|
Tag() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type InjectableInbound interface {
|
type TCPInjectableInbound interface {
|
||||||
Inbound
|
Inbound
|
||||||
Network() []string
|
ConnectionHandlerEx
|
||||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
}
|
||||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
|
||||||
|
type UDPInjectableInbound interface {
|
||||||
|
Inbound
|
||||||
|
PacketConnectionHandlerEx
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundRegistry interface {
|
||||||
|
option.InboundOptionsRegistry
|
||||||
|
CreateInbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Inbound, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type InboundContext struct {
|
type InboundContext struct {
|
||||||
@@ -43,10 +50,15 @@ type InboundContext struct {
|
|||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
InboundDetour string
|
// Deprecated: implement in rule action
|
||||||
LastInbound string
|
InboundDetour string
|
||||||
OriginDestination M.Socksaddr
|
LastInbound string
|
||||||
InboundOptions option.InboundOptions
|
OriginDestination M.Socksaddr
|
||||||
|
// Deprecated
|
||||||
|
InboundOptions option.InboundOptions
|
||||||
|
UDPDisableDomainUnmapping bool
|
||||||
|
DNSServer string
|
||||||
|
|
||||||
DestinationAddresses []netip.Addr
|
DestinationAddresses []netip.Addr
|
||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
@@ -91,15 +103,6 @@ func ContextFrom(ctx context.Context) *InboundContext {
|
|||||||
return metadata.(*InboundContext)
|
return metadata.(*InboundContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AppendContext(ctx context.Context) (context.Context, *InboundContext) {
|
|
||||||
metadata := ContextFrom(ctx)
|
|
||||||
if metadata != nil {
|
|
||||||
return ctx, metadata
|
|
||||||
}
|
|
||||||
metadata = new(InboundContext)
|
|
||||||
return WithContext(ctx, metadata), metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
||||||
var newMetadata InboundContext
|
var newMetadata InboundContext
|
||||||
if metadata := ContextFrom(ctx); metadata != nil {
|
if metadata := ContextFrom(ctx); metadata != nil {
|
||||||
|
|||||||
21
adapter/inbound/adapter.go
Normal file
21
adapter/inbound/adapter.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
type Adapter struct {
|
||||||
|
inboundType string
|
||||||
|
inboundTag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapter(inboundType string, inboundTag string) Adapter {
|
||||||
|
return Adapter{
|
||||||
|
inboundType: inboundType,
|
||||||
|
inboundTag: inboundTag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Type() string {
|
||||||
|
return a.inboundType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Tag() string {
|
||||||
|
return a.inboundTag
|
||||||
|
}
|
||||||
68
adapter/inbound/registry.go
Normal file
68
adapter/inbound/registry.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Inbound, error)
|
||||||
|
|
||||||
|
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||||
|
registry.register(outboundType, func() any {
|
||||||
|
return new(Options)
|
||||||
|
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error) {
|
||||||
|
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.InboundRegistry = (*Registry)(nil)
|
||||||
|
|
||||||
|
type (
|
||||||
|
optionsConstructorFunc func() any
|
||||||
|
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Registry struct {
|
||||||
|
access sync.Mutex
|
||||||
|
optionsType map[string]optionsConstructorFunc
|
||||||
|
constructors map[string]constructorFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry() *Registry {
|
||||||
|
return &Registry{
|
||||||
|
optionsType: make(map[string]optionsConstructorFunc),
|
||||||
|
constructors: make(map[string]constructorFunc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||||
|
r.access.Lock()
|
||||||
|
defer r.access.Unlock()
|
||||||
|
optionsConstructor, loaded := r.optionsType[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return optionsConstructor(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) CreateInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
|
||||||
|
r.access.Lock()
|
||||||
|
defer r.access.Unlock()
|
||||||
|
constructor, loaded := r.constructors[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
|
}
|
||||||
|
return constructor(ctx, router, logger, tag, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||||
|
r.access.Lock()
|
||||||
|
defer r.access.Unlock()
|
||||||
|
r.optionsType[outboundType] = optionsConstructor
|
||||||
|
r.constructors[outboundType] = constructor
|
||||||
|
}
|
||||||
@@ -2,8 +2,9 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,6 +16,9 @@ type Outbound interface {
|
|||||||
Network() []string
|
Network() []string
|
||||||
Dependencies() []string
|
Dependencies() []string
|
||||||
N.Dialer
|
N.Dialer
|
||||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
}
|
||||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
|
||||||
|
type OutboundRegistry interface {
|
||||||
|
option.OutboundOptionsRegistry
|
||||||
|
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
||||||
}
|
}
|
||||||
|
|||||||
45
adapter/outbound/adapter.go
Normal file
45
adapter/outbound/adapter.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Adapter struct {
|
||||||
|
protocol string
|
||||||
|
network []string
|
||||||
|
tag string
|
||||||
|
dependencies []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapter(protocol string, network []string, tag string, dependencies []string) Adapter {
|
||||||
|
return Adapter{
|
||||||
|
protocol: protocol,
|
||||||
|
network: network,
|
||||||
|
tag: tag,
|
||||||
|
dependencies: dependencies,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapterWithDialerOptions(protocol string, network []string, tag string, dialOptions option.DialerOptions) Adapter {
|
||||||
|
var dependencies []string
|
||||||
|
if dialOptions.Detour != "" {
|
||||||
|
dependencies = []string{dialOptions.Detour}
|
||||||
|
}
|
||||||
|
return NewAdapter(protocol, network, tag, dependencies)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Type() string {
|
||||||
|
return a.protocol
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Tag() string {
|
||||||
|
return a.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Network() []string {
|
||||||
|
return a.network
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Dependencies() []string {
|
||||||
|
return a.dependencies
|
||||||
|
}
|
||||||
@@ -9,8 +9,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
@@ -21,42 +19,6 @@ import (
|
|||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
type myOutboundAdapter struct {
|
|
||||||
protocol string
|
|
||||||
network []string
|
|
||||||
router adapter.Router
|
|
||||||
logger log.ContextLogger
|
|
||||||
tag string
|
|
||||||
dependencies []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *myOutboundAdapter) Type() string {
|
|
||||||
return a.protocol
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *myOutboundAdapter) Tag() string {
|
|
||||||
return a.tag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *myOutboundAdapter) Network() []string {
|
|
||||||
return a.network
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *myOutboundAdapter) Dependencies() []string {
|
|
||||||
return a.dependencies
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *myOutboundAdapter) NewError(ctx context.Context, err error) {
|
|
||||||
NewError(a.logger, ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func withDialerDependency(options option.DialerOptions) []string {
|
|
||||||
if options.Detour != "" {
|
|
||||||
return []string{options.Detour}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
|
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
ctx = adapter.WithContext(ctx, &metadata)
|
ctx = adapter.WithContext(ctx, &metadata)
|
||||||
var outConn net.Conn
|
var outConn net.Conn
|
||||||
@@ -69,7 +31,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return N.ReportHandshakeFailure(conn, err)
|
return N.ReportHandshakeFailure(conn, err)
|
||||||
}
|
}
|
||||||
err = N.ReportHandshakeSuccess(conn)
|
err = N.ReportConnHandshakeSuccess(conn, outConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outConn.Close()
|
outConn.Close()
|
||||||
return err
|
return err
|
||||||
@@ -96,7 +58,7 @@ func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dial
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return N.ReportHandshakeFailure(conn, err)
|
return N.ReportHandshakeFailure(conn, err)
|
||||||
}
|
}
|
||||||
err = N.ReportHandshakeSuccess(conn)
|
err = N.ReportConnHandshakeSuccess(conn, outConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outConn.Close()
|
outConn.Close()
|
||||||
return err
|
return err
|
||||||
@@ -117,14 +79,14 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return N.ReportHandshakeFailure(conn, err)
|
return N.ReportHandshakeFailure(conn, err)
|
||||||
}
|
}
|
||||||
err = N.ReportHandshakeSuccess(conn)
|
err = N.ReportPacketConnHandshakeSuccess(conn, outConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outConn.Close()
|
outConn.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if destinationAddress.IsValid() {
|
if destinationAddress.IsValid() {
|
||||||
if metadata.Destination.IsFqdn() {
|
if metadata.Destination.IsFqdn() {
|
||||||
if metadata.InboundOptions.UDPDisableDomainUnmapping {
|
if metadata.UDPDisableDomainUnmapping {
|
||||||
outConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
outConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
||||||
} else {
|
} else {
|
||||||
outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
||||||
@@ -165,7 +127,7 @@ func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return N.ReportHandshakeFailure(conn, err)
|
return N.ReportHandshakeFailure(conn, err)
|
||||||
}
|
}
|
||||||
err = N.ReportHandshakeSuccess(conn)
|
err = N.ReportPacketConnHandshakeSuccess(conn, outConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outConn.Close()
|
outConn.Close()
|
||||||
return err
|
return err
|
||||||
@@ -233,12 +195,3 @@ func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) erro
|
|||||||
}
|
}
|
||||||
return bufio.CopyConn(ctx, conn, serverConn)
|
return bufio.CopyConn(ctx, conn, serverConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewError(logger log.ContextLogger, ctx context.Context, err error) {
|
|
||||||
common.Close(err)
|
|
||||||
if E.IsClosedOrCanceled(err) {
|
|
||||||
logger.DebugContext(ctx, "connection closed: ", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.ErrorContext(ctx, err)
|
|
||||||
}
|
|
||||||
68
adapter/outbound/registry.go
Normal file
68
adapter/outbound/registry.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Outbound, error)
|
||||||
|
|
||||||
|
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||||
|
registry.register(outboundType, func() any {
|
||||||
|
return new(Options)
|
||||||
|
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error) {
|
||||||
|
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.OutboundRegistry = (*Registry)(nil)
|
||||||
|
|
||||||
|
type (
|
||||||
|
optionsConstructorFunc func() any
|
||||||
|
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Registry struct {
|
||||||
|
access sync.Mutex
|
||||||
|
optionsType map[string]optionsConstructorFunc
|
||||||
|
constructors map[string]constructorFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry() *Registry {
|
||||||
|
return &Registry{
|
||||||
|
optionsType: make(map[string]optionsConstructorFunc),
|
||||||
|
constructors: make(map[string]constructorFunc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||||
|
r.access.Lock()
|
||||||
|
defer r.access.Unlock()
|
||||||
|
optionsConstructor, loaded := r.optionsType[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return optionsConstructor(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) {
|
||||||
|
r.access.Lock()
|
||||||
|
defer r.access.Unlock()
|
||||||
|
constructor, loaded := r.constructors[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
|
}
|
||||||
|
return constructor(ctx, router, logger, tag, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||||
|
r.access.Lock()
|
||||||
|
defer r.access.Unlock()
|
||||||
|
r.optionsType[outboundType] = optionsConstructor
|
||||||
|
r.constructors[outboundType] = constructor
|
||||||
|
}
|
||||||
@@ -2,13 +2,17 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/geoip"
|
"github.com/sagernet/sing-box/common/geoip"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
@@ -30,6 +34,8 @@ type Router interface {
|
|||||||
FakeIPStore() FakeIPStore
|
FakeIPStore() FakeIPStore
|
||||||
|
|
||||||
ConnectionRouter
|
ConnectionRouter
|
||||||
|
PreMatch(metadata InboundContext) error
|
||||||
|
ConnectionRouterEx
|
||||||
|
|
||||||
GeoIPReader() *geoip.Reader
|
GeoIPReader() *geoip.Reader
|
||||||
LoadGeosite(code string) (Rule, error)
|
LoadGeosite(code string) (Rule, error)
|
||||||
@@ -66,6 +72,18 @@ type Router interface {
|
|||||||
ResetNetwork() error
|
ResetNetwork() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
type ConnectionRouter interface {
|
||||||
|
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
|
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionRouterEx interface {
|
||||||
|
ConnectionRouter
|
||||||
|
RouteConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
}
|
||||||
|
|
||||||
func ContextWithRouter(ctx context.Context, router Router) context.Context {
|
func ContextWithRouter(ctx context.Context, router Router) context.Context {
|
||||||
return service.ContextWith(ctx, router)
|
return service.ContextWith(ctx, router)
|
||||||
}
|
}
|
||||||
@@ -74,31 +92,9 @@ func RouterFromContext(ctx context.Context) Router {
|
|||||||
return service.FromContext[Router](ctx)
|
return service.FromContext[Router](ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeadlessRule interface {
|
|
||||||
Match(metadata *InboundContext) bool
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Rule interface {
|
|
||||||
HeadlessRule
|
|
||||||
Service
|
|
||||||
Type() string
|
|
||||||
UpdateGeosite() error
|
|
||||||
Outbound() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type DNSRule interface {
|
|
||||||
Rule
|
|
||||||
DisableCache() bool
|
|
||||||
RewriteTTL() *uint32
|
|
||||||
ClientSubnet() *netip.Prefix
|
|
||||||
WithAddressLimit() bool
|
|
||||||
MatchAddressLimit(metadata *InboundContext) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type RuleSet interface {
|
type RuleSet interface {
|
||||||
Name() string
|
Name() string
|
||||||
StartContext(ctx context.Context, startContext RuleSetStartContext) error
|
StartContext(ctx context.Context, startContext *HTTPStartContext) error
|
||||||
PostStart() error
|
PostStart() error
|
||||||
Metadata() RuleSetMetadata
|
Metadata() RuleSetMetadata
|
||||||
ExtractIPSet() []*netipx.IPSet
|
ExtractIPSet() []*netipx.IPSet
|
||||||
@@ -118,10 +114,42 @@ type RuleSetMetadata struct {
|
|||||||
ContainsWIFIRule bool
|
ContainsWIFIRule bool
|
||||||
ContainsIPCIDRRule bool
|
ContainsIPCIDRRule bool
|
||||||
}
|
}
|
||||||
|
type HTTPStartContext struct {
|
||||||
|
access sync.Mutex
|
||||||
|
httpClientCache map[string]*http.Client
|
||||||
|
}
|
||||||
|
|
||||||
type RuleSetStartContext interface {
|
func NewHTTPStartContext() *HTTPStartContext {
|
||||||
HTTPClient(detour string, dialer N.Dialer) *http.Client
|
return &HTTPStartContext{
|
||||||
Close()
|
httpClientCache: make(map[string]*http.Client),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Client {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
if httpClient, loaded := c.httpClientCache[detour]; loaded {
|
||||||
|
return httpClient
|
||||||
|
}
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
TLSHandshakeTimeout: C.TCPTimeout,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.httpClientCache[detour] = httpClient
|
||||||
|
return httpClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPStartContext) Close() {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
for _, client := range c.httpClientCache {
|
||||||
|
client.CloseIdleConnections()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type InterfaceUpdateListener interface {
|
type InterfaceUpdateListener interface {
|
||||||
|
|||||||
38
adapter/rule.go
Normal file
38
adapter/rule.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HeadlessRule interface {
|
||||||
|
Match(metadata *InboundContext) bool
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rule interface {
|
||||||
|
HeadlessRule
|
||||||
|
Service
|
||||||
|
Type() string
|
||||||
|
UpdateGeosite() error
|
||||||
|
Action() RuleAction
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSRule interface {
|
||||||
|
Rule
|
||||||
|
WithAddressLimit() bool
|
||||||
|
MatchAddressLimit(metadata *InboundContext) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleAction interface {
|
||||||
|
Type() string
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsFinalAction(action RuleAction) bool {
|
||||||
|
switch action.Type() {
|
||||||
|
case C.RuleActionTypeSniff, C.RuleActionTypeResolve:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,112 +4,165 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
ConnectionHandlerFuncEx = func(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
PacketConnectionHandlerFuncEx = func(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewUpstreamHandler(
|
func NewUpstreamHandlerEx(
|
||||||
metadata InboundContext,
|
metadata InboundContext,
|
||||||
connectionHandler ConnectionHandlerFunc,
|
connectionHandler ConnectionHandlerFuncEx,
|
||||||
packetHandler PacketConnectionHandlerFunc,
|
packetHandler PacketConnectionHandlerFuncEx,
|
||||||
errorHandler E.Handler,
|
) UpstreamHandlerAdapterEx {
|
||||||
) UpstreamHandlerAdapter {
|
return &myUpstreamHandlerWrapperEx{
|
||||||
return &myUpstreamHandlerWrapper{
|
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
connectionHandler: connectionHandler,
|
connectionHandler: connectionHandler,
|
||||||
packetHandler: packetHandler,
|
packetHandler: packetHandler,
|
||||||
errorHandler: errorHandler,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
|
var _ UpstreamHandlerAdapterEx = (*myUpstreamHandlerWrapperEx)(nil)
|
||||||
|
|
||||||
type myUpstreamHandlerWrapper struct {
|
type myUpstreamHandlerWrapperEx struct {
|
||||||
metadata InboundContext
|
metadata InboundContext
|
||||||
connectionHandler ConnectionHandlerFunc
|
connectionHandler ConnectionHandlerFuncEx
|
||||||
packetHandler PacketConnectionHandlerFunc
|
packetHandler PacketConnectionHandlerFuncEx
|
||||||
errorHandler E.Handler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
func (w *myUpstreamHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := w.metadata
|
myMetadata := w.metadata
|
||||||
if metadata.Source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = metadata.Source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
if metadata.Destination.IsValid() {
|
if destination.IsValid() {
|
||||||
myMetadata.Destination = metadata.Destination
|
myMetadata.Destination = destination
|
||||||
}
|
}
|
||||||
return w.connectionHandler(ctx, conn, myMetadata)
|
w.connectionHandler(ctx, conn, myMetadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
func (w *myUpstreamHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := w.metadata
|
myMetadata := w.metadata
|
||||||
if metadata.Source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = metadata.Source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
if metadata.Destination.IsValid() {
|
if destination.IsValid() {
|
||||||
myMetadata.Destination = metadata.Destination
|
myMetadata.Destination = destination
|
||||||
}
|
}
|
||||||
return w.packetHandler(ctx, conn, myMetadata)
|
w.packetHandler(ctx, conn, myMetadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
var _ UpstreamHandlerAdapterEx = (*myUpstreamContextHandlerWrapperEx)(nil)
|
||||||
w.errorHandler.NewError(ctx, err)
|
|
||||||
|
type myUpstreamContextHandlerWrapperEx struct {
|
||||||
|
connectionHandler ConnectionHandlerFuncEx
|
||||||
|
packetHandler PacketConnectionHandlerFuncEx
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
func NewUpstreamContextHandlerEx(
|
||||||
return M.Metadata{
|
connectionHandler ConnectionHandlerFuncEx,
|
||||||
Source: metadata.Source,
|
packetHandler PacketConnectionHandlerFuncEx,
|
||||||
Destination: metadata.Destination,
|
) UpstreamHandlerAdapterEx {
|
||||||
}
|
return &myUpstreamContextHandlerWrapperEx{
|
||||||
}
|
|
||||||
|
|
||||||
type myUpstreamContextHandlerWrapper struct {
|
|
||||||
connectionHandler ConnectionHandlerFunc
|
|
||||||
packetHandler PacketConnectionHandlerFunc
|
|
||||||
errorHandler E.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUpstreamContextHandler(
|
|
||||||
connectionHandler ConnectionHandlerFunc,
|
|
||||||
packetHandler PacketConnectionHandlerFunc,
|
|
||||||
errorHandler E.Handler,
|
|
||||||
) UpstreamHandlerAdapter {
|
|
||||||
return &myUpstreamContextHandlerWrapper{
|
|
||||||
connectionHandler: connectionHandler,
|
connectionHandler: connectionHandler,
|
||||||
packetHandler: packetHandler,
|
packetHandler: packetHandler,
|
||||||
errorHandler: errorHandler,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := ContextFrom(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
if metadata.Source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = metadata.Source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
if metadata.Destination.IsValid() {
|
if destination.IsValid() {
|
||||||
myMetadata.Destination = metadata.Destination
|
myMetadata.Destination = destination
|
||||||
}
|
}
|
||||||
return w.connectionHandler(ctx, conn, *myMetadata)
|
w.connectionHandler(ctx, conn, *myMetadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := ContextFrom(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
if metadata.Source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = metadata.Source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
if metadata.Destination.IsValid() {
|
if destination.IsValid() {
|
||||||
myMetadata.Destination = metadata.Destination
|
myMetadata.Destination = destination
|
||||||
}
|
}
|
||||||
return w.packetHandler(ctx, conn, *myMetadata)
|
w.packetHandler(ctx, conn, *myMetadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
func NewRouteHandlerEx(
|
||||||
w.errorHandler.NewError(ctx, err)
|
metadata InboundContext,
|
||||||
|
router ConnectionRouterEx,
|
||||||
|
) UpstreamHandlerAdapterEx {
|
||||||
|
return &routeHandlerWrapperEx{
|
||||||
|
metadata: metadata,
|
||||||
|
router: router,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapterEx = (*routeHandlerWrapperEx)(nil)
|
||||||
|
|
||||||
|
type routeHandlerWrapperEx struct {
|
||||||
|
metadata InboundContext
|
||||||
|
router ConnectionRouterEx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routeHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
|
if source.IsValid() {
|
||||||
|
r.metadata.Source = source
|
||||||
|
}
|
||||||
|
if destination.IsValid() {
|
||||||
|
r.metadata.Destination = destination
|
||||||
|
}
|
||||||
|
r.router.RouteConnectionEx(ctx, conn, r.metadata, onClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routeHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
|
if source.IsValid() {
|
||||||
|
r.metadata.Source = source
|
||||||
|
}
|
||||||
|
if destination.IsValid() {
|
||||||
|
r.metadata.Destination = destination
|
||||||
|
}
|
||||||
|
r.router.RoutePacketConnectionEx(ctx, conn, r.metadata, onClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouteContextHandlerEx(
|
||||||
|
router ConnectionRouterEx,
|
||||||
|
) UpstreamHandlerAdapterEx {
|
||||||
|
return &routeContextHandlerWrapperEx{
|
||||||
|
router: router,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapterEx = (*routeContextHandlerWrapperEx)(nil)
|
||||||
|
|
||||||
|
type routeContextHandlerWrapperEx struct {
|
||||||
|
router ConnectionRouterEx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
|
metadata := ContextFrom(ctx)
|
||||||
|
if source.IsValid() {
|
||||||
|
metadata.Source = source
|
||||||
|
}
|
||||||
|
if destination.IsValid() {
|
||||||
|
metadata.Destination = destination
|
||||||
|
}
|
||||||
|
r.router.RouteConnectionEx(ctx, conn, *metadata, onClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
|
metadata := ContextFrom(ctx)
|
||||||
|
if source.IsValid() {
|
||||||
|
metadata.Source = source
|
||||||
|
}
|
||||||
|
if destination.IsValid() {
|
||||||
|
metadata.Destination = destination
|
||||||
|
}
|
||||||
|
r.router.RoutePacketConnectionEx(ctx, conn, *metadata, onClose)
|
||||||
}
|
}
|
||||||
|
|||||||
216
adapter/upstream_legacy.go
Normal file
216
adapter/upstream_legacy.go
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Deprecated
|
||||||
|
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
|
// Deprecated
|
||||||
|
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
|
)
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
func NewUpstreamHandler(
|
||||||
|
metadata InboundContext,
|
||||||
|
connectionHandler ConnectionHandlerFunc,
|
||||||
|
packetHandler PacketConnectionHandlerFunc,
|
||||||
|
errorHandler E.Handler,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &myUpstreamHandlerWrapper{
|
||||||
|
metadata: metadata,
|
||||||
|
connectionHandler: connectionHandler,
|
||||||
|
packetHandler: packetHandler,
|
||||||
|
errorHandler: errorHandler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
type myUpstreamHandlerWrapper struct {
|
||||||
|
metadata InboundContext
|
||||||
|
connectionHandler ConnectionHandlerFunc
|
||||||
|
packetHandler PacketConnectionHandlerFunc
|
||||||
|
errorHandler E.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
myMetadata := w.metadata
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.connectionHandler(ctx, conn, myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
|
myMetadata := w.metadata
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.packetHandler(ctx, conn, myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.errorHandler.NewError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||||
|
return M.Metadata{
|
||||||
|
Source: metadata.Source,
|
||||||
|
Destination: metadata.Destination,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
type myUpstreamContextHandlerWrapper struct {
|
||||||
|
connectionHandler ConnectionHandlerFunc
|
||||||
|
packetHandler PacketConnectionHandlerFunc
|
||||||
|
errorHandler E.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
func NewUpstreamContextHandler(
|
||||||
|
connectionHandler ConnectionHandlerFunc,
|
||||||
|
packetHandler PacketConnectionHandlerFunc,
|
||||||
|
errorHandler E.Handler,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &myUpstreamContextHandlerWrapper{
|
||||||
|
connectionHandler: connectionHandler,
|
||||||
|
packetHandler: packetHandler,
|
||||||
|
errorHandler: errorHandler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
myMetadata := ContextFrom(ctx)
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.connectionHandler(ctx, conn, *myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
|
myMetadata := ContextFrom(ctx)
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.packetHandler(ctx, conn, *myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.errorHandler.NewError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
func NewRouteHandler(
|
||||||
|
metadata InboundContext,
|
||||||
|
router ConnectionRouter,
|
||||||
|
logger logger.ContextLogger,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &routeHandlerWrapper{
|
||||||
|
metadata: metadata,
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
func NewRouteContextHandler(
|
||||||
|
router ConnectionRouter,
|
||||||
|
logger logger.ContextLogger,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &routeContextHandlerWrapper{
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
type routeHandlerWrapper struct {
|
||||||
|
metadata InboundContext
|
||||||
|
router ConnectionRouter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
myMetadata := w.metadata
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RouteConnection(ctx, conn, myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
|
myMetadata := w.metadata
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.logger.ErrorContext(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
type routeContextHandlerWrapper struct {
|
||||||
|
router ConnectionRouter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
myMetadata := ContextFrom(ctx)
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RouteConnection(ctx, conn, *myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
|
myMetadata := ContextFrom(ctx)
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.logger.ErrorContext(ctx, err)
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,8 +15,7 @@ type V2RayServerTransport interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type V2RayServerTransportHandler interface {
|
type V2RayServerTransportHandler interface {
|
||||||
N.TCPConnectionHandler
|
N.TCPConnectionHandlerEx
|
||||||
E.Handler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type V2RayClientTransport interface {
|
type V2RayClientTransport interface {
|
||||||
|
|||||||
91
box.go
91
box.go
@@ -14,10 +14,9 @@ import (
|
|||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/inbound"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/outbound"
|
"github.com/sagernet/sing-box/protocol/direct"
|
||||||
"github.com/sagernet/sing-box/route"
|
"github.com/sagernet/sing-box/route"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -44,16 +43,37 @@ type Box struct {
|
|||||||
type Options struct {
|
type Options struct {
|
||||||
option.Options
|
option.Options
|
||||||
Context context.Context
|
Context context.Context
|
||||||
PlatformInterface platform.Interface
|
|
||||||
PlatformLogWriter log.PlatformWriter
|
PlatformLogWriter log.PlatformWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Context(ctx context.Context, inboundRegistry adapter.InboundRegistry, outboundRegistry adapter.OutboundRegistry) context.Context {
|
||||||
|
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||||
|
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.InboundRegistry](ctx, inboundRegistry)
|
||||||
|
}
|
||||||
|
if service.FromContext[option.OutboundOptionsRegistry](ctx) == nil ||
|
||||||
|
service.FromContext[adapter.OutboundRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry)
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
func New(options Options) (*Box, error) {
|
func New(options Options) (*Box, error) {
|
||||||
createdAt := time.Now()
|
createdAt := time.Now()
|
||||||
ctx := options.Context
|
ctx := options.Context
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
}
|
}
|
||||||
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
|
if inboundRegistry == nil {
|
||||||
|
return nil, E.New("missing inbound registry in context")
|
||||||
|
}
|
||||||
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
|
if outboundRegistry == nil {
|
||||||
|
return nil, E.New("missing outbound registry in context")
|
||||||
|
}
|
||||||
ctx = service.ContextWithDefaultRegistry(ctx)
|
ctx = service.ContextWithDefaultRegistry(ctx)
|
||||||
ctx = pause.WithDefaultManager(ctx)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
@@ -70,8 +90,9 @@ func New(options Options) (*Box, error) {
|
|||||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||||
needV2RayAPI = true
|
needV2RayAPI = true
|
||||||
}
|
}
|
||||||
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
var defaultLogWriter io.Writer
|
var defaultLogWriter io.Writer
|
||||||
if options.PlatformInterface != nil {
|
if platformInterface != nil {
|
||||||
defaultLogWriter = io.Discard
|
defaultLogWriter = io.Discard
|
||||||
}
|
}
|
||||||
logFactory, err := log.New(log.Options{
|
logFactory, err := log.New(log.Options{
|
||||||
@@ -92,64 +113,92 @@ func New(options Options) (*Box, error) {
|
|||||||
common.PtrValueOrDefault(options.DNS),
|
common.PtrValueOrDefault(options.DNS),
|
||||||
common.PtrValueOrDefault(options.NTP),
|
common.PtrValueOrDefault(options.NTP),
|
||||||
options.Inbounds,
|
options.Inbounds,
|
||||||
options.PlatformInterface,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse route options")
|
return nil, E.Cause(err, "parse route options")
|
||||||
}
|
}
|
||||||
|
//nolint:staticcheck
|
||||||
|
if len(options.LegacyInbounds) > 0 {
|
||||||
|
for _, legacyInbound := range options.LegacyInbounds {
|
||||||
|
options.Inbounds = append(options.Inbounds, option.Inbound{
|
||||||
|
Type: legacyInbound.Type,
|
||||||
|
Tag: legacyInbound.Tag,
|
||||||
|
Options: common.Must1(legacyInbound.RawOptions()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
|
inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
|
||||||
|
//nolint:staticcheck
|
||||||
|
if len(options.LegacyOutbounds) > 0 {
|
||||||
|
for _, legacyOutbound := range options.LegacyOutbounds {
|
||||||
|
options.Outbounds = append(options.Outbounds, option.Outbound{
|
||||||
|
Type: legacyOutbound.Type,
|
||||||
|
Tag: legacyOutbound.Tag,
|
||||||
|
Options: common.Must1(legacyOutbound.RawOptions()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
|
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
|
||||||
for i, inboundOptions := range options.Inbounds {
|
for i, inboundOptions := range options.Inbounds {
|
||||||
var in adapter.Inbound
|
var currentInbound adapter.Inbound
|
||||||
var tag string
|
var tag string
|
||||||
if inboundOptions.Tag != "" {
|
if inboundOptions.Tag != "" {
|
||||||
tag = inboundOptions.Tag
|
tag = inboundOptions.Tag
|
||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
in, err = inbound.New(
|
currentInbound, err = inboundRegistry.CreateInbound(
|
||||||
ctx,
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
inboundOptions,
|
inboundOptions.Type,
|
||||||
options.PlatformInterface,
|
inboundOptions.Options,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse inbound[", i, "]")
|
return nil, E.Cause(err, "parse inbound[", i, "]")
|
||||||
}
|
}
|
||||||
inbounds = append(inbounds, in)
|
inbounds = append(inbounds, currentInbound)
|
||||||
}
|
}
|
||||||
for i, outboundOptions := range options.Outbounds {
|
for i, outboundOptions := range options.Outbounds {
|
||||||
var out adapter.Outbound
|
var currentOutbound adapter.Outbound
|
||||||
var tag string
|
var tag string
|
||||||
if outboundOptions.Tag != "" {
|
if outboundOptions.Tag != "" {
|
||||||
tag = outboundOptions.Tag
|
tag = outboundOptions.Tag
|
||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
out, err = outbound.New(
|
outboundCtx := ctx
|
||||||
ctx,
|
if tag != "" {
|
||||||
|
// TODO: remove this
|
||||||
|
outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{
|
||||||
|
Outbound: tag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
currentOutbound, err = outboundRegistry.CreateOutbound(
|
||||||
|
outboundCtx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
outboundOptions)
|
outboundOptions.Type,
|
||||||
|
outboundOptions.Options,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse outbound[", i, "]")
|
return nil, E.Cause(err, "parse outbound[", i, "]")
|
||||||
}
|
}
|
||||||
outbounds = append(outbounds, out)
|
outbounds = append(outbounds, currentOutbound)
|
||||||
}
|
}
|
||||||
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
||||||
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"})
|
defaultOutbound, cErr := direct.NewOutbound(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.DirectOutboundOptions{})
|
||||||
common.Must(oErr)
|
common.Must(cErr)
|
||||||
outbounds = append(outbounds, out)
|
outbounds = append(outbounds, defaultOutbound)
|
||||||
return out
|
return defaultOutbound
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if options.PlatformInterface != nil {
|
if platformInterface != nil {
|
||||||
err = options.PlatformInterface.Initialize(ctx, router)
|
err = platformInterface.Initialize(ctx, router)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize platform interface")
|
return nil, E.Cause(err, "initialize platform interface")
|
||||||
}
|
}
|
||||||
|
|||||||
Submodule clients/android updated: 440aaa9a1a...45a1f5f0aa
Submodule clients/apple updated: aa4ce98421...c7d9b49de7
@@ -58,7 +58,7 @@ func FindSDK() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findNDK() bool {
|
func findNDK() bool {
|
||||||
const fixedVersion = "26.2.11394342"
|
const fixedVersion = "27.2.12479018"
|
||||||
const versionFile = "source.properties"
|
const versionFile = "source.properties"
|
||||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
||||||
androidNDKPath = fixedPath
|
androidNDKPath = fixedPath
|
||||||
@@ -86,7 +86,7 @@ func findNDK() bool {
|
|||||||
})
|
})
|
||||||
for _, versionName := range versionNames {
|
for _, versionName := range versionNames {
|
||||||
currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName)
|
currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName)
|
||||||
if rw.IsFile(filepath.Join(androidSDKPath, versionFile)) {
|
if rw.IsFile(filepath.Join(currentNDKPath, versionFile)) {
|
||||||
androidNDKPath = currentNDKPath
|
androidNDKPath = currentNDKPath
|
||||||
log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion)
|
log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion)
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ func main() {
|
|||||||
common.Must(decoder.Decode(&project))
|
common.Must(decoder.Decode(&project))
|
||||||
objectsMap := project["objects"].(map[string]any)
|
objectsMap := project["objects"].(map[string]any)
|
||||||
projectContent := string(common.Must1(os.ReadFile("sing-box.xcodeproj/project.pbxproj")))
|
projectContent := string(common.Must1(os.ReadFile("sing-box.xcodeproj/project.pbxproj")))
|
||||||
newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfa"}, newVersion.VersionString())
|
newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfavt"}, newVersion.VersionString())
|
||||||
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfa.independent", "io.nekohasekai.sfa.system"}, newVersion.String())
|
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfavt.standalone", "io.nekohasekai.sfavt.system"}, newVersion.String())
|
||||||
if updated0 || updated1 {
|
if updated0 || updated1 {
|
||||||
log.Info("updated version to ", newVersion.VersionString(), " (", newVersion.String(), ")")
|
log.Info("updated version to ", newVersion.VersionString(), " (", newVersion.String(), ")")
|
||||||
}
|
}
|
||||||
|
|||||||
73
cmd/sing-box/cmd.go
Normal file
73
cmd/sing-box/cmd.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box"
|
||||||
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
|
"github.com/sagernet/sing-box/include"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalCtx context.Context
|
||||||
|
configPaths []string
|
||||||
|
configDirectories []string
|
||||||
|
workingDir string
|
||||||
|
disableColor bool
|
||||||
|
)
|
||||||
|
|
||||||
|
var mainCommand = &cobra.Command{
|
||||||
|
Use: "sing-box",
|
||||||
|
PersistentPreRun: preRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path")
|
||||||
|
mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path")
|
||||||
|
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
|
||||||
|
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
err = os.Chdir(workingDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||||
|
configPaths = append(configPaths, "config.json")
|
||||||
|
}
|
||||||
|
globalCtx = service.ContextWith(globalCtx, deprecated.NewEnvManager(log.StdLogger()))
|
||||||
|
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry())
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ func format() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, optionsEntry := range optionsList {
|
for _, optionsEntry := range optionsList {
|
||||||
optionsEntry.options, err = badjson.Omitempty(optionsEntry.options)
|
optionsEntry.options, err = badjson.Omitempty(context.TODO(), optionsEntry.options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,29 +68,19 @@ func merge(outputPath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mergePathResources(options *option.Options) error {
|
func mergePathResources(options *option.Options) error {
|
||||||
for index, inbound := range options.Inbounds {
|
for _, inbound := range options.Inbounds {
|
||||||
rawOptions, err := inbound.RawOptions()
|
if tlsOptions, containsTLSOptions := inbound.Options.(option.InboundTLSOptionsWrapper); containsTLSOptions {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if tlsOptions, containsTLSOptions := rawOptions.(option.InboundTLSOptionsWrapper); containsTLSOptions {
|
|
||||||
tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
|
tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
|
||||||
}
|
}
|
||||||
options.Inbounds[index] = inbound
|
|
||||||
}
|
}
|
||||||
for index, outbound := range options.Outbounds {
|
for _, outbound := range options.Outbounds {
|
||||||
rawOptions, err := outbound.RawOptions()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch outbound.Type {
|
switch outbound.Type {
|
||||||
case C.TypeSSH:
|
case C.TypeSSH:
|
||||||
outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions)
|
mergeSSHOutboundOptions(outbound.Options.(*option.SSHOutboundOptions))
|
||||||
}
|
}
|
||||||
if tlsOptions, containsTLSOptions := rawOptions.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
|
if tlsOptions, containsTLSOptions := outbound.Options.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
|
||||||
tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
|
tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
|
||||||
}
|
}
|
||||||
options.Outbounds[index] = outbound
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -138,13 +128,12 @@ func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.Outboun
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeSSHOutboundOptions(options option.SSHOutboundOptions) option.SSHOutboundOptions {
|
func mergeSSHOutboundOptions(options *option.SSHOutboundOptions) {
|
||||||
if options.PrivateKeyPath != "" {
|
if options.PrivateKeyPath != "" {
|
||||||
if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
|
if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
|
||||||
options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
|
options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return options
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func trimStringArray(array []string) []string {
|
func trimStringArray(array []string) []string {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/route"
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
@@ -84,7 +84,7 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
|||||||
}
|
}
|
||||||
for i, ruleOptions := range plainRuleSet.Rules {
|
for i, ruleOptions := range plainRuleSet.Rules {
|
||||||
var currentRule adapter.HeadlessRule
|
var currentRule adapter.HeadlessRule
|
||||||
currentRule, err = route.NewHeadlessRule(nil, ruleOptions)
|
currentRule, err = rule.NewHeadlessRule(nil, ruleOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func readConfigAt(path string) (*OptionsEntry, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "read config at ", path)
|
return nil, E.Cause(err, "read config at ", path)
|
||||||
}
|
}
|
||||||
options, err := json.UnmarshalExtended[option.Options](configContent)
|
options, err := json.UnmarshalExtendedContext[option.Options](globalCtx, configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "decode config at ", path)
|
return nil, E.Cause(err, "decode config at ", path)
|
||||||
}
|
}
|
||||||
@@ -109,13 +109,13 @@ func readConfigAndMerge() (option.Options, error) {
|
|||||||
}
|
}
|
||||||
var mergedMessage json.RawMessage
|
var mergedMessage json.RawMessage
|
||||||
for _, options := range optionsList {
|
for _, options := range optionsList {
|
||||||
mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage, false)
|
mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return option.Options{}, E.Cause(err, "merge config at ", options.path)
|
return option.Options{}, E.Cause(err, "merge config at ", options.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var mergedOptions option.Options
|
var mergedOptions option.Options
|
||||||
err = mergedOptions.UnmarshalJSON(mergedMessage)
|
err = mergedOptions.UnmarshalJSONContext(globalCtx, mergedMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return option.Options{}, E.Cause(err, "unmarshal merged config")
|
return option.Options{}, E.Cause(err, "unmarshal merged config")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -23,7 +26,9 @@ func init() {
|
|||||||
func createPreStartedClient() (*box.Box, error) {
|
func createPreStartedClient() (*box.Box, error) {
|
||||||
options, err := readConfigAndMerge()
|
options, err := readConfigAndMerge()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
if !(errors.Is(err, os.ErrNotExist) && len(configDirectories) == 0 && len(configPaths) == 1) || configPaths[0] != "config.json" {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
instance, err := box.New(box.Options{Options: options})
|
instance, err := box.New(box.Options{Options: options})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
28
cmd/sing-box/generate_completions.go
Normal file
28
cmd/sing-box/generate_completions.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
//go:build generate && generate_completions
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/sagernet/sing-box/log"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := generateCompletions()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateCompletions() error {
|
||||||
|
err := mainCommand.GenBashCompletionFile("release/completions/sing-box.bash")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = mainCommand.GenFishCompletionFile("release/completions/sing-box.fish", true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = mainCommand.GenZshCompletionFile("release/completions/sing-box.zsh")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/route"
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -26,7 +26,7 @@ example.arpa
|
|||||||
`))
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := route.NewHeadlessRule(nil, rules[0])
|
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
matchDomain := []string{
|
matchDomain := []string{
|
||||||
"example.org",
|
"example.org",
|
||||||
@@ -85,7 +85,7 @@ func TestHosts(t *testing.T) {
|
|||||||
`))
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := route.NewHeadlessRule(nil, rules[0])
|
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
matchDomain := []string{
|
matchDomain := []string{
|
||||||
"google.com",
|
"google.com",
|
||||||
@@ -115,7 +115,7 @@ www.example.org
|
|||||||
`))
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := route.NewHeadlessRule(nil, rules[0])
|
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
matchDomain := []string{
|
matchDomain := []string{
|
||||||
"example.com",
|
"example.com",
|
||||||
|
|||||||
@@ -1,74 +1,11 @@
|
|||||||
|
//go:build !generate
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/sagernet/sing-box/log"
|
||||||
"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
|
|
||||||
disableColor bool
|
|
||||||
)
|
|
||||||
|
|
||||||
var mainCommand = &cobra.Command{
|
|
||||||
Use: "sing-box",
|
|
||||||
PersistentPreRun: preRun,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path")
|
|
||||||
mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path")
|
|
||||||
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
|
|
||||||
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := mainCommand.Execute(); err != nil {
|
if err := mainCommand.Execute(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
err = os.Chdir(workingDir)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
|
||||||
configPaths = append(configPaths, "config.json")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
|||||||
if options.ConnectTimeout != 0 {
|
if options.ConnectTimeout != 0 {
|
||||||
dialer.Timeout = time.Duration(options.ConnectTimeout)
|
dialer.Timeout = time.Duration(options.ConnectTimeout)
|
||||||
} else {
|
} else {
|
||||||
dialer.Timeout = C.TCPTimeout
|
dialer.Timeout = C.TCPConnectTimeout
|
||||||
}
|
}
|
||||||
// TODO: Add an option to customize the keep alive period
|
// TODO: Add an option to customize the keep alive period
|
||||||
dialer.KeepAlive = C.TCPKeepAliveInitial
|
dialer.KeepAlive = C.TCPKeepAliveInitial
|
||||||
@@ -125,7 +125,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
|||||||
setMultiPathTCP(&dialer4)
|
setMultiPathTCP(&dialer4)
|
||||||
}
|
}
|
||||||
if options.IsWireGuardListener {
|
if options.IsWireGuardListener {
|
||||||
for _, controlFn := range wgControlFns {
|
for _, controlFn := range WgControlFns {
|
||||||
listener.Control = control.Append(listener.Control, controlFn)
|
listener.Control = control.Append(listener.Control, controlFn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ package dialer
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/tfo-go"
|
"github.com/metacubex/tfo-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tcpDialer = tfo.Dialer
|
type tcpDialer = tfo.Dialer
|
||||||
|
|||||||
@@ -28,13 +28,12 @@ func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error)
|
|||||||
} else {
|
} else {
|
||||||
dialer = NewDetour(router, options.Detour)
|
dialer = NewDetour(router, options.Detour)
|
||||||
}
|
}
|
||||||
domainStrategy := dns.DomainStrategy(options.DomainStrategy)
|
if options.Detour == "" {
|
||||||
if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" {
|
|
||||||
dialer = NewResolveDialer(
|
dialer = NewResolveDialer(
|
||||||
router,
|
router,
|
||||||
dialer,
|
dialer,
|
||||||
options.Detour == "" && !options.TCPFastOpen,
|
options.Detour == "" && !options.TCPFastOpen,
|
||||||
domainStrategy,
|
dns.DomainStrategy(options.DomainStrategy),
|
||||||
time.Duration(options.FallbackDelay))
|
time.Duration(options.FallbackDelay))
|
||||||
}
|
}
|
||||||
return dialer, nil
|
return dialer, nil
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/tfo-go"
|
|
||||||
|
"github.com/metacubex/tfo-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
type slowOpenConn struct {
|
type slowOpenConn struct {
|
||||||
|
|||||||
@@ -2,8 +2,12 @@ package dialer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WireGuardListener interface {
|
type WireGuardListener interface {
|
||||||
ListenPacketCompat(network, address string) (net.PacketConn, error)
|
ListenPacketCompat(network, address string) (net.PacketConn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var WgControlFns []control.Func
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
//go:build !with_wireguard
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
)
|
|
||||||
|
|
||||||
var wgControlFns []control.Func
|
|
||||||
34
common/geosite/geosite_test.go
Normal file
34
common/geosite/geosite_test.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package geosite_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/geosite"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGeosite(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
err := geosite.Write(&buffer, map[string][]geosite.Item{
|
||||||
|
"test": {
|
||||||
|
{
|
||||||
|
Type: geosite.RuleTypeDomain,
|
||||||
|
Value: "example.org",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
reader, codes, err := geosite.NewReader(bytes.NewReader(buffer.Bytes()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, []string{"test"}, codes)
|
||||||
|
items, err := reader.Read("test")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, []geosite.Item{{
|
||||||
|
Type: geosite.RuleTypeDomain,
|
||||||
|
Value: "example.org",
|
||||||
|
}}, items)
|
||||||
|
}
|
||||||
@@ -26,14 +26,22 @@ func Open(path string) (*Reader, []string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
reader := &Reader{
|
reader, codes, err := NewReader(content)
|
||||||
reader: content,
|
|
||||||
}
|
|
||||||
err = reader.readMetadata()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
content.Close()
|
content.Close()
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
return reader, codes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReader(readSeeker io.ReadSeeker) (*Reader, []string, error) {
|
||||||
|
reader := &Reader{
|
||||||
|
reader: readSeeker,
|
||||||
|
}
|
||||||
|
err := reader.readMetadata()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
codes := make([]string, 0, len(reader.domainIndex))
|
codes := make([]string, 0, len(reader.domainIndex))
|
||||||
for code := range reader.domainIndex {
|
for code := range reader.domainIndex {
|
||||||
codes = append(codes, code)
|
codes = append(codes, code)
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ func Write(writer varbin.Writer, domains map[string][]Item) error {
|
|||||||
index := make(map[string]int)
|
index := make(map[string]int)
|
||||||
for _, code := range keys {
|
for _, code := range keys {
|
||||||
index[code] = content.Len()
|
index[code] = content.Len()
|
||||||
err := varbin.Write(content, binary.BigEndian, domains[code])
|
for _, item := range domains[code] {
|
||||||
if err != nil {
|
err := varbin.Write(content, binary.BigEndian, item)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
136
common/listener/listener.go
Normal file
136
common/listener/listener.go
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
package listener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/settings"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Listener struct {
|
||||||
|
ctx context.Context
|
||||||
|
logger logger.ContextLogger
|
||||||
|
network []string
|
||||||
|
listenOptions option.ListenOptions
|
||||||
|
connHandler adapter.ConnectionHandlerEx
|
||||||
|
packetHandler adapter.PacketHandlerEx
|
||||||
|
oobPacketHandler adapter.OOBPacketHandlerEx
|
||||||
|
threadUnsafePacketWriter bool
|
||||||
|
disablePacketOutput bool
|
||||||
|
setSystemProxy bool
|
||||||
|
systemProxySOCKS bool
|
||||||
|
|
||||||
|
tcpListener net.Listener
|
||||||
|
systemProxy settings.SystemProxy
|
||||||
|
udpConn *net.UDPConn
|
||||||
|
udpAddr M.Socksaddr
|
||||||
|
packetOutbound chan *N.PacketBuffer
|
||||||
|
packetOutboundClosed chan struct{}
|
||||||
|
shutdown atomic.Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Context context.Context
|
||||||
|
Logger logger.ContextLogger
|
||||||
|
Network []string
|
||||||
|
Listen option.ListenOptions
|
||||||
|
ConnectionHandler adapter.ConnectionHandlerEx
|
||||||
|
PacketHandler adapter.PacketHandlerEx
|
||||||
|
OOBPacketHandler adapter.OOBPacketHandlerEx
|
||||||
|
ThreadUnsafePacketWriter bool
|
||||||
|
DisablePacketOutput bool
|
||||||
|
SetSystemProxy bool
|
||||||
|
SystemProxySOCKS bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(
|
||||||
|
options Options,
|
||||||
|
) *Listener {
|
||||||
|
return &Listener{
|
||||||
|
ctx: options.Context,
|
||||||
|
logger: options.Logger,
|
||||||
|
network: options.Network,
|
||||||
|
listenOptions: options.Listen,
|
||||||
|
connHandler: options.ConnectionHandler,
|
||||||
|
packetHandler: options.PacketHandler,
|
||||||
|
oobPacketHandler: options.OOBPacketHandler,
|
||||||
|
threadUnsafePacketWriter: options.ThreadUnsafePacketWriter,
|
||||||
|
disablePacketOutput: options.DisablePacketOutput,
|
||||||
|
setSystemProxy: options.SetSystemProxy,
|
||||||
|
systemProxySOCKS: options.SystemProxySOCKS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Start() error {
|
||||||
|
if common.Contains(l.network, N.NetworkTCP) {
|
||||||
|
_, err := l.ListenTCP()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go l.loopTCPIn()
|
||||||
|
}
|
||||||
|
if common.Contains(l.network, N.NetworkUDP) {
|
||||||
|
_, err := l.ListenUDP()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.packetOutboundClosed = make(chan struct{})
|
||||||
|
l.packetOutbound = make(chan *N.PacketBuffer, 64)
|
||||||
|
go l.loopUDPIn()
|
||||||
|
if !l.disablePacketOutput {
|
||||||
|
go l.loopUDPOut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.setSystemProxy {
|
||||||
|
listenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port
|
||||||
|
var listenAddrString string
|
||||||
|
listenAddr := l.listenOptions.Listen.Build()
|
||||||
|
if listenAddr.IsUnspecified() {
|
||||||
|
listenAddrString = "127.0.0.1"
|
||||||
|
} else {
|
||||||
|
listenAddrString = listenAddr.String()
|
||||||
|
}
|
||||||
|
systemProxy, err := settings.NewSystemProxy(l.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), l.systemProxySOCKS)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "initialize system proxy")
|
||||||
|
}
|
||||||
|
err = systemProxy.Enable()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "set system proxy")
|
||||||
|
}
|
||||||
|
l.systemProxy = systemProxy
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Close() error {
|
||||||
|
l.shutdown.Store(true)
|
||||||
|
var err error
|
||||||
|
if l.systemProxy != nil && l.systemProxy.IsEnabled() {
|
||||||
|
err = l.systemProxy.Disable()
|
||||||
|
}
|
||||||
|
return E.Errors(err, common.Close(
|
||||||
|
l.tcpListener,
|
||||||
|
common.PtrOrNil(l.udpConn),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) TCPListener() net.Listener {
|
||||||
|
return l.tcpListener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) UDPConn() *net.UDPConn {
|
||||||
|
return l.udpConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) ListenOptions() option.ListenOptions {
|
||||||
|
return l.listenOptions
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//go:build go1.21
|
//go:build go1.21
|
||||||
|
|
||||||
package inbound
|
package listener
|
||||||
|
|
||||||
import "net"
|
import "net"
|
||||||
|
|
||||||
16
common/listener/listener_go123.go
Normal file
16
common/listener/listener_go123.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//go:build go1.23
|
||||||
|
|
||||||
|
package listener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) {
|
||||||
|
listener.KeepAliveConfig = net.KeepAliveConfig{
|
||||||
|
Enable: true,
|
||||||
|
Idle: idle,
|
||||||
|
Interval: interval,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//go:build !go1.21
|
//go:build !go1.21
|
||||||
|
|
||||||
package inbound
|
package listener
|
||||||
|
|
||||||
import "net"
|
import "net"
|
||||||
|
|
||||||
15
common/listener/listener_nongo123.go
Normal file
15
common/listener/listener_nongo123.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//go:build !go1.23
|
||||||
|
|
||||||
|
package listener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) {
|
||||||
|
listener.KeepAlive = idle
|
||||||
|
listener.Control = control.Append(listener.Control, control.SetKeepAlivePeriod(idle, interval))
|
||||||
|
}
|
||||||
85
common/listener/listener_tcp.go
Normal file
85
common/listener/listener_tcp.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package listener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"github.com/metacubex/tfo-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l *Listener) ListenTCP() (net.Listener, error) {
|
||||||
|
var err error
|
||||||
|
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort)
|
||||||
|
var tcpListener net.Listener
|
||||||
|
var listenConfig net.ListenConfig
|
||||||
|
if l.listenOptions.TCPKeepAlive >= 0 {
|
||||||
|
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
||||||
|
if keepIdle == 0 {
|
||||||
|
keepIdle = C.TCPKeepAliveInitial
|
||||||
|
}
|
||||||
|
keepInterval := time.Duration(l.listenOptions.TCPKeepAliveInterval)
|
||||||
|
if keepInterval == 0 {
|
||||||
|
keepInterval = C.TCPKeepAliveInterval
|
||||||
|
}
|
||||||
|
setKeepAliveConfig(&listenConfig, keepIdle, keepInterval)
|
||||||
|
}
|
||||||
|
if l.listenOptions.TCPMultiPath {
|
||||||
|
if !go121Available {
|
||||||
|
return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")
|
||||||
|
}
|
||||||
|
setMultiPathTCP(&listenConfig)
|
||||||
|
}
|
||||||
|
if l.listenOptions.TCPFastOpen {
|
||||||
|
var tfoConfig tfo.ListenConfig
|
||||||
|
tfoConfig.ListenConfig = listenConfig
|
||||||
|
tcpListener, err = tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
|
||||||
|
} else {
|
||||||
|
tcpListener, err = listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
l.logger.Info("tcp server started at ", tcpListener.Addr())
|
||||||
|
}
|
||||||
|
//nolint:staticcheck
|
||||||
|
if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader {
|
||||||
|
return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0")
|
||||||
|
}
|
||||||
|
l.tcpListener = tcpListener
|
||||||
|
return tcpListener, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) loopTCPIn() {
|
||||||
|
tcpListener := l.tcpListener
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
for {
|
||||||
|
conn, err := tcpListener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
//nolint:staticcheck
|
||||||
|
if netError, isNetError := err.(net.Error); isNetError && netError.Temporary() {
|
||||||
|
l.logger.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if l.shutdown.Load() && E.IsClosed(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.tcpListener.Close()
|
||||||
|
l.logger.Error("tcp listener closed: ", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
//nolint:staticcheck
|
||||||
|
metadata.InboundDetour = l.listenOptions.Detour
|
||||||
|
//nolint:staticcheck
|
||||||
|
metadata.InboundOptions = l.listenOptions.InboundOptions
|
||||||
|
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
|
||||||
|
metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
|
||||||
|
ctx := log.ContextWithNewID(l.ctx)
|
||||||
|
l.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||||
|
go l.connHandler.NewConnectionEx(ctx, conn, metadata, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
154
common/listener/listener_udp.go
Normal file
154
common/listener/listener_udp.go
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
package listener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
||||||
|
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort)
|
||||||
|
var lc net.ListenConfig
|
||||||
|
var udpFragment bool
|
||||||
|
if l.listenOptions.UDPFragment != nil {
|
||||||
|
udpFragment = *l.listenOptions.UDPFragment
|
||||||
|
} else {
|
||||||
|
udpFragment = l.listenOptions.UDPFragmentDefault
|
||||||
|
}
|
||||||
|
if !udpFragment {
|
||||||
|
lc.Control = control.Append(lc.Control, control.DisableUDPFragment())
|
||||||
|
}
|
||||||
|
udpConn, err := lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l.udpConn = udpConn.(*net.UDPConn)
|
||||||
|
l.udpAddr = bindAddr
|
||||||
|
l.logger.Info("udp server started at ", udpConn.LocalAddr())
|
||||||
|
return udpConn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) UDPAddr() M.Socksaddr {
|
||||||
|
return l.udpAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) PacketWriter() N.PacketWriter {
|
||||||
|
return (*packetWriter)(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) loopUDPIn() {
|
||||||
|
defer close(l.packetOutboundClosed)
|
||||||
|
var buffer *buf.Buffer
|
||||||
|
if !l.threadUnsafePacketWriter {
|
||||||
|
buffer = buf.NewPacket()
|
||||||
|
defer buffer.Release()
|
||||||
|
}
|
||||||
|
buffer.IncRef()
|
||||||
|
defer buffer.DecRef()
|
||||||
|
if l.oobPacketHandler != nil {
|
||||||
|
oob := make([]byte, 1024)
|
||||||
|
for {
|
||||||
|
if l.threadUnsafePacketWriter {
|
||||||
|
buffer = buf.NewPacket()
|
||||||
|
} else {
|
||||||
|
buffer.Reset()
|
||||||
|
}
|
||||||
|
n, oobN, _, addr, err := l.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob)
|
||||||
|
if err != nil {
|
||||||
|
if l.threadUnsafePacketWriter {
|
||||||
|
buffer.Release()
|
||||||
|
}
|
||||||
|
if l.shutdown.Load() && E.IsClosed(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.udpConn.Close()
|
||||||
|
l.logger.Error("udp listener closed: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buffer.Truncate(n)
|
||||||
|
l.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for {
|
||||||
|
if l.threadUnsafePacketWriter {
|
||||||
|
buffer = buf.NewPacket()
|
||||||
|
} else {
|
||||||
|
buffer.Reset()
|
||||||
|
}
|
||||||
|
n, addr, err := l.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes())
|
||||||
|
if err != nil {
|
||||||
|
if l.threadUnsafePacketWriter {
|
||||||
|
buffer.Release()
|
||||||
|
}
|
||||||
|
if l.shutdown.Load() && E.IsClosed(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.udpConn.Close()
|
||||||
|
l.logger.Error("udp listener closed: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buffer.Truncate(n)
|
||||||
|
l.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) loopUDPOut() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case packet := <-l.packetOutbound:
|
||||||
|
destination := packet.Destination.AddrPort()
|
||||||
|
_, err := l.udpConn.WriteToUDPAddrPort(packet.Buffer.Bytes(), destination)
|
||||||
|
packet.Buffer.Release()
|
||||||
|
N.PutPacketBuffer(packet)
|
||||||
|
if err != nil {
|
||||||
|
if l.shutdown.Load() && E.IsClosed(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.udpConn.Close()
|
||||||
|
l.logger.Error("udp listener write back: ", destination, ": ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
case <-l.packetOutboundClosed:
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case packet := <-l.packetOutbound:
|
||||||
|
packet.Buffer.Release()
|
||||||
|
N.PutPacketBuffer(packet)
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type packetWriter Listener
|
||||||
|
|
||||||
|
func (w *packetWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
packet := N.NewPacketBuffer()
|
||||||
|
packet.Buffer = buffer
|
||||||
|
packet.Destination = destination
|
||||||
|
select {
|
||||||
|
case w.packetOutbound <- packet:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
buffer.Release()
|
||||||
|
N.PutPacketBuffer(packet)
|
||||||
|
if w.shutdown.Load() {
|
||||||
|
return os.ErrClosed
|
||||||
|
}
|
||||||
|
w.logger.Trace("dropped packet to ", destination)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *packetWriter) WriteIsThreadUnsafe() {
|
||||||
|
}
|
||||||
@@ -15,11 +15,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Router struct {
|
type Router struct {
|
||||||
router adapter.ConnectionRouter
|
router adapter.ConnectionRouterEx
|
||||||
service *mux.Service
|
service *mux.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouter, error) {
|
func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouterEx, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return router, nil
|
return router, nil
|
||||||
}
|
}
|
||||||
@@ -54,6 +54,7 @@ func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.Context
|
|||||||
|
|
||||||
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
if metadata.Destination == mux.Destination {
|
if metadata.Destination == mux.Destination {
|
||||||
|
// TODO: check if WithContext is necessary
|
||||||
return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata))
|
return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata))
|
||||||
} else {
|
} else {
|
||||||
return r.router.RouteConnection(ctx, conn, metadata)
|
return r.router.RouteConnection(ctx, conn, metadata)
|
||||||
@@ -63,3 +64,15 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
|
if metadata.Destination == mux.Destination {
|
||||||
|
r.service.NewConnectionEx(adapter.WithContext(ctx, &metadata), conn, metadata.Source, metadata.Destination, onClose)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
|
r.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
vmess "github.com/sagernet/sing-vmess"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
type V2RayLegacyRouter struct {
|
|
||||||
router adapter.ConnectionRouter
|
|
||||||
logger logger.ContextLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewV2RayLegacyRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) adapter.ConnectionRouter {
|
|
||||||
return &V2RayLegacyRouter{router, logger}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *V2RayLegacyRouter) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
|
||||||
if metadata.Destination.Fqdn == vmess.MuxDestination.Fqdn {
|
|
||||||
r.logger.InfoContext(ctx, "inbound legacy multiplex connection")
|
|
||||||
return vmess.HandleMuxConnection(ctx, conn, adapter.NewRouteHandler(metadata, r.router, r.logger))
|
|
||||||
}
|
|
||||||
return r.router.RouteConnection(ctx, conn, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *V2RayLegacyRouter) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
|
||||||
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
|
||||||
}
|
|
||||||
90
common/sniff/rdp.go
Normal file
90
common/sniff/rdp.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package sniff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
||||||
|
var tpktVersion uint8
|
||||||
|
err := binary.Read(reader, binary.BigEndian, &tpktVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if tpktVersion != 0x03 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
var tpktReserved uint8
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &tpktReserved)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if tpktReserved != 0x00 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
var tpktLength uint16
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &tpktLength)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tpktLength != 19 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
var cotpLength uint8
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &cotpLength)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cotpLength != 14 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
var cotpTpduType uint8
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &cotpTpduType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if cotpTpduType != 0xE0 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rw.SkipN(reader, 5)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var rdpType uint8
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &rdpType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if rdpType != 0x01 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
var rdpFlags uint8
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &rdpFlags)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var rdpLength uint8
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &rdpLength)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if rdpLength != 8 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
metadata.Protocol = C.ProtocolRDP
|
||||||
|
return nil
|
||||||
|
}
|
||||||
25
common/sniff/rdp_test.go
Normal file
25
common/sniff/rdp_test.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package sniff_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/sniff"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSniffRDP(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
pkt, err := hex.DecodeString("030000130ee00000000000010008000b000000010008000b000000")
|
||||||
|
require.NoError(t, err)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
err = sniff.RDP(context.TODO(), &metadata, bytes.NewReader(pkt))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, C.ProtocolRDP, metadata.Protocol)
|
||||||
|
}
|
||||||
@@ -18,26 +18,42 @@ type (
|
|||||||
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
|
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func Skip(metadata *adapter.InboundContext) bool {
|
||||||
|
// skip server first protocols
|
||||||
|
switch metadata.Destination.Port {
|
||||||
|
case 25, 465, 587:
|
||||||
|
// SMTP
|
||||||
|
return true
|
||||||
|
case 143, 993:
|
||||||
|
// IMAP
|
||||||
|
return true
|
||||||
|
case 110, 995:
|
||||||
|
// POP3
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error {
|
func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error {
|
||||||
if timeout == 0 {
|
if timeout == 0 {
|
||||||
timeout = C.ReadPayloadTimeout
|
timeout = C.ReadPayloadTimeout
|
||||||
}
|
}
|
||||||
deadline := time.Now().Add(timeout)
|
deadline := time.Now().Add(timeout)
|
||||||
var errors []error
|
var errors []error
|
||||||
|
for i := 0; ; i++ {
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
err := conn.SetReadDeadline(deadline)
|
err := conn.SetReadDeadline(deadline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "set read deadline")
|
return E.Cause(err, "set read deadline")
|
||||||
}
|
}
|
||||||
_, err = buffer.ReadOnceFrom(conn)
|
_, err = buffer.ReadOnceFrom(conn)
|
||||||
err = E.Errors(err, conn.SetReadDeadline(time.Time{}))
|
_ = conn.SetReadDeadline(time.Time{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return E.Cause(err, "read payload")
|
return E.Cause(err, "read payload")
|
||||||
}
|
}
|
||||||
|
errors = nil
|
||||||
for _, sniffer := range sniffers {
|
for _, sniffer := range sniffers {
|
||||||
err = sniffer(ctx, metadata, bytes.NewReader(buffer.Bytes()))
|
err = sniffer(ctx, metadata, bytes.NewReader(buffer.Bytes()))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
26
common/sniff/ssh.go
Normal file
26
common/sniff/ssh.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package sniff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
if !scanner.Scan() {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
fistLine := scanner.Text()
|
||||||
|
if !strings.HasPrefix(fistLine, "SSH-2.0-") {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
metadata.Protocol = C.ProtocolSSH
|
||||||
|
metadata.Client = fistLine[8:]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
26
common/sniff/ssh_test.go
Normal file
26
common/sniff/ssh_test.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package sniff_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/sniff"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSniffSSH(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
pkt, err := hex.DecodeString("5353482d322e302d64726f70626561720d0a000001a40a1492892570d1223aef61b0d647972c8bd30000009f637572766532353531392d7368613235362c637572766532353531392d736861323536406c69627373682e6f72672c6469666669652d68656c6c6d616e2d67726f757031342d7368613235362c6469666669652d68656c6c6d616e2d67726f757031342d736861312c6b6578677565737332406d6174742e7563632e61736e2e61752c6b65782d7374726963742d732d763030406f70656e7373682e636f6d000000207373682d656432353531392c7273612d736861322d3235362c7373682d7273610000003363686163686132302d706f6c7931333035406f70656e7373682e636f6d2c6165733132382d6374722c6165733235362d6374720000003363686163686132302d706f6c7931333035406f70656e7373682e636f6d2c6165733132382d6374722c6165733235362d63747200000017686d61632d736861312c686d61632d736861322d32353600000017686d61632d736861312c686d61632d736861322d323536000000046e6f6e65000000046e6f6e65000000000000000000000000002aa6ed090585b7d635b6")
|
||||||
|
require.NoError(t, err)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, C.ProtocolSSH, metadata.Protocol)
|
||||||
|
require.Equal(t, "dropbear", metadata.Client)
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ const (
|
|||||||
ruleItemWIFISSID
|
ruleItemWIFISSID
|
||||||
ruleItemWIFIBSSID
|
ruleItemWIFIBSSID
|
||||||
ruleItemAdGuardDomain
|
ruleItemAdGuardDomain
|
||||||
|
ruleItemProcessPathRegex
|
||||||
ruleItemFinal uint8 = 0xFF
|
ruleItemFinal uint8 = 0xFF
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -207,6 +208,8 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
|||||||
rule.ProcessName, err = readRuleItemString(reader)
|
rule.ProcessName, err = readRuleItemString(reader)
|
||||||
case ruleItemProcessPath:
|
case ruleItemProcessPath:
|
||||||
rule.ProcessPath, err = readRuleItemString(reader)
|
rule.ProcessPath, err = readRuleItemString(reader)
|
||||||
|
case ruleItemProcessPathRegex:
|
||||||
|
rule.ProcessPathRegex, err = readRuleItemString(reader)
|
||||||
case ruleItemPackageName:
|
case ruleItemPackageName:
|
||||||
rule.PackageName, err = readRuleItemString(reader)
|
rule.PackageName, err = readRuleItemString(reader)
|
||||||
case ruleItemWIFISSID:
|
case ruleItemWIFISSID:
|
||||||
@@ -326,6 +329,12 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(rule.ProcessPathRegex) > 0 {
|
||||||
|
err = writeRuleItemString(writer, ruleItemProcessPathRegex, rule.ProcessPathRegex)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if len(rule.PackageName) > 0 {
|
if len(rule.PackageName) > 0 {
|
||||||
err = writeRuleItemString(writer, ruleItemPackageName, rule.PackageName)
|
err = writeRuleItemString(writer, ruleItemPackageName, rule.PackageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -217,18 +217,10 @@ func init() {
|
|||||||
|
|
||||||
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
||||||
switch name {
|
switch name {
|
||||||
|
case "chrome_psk", "chrome_psk_shuffle", "chrome_padding_psk_shuffle", "chrome_pq":
|
||||||
|
fallthrough
|
||||||
case "chrome", "":
|
case "chrome", "":
|
||||||
return utls.HelloChrome_Auto, nil
|
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":
|
case "firefox":
|
||||||
return utls.HelloFirefox_Auto, nil
|
return utls.HelloFirefox_Auto, nil
|
||||||
case "edge":
|
case "edge":
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ import (
|
|||||||
"github.com/sagernet/sing/common/uot"
|
"github.com/sagernet/sing/common/uot"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.ConnectionRouter = (*Router)(nil)
|
var _ adapter.ConnectionRouterEx = (*Router)(nil)
|
||||||
|
|
||||||
type Router struct {
|
type Router struct {
|
||||||
router adapter.ConnectionRouter
|
router adapter.ConnectionRouterEx
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) *Router {
|
func NewRouter(router adapter.ConnectionRouterEx, logger logger.ContextLogger) *Router {
|
||||||
return &Router{router, logger}
|
return &Router{router, logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,3 +51,36 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
|
switch metadata.Destination.Fqdn {
|
||||||
|
case uot.MagicAddress:
|
||||||
|
request, err := uot.ReadRequest(conn)
|
||||||
|
if err != nil {
|
||||||
|
err = E.Cause(err, "UoT read request")
|
||||||
|
r.logger.ErrorContext(ctx, "process connection from ", metadata.Source, ": ", err)
|
||||||
|
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if request.IsConnect {
|
||||||
|
r.logger.InfoContext(ctx, "inbound UoT connect connection to ", request.Destination)
|
||||||
|
} else {
|
||||||
|
r.logger.InfoContext(ctx, "inbound UoT connection to ", request.Destination)
|
||||||
|
}
|
||||||
|
metadata.Domain = metadata.Destination.Fqdn
|
||||||
|
metadata.Destination = request.Destination
|
||||||
|
r.router.RoutePacketConnectionEx(ctx, uot.NewConn(conn, *request), metadata, onClose)
|
||||||
|
return
|
||||||
|
case uot.LegacyMagicAddress:
|
||||||
|
r.logger.InfoContext(ctx, "inbound legacy UoT connection")
|
||||||
|
metadata.Domain = metadata.Destination.Fqdn
|
||||||
|
metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
|
||||||
|
r.RoutePacketConnectionEx(ctx, uot.NewConn(conn, uot.Request{}), metadata, onClose)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
|
r.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -113,6 +114,7 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e
|
|||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
},
|
},
|
||||||
|
Timeout: C.TCPTimeout,
|
||||||
}
|
}
|
||||||
defer client.CloseIdleConnections()
|
defer client.CloseIdleConnections()
|
||||||
resp, err := client.Do(req.WithContext(ctx))
|
resp, err := client.Do(req.WithContext(ctx))
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ const (
|
|||||||
ProtocolSTUN = "stun"
|
ProtocolSTUN = "stun"
|
||||||
ProtocolBitTorrent = "bittorrent"
|
ProtocolBitTorrent = "bittorrent"
|
||||||
ProtocolDTLS = "dtls"
|
ProtocolDTLS = "dtls"
|
||||||
|
ProtocolSSH = "ssh"
|
||||||
|
ProtocolRDP = "rdp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -22,3 +22,21 @@ const (
|
|||||||
RuleSetVersion1 = 1 + iota
|
RuleSetVersion1 = 1 + iota
|
||||||
RuleSetVersion2
|
RuleSetVersion2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
RuleActionTypeRoute = "route"
|
||||||
|
RuleActionTypeReturn = "return"
|
||||||
|
RuleActionTypeReject = "reject"
|
||||||
|
RuleActionTypeHijackDNS = "hijack-dns"
|
||||||
|
RuleActionTypeSniff = "sniff"
|
||||||
|
RuleActionTypeResolve = "resolve"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
RuleActionRejectMethodDefault = "default"
|
||||||
|
RuleActionRejectMethodReset = "reset"
|
||||||
|
RuleActionRejectMethodNetworkUnreachable = "network-unreachable"
|
||||||
|
RuleActionRejectMethodHostUnreachable = "host-unreachable"
|
||||||
|
RuleActionRejectMethodPortUnreachable = "port-unreachable"
|
||||||
|
RuleActionRejectMethodDrop = "drop"
|
||||||
|
)
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import "time"
|
|||||||
const (
|
const (
|
||||||
TCPKeepAliveInitial = 10 * time.Minute
|
TCPKeepAliveInitial = 10 * time.Minute
|
||||||
TCPKeepAliveInterval = 75 * time.Second
|
TCPKeepAliveInterval = 75 * time.Second
|
||||||
TCPTimeout = 5 * time.Second
|
TCPConnectTimeout = 5 * time.Second
|
||||||
|
TCPTimeout = 15 * time.Second
|
||||||
ReadPayloadTimeout = 300 * time.Millisecond
|
ReadPayloadTimeout = 300 * time.Millisecond
|
||||||
DNSTimeout = 10 * time.Second
|
DNSTimeout = 10 * time.Second
|
||||||
QUICTimeout = 30 * time.Second
|
QUICTimeout = 30 * time.Second
|
||||||
|
|||||||
@@ -2,14 +2,198 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! failure "Help needed"
|
#### 1.11.0-alpha.5
|
||||||
|
|
||||||
Due to problems with our Apple developer account, sing-box apps on Apple platforms are temporarily unavailable for download or update.
|
* Fixes and improvements
|
||||||
|
|
||||||
If your company or organization is willing to help us return to the App Store, please [contact us](mailto:contact@sagernet.org).
|
#### 1.11.0-alpha.2
|
||||||
|
|
||||||
#### 1.10.0-beta.1
|
* Add warnings for usage of deprecated features
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.11.0-alpha.1
|
||||||
|
|
||||||
|
* Update quic-go to v0.48.0
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
### 1.10.1
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
### 1.10.0
|
||||||
|
|
||||||
|
Important changes since 1.9:
|
||||||
|
|
||||||
|
* Introducing auto-redirect **1**
|
||||||
|
* Add AdGuard DNS Filter support **2**
|
||||||
|
* TUN address fields are merged **3**
|
||||||
|
* Add custom options for `auto-route` and `auto-redirect` **4**
|
||||||
|
* Drop support for go1.18 and go1.19 **5**
|
||||||
|
* Add tailing comma support in JSON configuration
|
||||||
|
* Improve sniffers **6**
|
||||||
|
* Add new `inline` rule-set type **7**
|
||||||
|
* Add access control options for Clash API **8**
|
||||||
|
* Add `rule_set_ip_cidr_accept_empty` DNS address filter rule item **9**
|
||||||
|
* Add auto reload support for local rule-set
|
||||||
|
* Update fsnotify usages **10**
|
||||||
|
* Add IP address support for `rule-set match` command
|
||||||
|
* Add `rule-set decompile` command
|
||||||
|
* Add `process_path_regex` rule item
|
||||||
|
* Update uTLS to v1.6.7 **11**
|
||||||
|
* Optimize memory usages of rule-sets **12**
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
The new auto-redirect feature allows TUN to automatically
|
||||||
|
configure connection redirection to improve proxy performance.
|
||||||
|
|
||||||
|
When auto-redirect is enabled, new route address set options will allow you to
|
||||||
|
automatically configure destination IP CIDR rules from a specified rule set to the firewall.
|
||||||
|
|
||||||
|
Specified or unspecified destinations will bypass the sing-box routes to get better performance
|
||||||
|
(for example, keep hardware offloading of direct traffics on the router).
|
||||||
|
|
||||||
|
See [TUN](/configuration/inbound/tun).
|
||||||
|
|
||||||
|
**2**:
|
||||||
|
|
||||||
|
The new feature allows you to use AdGuard DNS Filter lists in a sing-box without AdGuard Home.
|
||||||
|
|
||||||
|
See [AdGuard DNS Filter](/configuration/rule-set/adguard/).
|
||||||
|
|
||||||
|
**3**:
|
||||||
|
|
||||||
|
See [Migration](/migration/#tun-address-fields-are-merged).
|
||||||
|
|
||||||
|
**4**:
|
||||||
|
|
||||||
|
See [iproute2_table_index](/configuration/inbound/tun/#iproute2_table_index),
|
||||||
|
[iproute2_rule_index](/configuration/inbound/tun/#iproute2_rule_index),
|
||||||
|
[auto_redirect_input_mark](/configuration/inbound/tun/#auto_redirect_input_mark) and
|
||||||
|
[auto_redirect_output_mark](/configuration/inbound/tun/#auto_redirect_output_mark).
|
||||||
|
|
||||||
|
**5**:
|
||||||
|
|
||||||
|
Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.
|
||||||
|
|
||||||
|
**6**:
|
||||||
|
|
||||||
|
BitTorrent, DTLS, RDP, SSH sniffers are added.
|
||||||
|
|
||||||
|
Now the QUIC sniffer can correctly extract the server name from Chromium requests and
|
||||||
|
can identify common QUIC clients, including
|
||||||
|
Chromium, Safari, Firefox, quic-go (including uquic disguised as Chrome).
|
||||||
|
|
||||||
|
**7**:
|
||||||
|
|
||||||
|
The new [rule-set](/configuration/rule-set/) type inline (which also becomes the default type)
|
||||||
|
allows you to write headless rules directly without creating a rule-set file.
|
||||||
|
|
||||||
|
**8**:
|
||||||
|
|
||||||
|
With the new access control options, not only can you allow Clash dashboards
|
||||||
|
to access the Clash API on your local network,
|
||||||
|
you can also manually limit the websites that can access the API instead of allowing everyone.
|
||||||
|
|
||||||
|
See [Clash API](/configuration/experimental/clash-api/).
|
||||||
|
|
||||||
|
**9**:
|
||||||
|
|
||||||
|
See [DNS Rule](/configuration/dns/rule/#rule_set_ip_cidr_accept_empty).
|
||||||
|
|
||||||
|
**10**:
|
||||||
|
|
||||||
|
sing-box now uses fsnotify correctly and will not cancel watching
|
||||||
|
if the target file is deleted or recreated via rename (e.g. `mv`).
|
||||||
|
|
||||||
|
This affects all path options that support reload, including
|
||||||
|
`tls.certificate_path`, `tls.key_path`, `tls.ech.key_path` and `rule_set.path`.
|
||||||
|
|
||||||
|
**11**:
|
||||||
|
|
||||||
|
Some legacy chrome fingerprints have been removed and will fallback to chrome,
|
||||||
|
see [utls](/configuration/shared/tls#utls).
|
||||||
|
|
||||||
|
**12**:
|
||||||
|
|
||||||
|
See [Source Format](/configuration/rule-set/source-format/#version).
|
||||||
|
|
||||||
|
### 1.9.7
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.10.0-beta.11
|
||||||
|
|
||||||
|
* Update uTLS to v1.6.7 **1**
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Some legacy chrome fingerprints have been removed and will fallback to chrome,
|
||||||
|
see [utls](/configuration/shared/tls#utls).
|
||||||
|
|
||||||
|
#### 1.10.0-beta.10
|
||||||
|
|
||||||
|
* Add `process_path_regex` rule item
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
_The macOS standalone versions of sing-box (>=1.9.5/<1.10.0-beta.11) now silently fail and require manual granting of
|
||||||
|
the **Full Disk Access** permission to system extension to start, probably due to Apple's changed security policy. We
|
||||||
|
will prompt users about this in feature versions._
|
||||||
|
|
||||||
|
### 1.9.6
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
### 1.9.5
|
||||||
|
|
||||||
|
* Update quic-go to v0.47.0
|
||||||
|
* Fix direct dialer not resolving domain
|
||||||
|
* Fix no error return when empty DNS cache retrieved
|
||||||
|
* Fix build with go1.23
|
||||||
|
* Fix stream sniffer
|
||||||
|
* Fix bad redirect in clash-api
|
||||||
|
* Fix wireguard events chan leak
|
||||||
|
* Fix cached conn eats up read deadlines
|
||||||
|
* Fix disconnected interface selected as default in windows
|
||||||
|
* Update Bundle Identifiers for Apple platform clients **1**
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
See [Migration](/migration/#bundle-identifier-updates-in-apple-platform-clients).
|
||||||
|
|
||||||
|
We are still working on getting all sing-box apps back on the App Store, which should be completed within a week
|
||||||
|
(SFI on the App Store and others on TestFlight are already available).
|
||||||
|
|
||||||
|
#### 1.10.0-beta.8
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
_With the help of a netizen, we are in the process of getting sing-box apps back on the App Store, which should be
|
||||||
|
completed within a month (TestFlight is already available)._
|
||||||
|
|
||||||
|
#### 1.10.0-beta.7
|
||||||
|
|
||||||
|
* Update quic-go to v0.47.0
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.10.0-beta.6
|
||||||
|
|
||||||
|
* Add RDP sniffer
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.10.0-beta.5
|
||||||
|
|
||||||
|
* Add PNA support for [Clash API](/configuration/experimental/clash-api/)
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.10.0-beta.3
|
||||||
|
|
||||||
|
* Add SSH sniffer
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.10.0-beta.2
|
||||||
|
|
||||||
|
* Build with go1.23
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
### 1.9.4
|
### 1.9.4
|
||||||
@@ -27,6 +211,11 @@ icon: material/alert-decagram
|
|||||||
* Fix UDP connnection leak when sniffing
|
* Fix UDP connnection leak when sniffing
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
_Due to problems with our Apple developer account,
|
||||||
|
sing-box apps on Apple platforms are temporarily unavailable for download or update.
|
||||||
|
If your company or organization is willing to help us return to the App Store,
|
||||||
|
please [contact us](mailto:contact@sagernet.org)._
|
||||||
|
|
||||||
#### 1.10.0-alpha.29
|
#### 1.10.0-alpha.29
|
||||||
|
|
||||||
* Update quic-go to v0.46.0
|
* Update quic-go to v0.46.0
|
||||||
@@ -85,11 +274,9 @@ See [Source Format](/configuration/rule-set/source-format/#version).
|
|||||||
|
|
||||||
**1**:
|
**1**:
|
||||||
|
|
||||||
The new [rule-set] type inline (which also becomes the default type)
|
The new [rule-set](/configuration/rule-set/) type inline (which also becomes the default type)
|
||||||
allows you to write headless rules directly without creating a rule-set file.
|
allows you to write headless rules directly without creating a rule-set file.
|
||||||
|
|
||||||
[rule-set]: /configuration/rule-set/
|
|
||||||
|
|
||||||
**2**:
|
**2**:
|
||||||
|
|
||||||
sing-box now uses fsnotify correctly and will not cancel watching
|
sing-box now uses fsnotify correctly and will not cancel watching
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ SFA provides an unprivileged TUN implementation through Android VpnService.
|
|||||||
|-----------------------|------------------|-----------------------------------|
|
|-----------------------|------------------|-----------------------------------|
|
||||||
| `process_name` | :material-close: | No permission |
|
| `process_name` | :material-close: | No permission |
|
||||||
| `process_path` | :material-close: | No permission |
|
| `process_path` | :material-close: | No permission |
|
||||||
|
| `process_path_regex` | :material-close: | No permission |
|
||||||
| `package_name` | :material-check: | / |
|
| `package_name` | :material-check: | / |
|
||||||
| `user` | :material-close: | Use `package_name` instead |
|
| `user` | :material-close: | Use `package_name` instead |
|
||||||
| `user_id` | :material-close: | Use `package_name` instead |
|
| `user_id` | :material-close: | Use `package_name` instead |
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ SFI/SFM/SFT provides an unprivileged TUN implementation through NetworkExtension
|
|||||||
|-----------------------|------------------|-----------------------|
|
|-----------------------|------------------|-----------------------|
|
||||||
| `process_name` | :material-close: | No permission |
|
| `process_name` | :material-close: | No permission |
|
||||||
| `process_path` | :material-close: | No permission |
|
| `process_path` | :material-close: | No permission |
|
||||||
|
| `process_path_regex` | :material-close: | No permission |
|
||||||
| `package_name` | :material-close: | / |
|
| `package_name` | :material-close: | / |
|
||||||
| `user` | :material-close: | No permission |
|
| `user` | :material-close: | No permission |
|
||||||
| `user_id` | :material-close: | No permission |
|
| `user_id` | :material-close: | No permission |
|
||||||
|
|||||||
@@ -7,13 +7,6 @@ icon: material/apple
|
|||||||
SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides
|
SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides
|
||||||
platform-specific function implementation, such as TUN transparent proxy implementation.
|
platform-specific function implementation, such as TUN transparent proxy implementation.
|
||||||
|
|
||||||
!!! failure "Unavailable"
|
|
||||||
|
|
||||||
Due to problems with our Apple developer account, sing-box apps on Apple platforms are temporarily unavailable for download or update.
|
|
||||||
|
|
||||||
If your company or organization is willing to help us return to the App Store, please [contact us](mailto:contact@sagernet.org).
|
|
||||||
|
|
||||||
|
|
||||||
## :material-graph: Requirements
|
## :material-graph: Requirements
|
||||||
|
|
||||||
* iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+
|
* iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+
|
||||||
@@ -21,13 +14,13 @@ platform-specific function implementation, such as TUN transparent proxy impleme
|
|||||||
|
|
||||||
## :material-download: Download
|
## :material-download: Download
|
||||||
|
|
||||||
* [App Store](https://apps.apple.com/us/app/sing-box/id6451272673)
|
* [App Store](https://apps.apple.com/app/sing-box-vt/id6673731168)
|
||||||
* ~~TestFlight (Beta)~~
|
* TestFlight (Beta)
|
||||||
|
|
||||||
TestFlight quota is only available to [sponsors](https://github.com/sponsors/nekohasekai)
|
TestFlight quota is only available to [sponsors](https://github.com/sponsors/nekohasekai)
|
||||||
(one-time sponsorships are accepted).
|
(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),
|
Once you donate, you can get an invitation by join our Telegram group for sponsors from [@yet_another_sponsor_bot](https://t.me/yet_another_sponsor_bot)
|
||||||
or join our Telegram group for sponsors from [@yet_another_sponsor_bot](https://t.me/yet_another_sponsor_bot).
|
or sending us your Apple ID [via email](mailto:contact@sagernet.org).
|
||||||
|
|
||||||
## :material-file-download: Download (macOS standalone version)
|
## :material-file-download: Download (macOS standalone version)
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
Maintained by Project S to provide a unified experience and platform-specific functionality.
|
Maintained by Project S to provide a unified experience and platform-specific functionality.
|
||||||
|
|
||||||
| Platform | Client |
|
| Platform | Client |
|
||||||
| ------------------------------------- | ---------------------------------------- |
|
|---------------------------------------|------------------------------------------|
|
||||||
| :material-android: Android | [sing-box for Android](./android/) |
|
| :material-android: Android | [sing-box for Android](./android/) |
|
||||||
| :material-apple: iOS/macOS/Apple tvOS | :material-alert: [Unavailable](./apple/) |
|
| :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple/) |
|
||||||
| :material-laptop: Desktop | Working in progress |
|
| :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
|
Some third-party projects that claim to use sing-box or use sing-box as a selling point are not listed here. The core
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
由 Project S 维护,提供统一的体验与平台特定的功能。
|
由 Project S 维护,提供统一的体验与平台特定的功能。
|
||||||
|
|
||||||
| 平台 | 客户端 |
|
| 平台 | 客户端 |
|
||||||
| ------------------------------------- | ----------------------------------- |
|
|---------------------------------------|------------------------------------------|
|
||||||
| :material-android: Android | [sing-box for Android](./android/) |
|
| :material-android: Android | [sing-box for Android](./android/) |
|
||||||
| :material-apple: iOS/macOS/Apple tvOS | :material-alert: [不可用](./apple/) |
|
| :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple/) |
|
||||||
| :material-laptop: Desktop | 施工中 |
|
| :material-laptop: Desktop | 施工中 |
|
||||||
|
|
||||||
此处没有列出一些声称使用或以 sing-box 为卖点的第三方项目。此类项目维护者的动机是获得更多用户,即使它们提供友好的商业
|
此处没有列出一些声称使用或以 sing-box 为卖点的第三方项目。此类项目维护者的动机是获得更多用户,即使它们提供友好的商业
|
||||||
VPN 客户端功能, 但代码质量很差且包含广告。
|
VPN 客户端功能, 但代码质量很差且包含广告。
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ icon: material/new-box
|
|||||||
|
|
||||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
: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_match_source](#rule_set_ip_cidr_match_source)
|
||||||
:material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
:material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
||||||
|
:material-plus: [process_path_regex](#process_path_regex)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.9.0"
|
!!! quote "Changes in sing-box 1.9.0"
|
||||||
|
|
||||||
@@ -103,6 +104,9 @@ icon: material/new-box
|
|||||||
"process_path": [
|
"process_path": [
|
||||||
"/usr/bin/curl"
|
"/usr/bin/curl"
|
||||||
],
|
],
|
||||||
|
"process_path_regex": [
|
||||||
|
"^/usr/bin/.+"
|
||||||
|
],
|
||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
@@ -268,6 +272,16 @@ Match process name.
|
|||||||
|
|
||||||
Match process path.
|
Match process path.
|
||||||
|
|
||||||
|
#### process_path_regex
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
Match process path using regular expression.
|
||||||
|
|
||||||
#### package_name
|
#### package_name
|
||||||
|
|
||||||
Match android package name.
|
Match android package name.
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ icon: material/new-box
|
|||||||
|
|
||||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
: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_match_source](#rule_set_ip_cidr_match_source)
|
||||||
:material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
:material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
||||||
|
:material-plus: [process_path_regex](#process_path_regex)
|
||||||
|
|
||||||
!!! quote "sing-box 1.9.0 中的更改"
|
!!! quote "sing-box 1.9.0 中的更改"
|
||||||
|
|
||||||
@@ -103,6 +104,9 @@ icon: material/new-box
|
|||||||
"process_path": [
|
"process_path": [
|
||||||
"/usr/bin/curl"
|
"/usr/bin/curl"
|
||||||
],
|
],
|
||||||
|
"process_path_regex": [
|
||||||
|
"^/usr/bin/.+"
|
||||||
|
],
|
||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
@@ -266,6 +270,16 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
匹配进程路径。
|
匹配进程路径。
|
||||||
|
|
||||||
|
#### process_path_regex
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
|
使用正则表达式匹配进程路径。
|
||||||
|
|
||||||
#### package_name
|
#### package_name
|
||||||
|
|
||||||
匹配 Android 应用包名。
|
匹配 Android 应用包名。
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.10.0"
|
||||||
|
|
||||||
|
:material-plus: [access_control_allow_origin](#access_control_allow_origin)
|
||||||
|
:material-plus: [access_control_allow_private_network](#access_control_allow_private_network)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.8.0"
|
!!! quote "Changes in sing-box 1.8.0"
|
||||||
|
|
||||||
:material-delete-alert: [store_mode](#store_mode)
|
:material-delete-alert: [store_mode](#store_mode)
|
||||||
@@ -8,24 +17,59 @@
|
|||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
```json
|
=== "Structure"
|
||||||
{
|
|
||||||
"external_controller": "127.0.0.1:9090",
|
```json
|
||||||
"external_ui": "",
|
{
|
||||||
"external_ui_download_url": "",
|
"external_controller": "127.0.0.1:9090",
|
||||||
"external_ui_download_detour": "",
|
"external_ui": "",
|
||||||
"secret": "",
|
"external_ui_download_url": "",
|
||||||
"default_mode": "",
|
"external_ui_download_detour": "",
|
||||||
|
"secret": "",
|
||||||
// Deprecated
|
"default_mode": "",
|
||||||
|
"access_control_allow_origin": [],
|
||||||
"store_mode": false,
|
"access_control_allow_private_network": false,
|
||||||
"store_selected": false,
|
|
||||||
"store_fakeip": false,
|
// Deprecated
|
||||||
"cache_file": "",
|
|
||||||
"cache_id": ""
|
"store_mode": false,
|
||||||
}
|
"store_selected": false,
|
||||||
```
|
"store_fakeip": false,
|
||||||
|
"cache_file": "",
|
||||||
|
"cache_id": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Example (online)"
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"external_controller": "127.0.0.1:9090",
|
||||||
|
"access_control_allow_origin": [
|
||||||
|
"http://127.0.0.1",
|
||||||
|
"http://yacd.haishan.me"
|
||||||
|
],
|
||||||
|
"access_control_allow_private_network": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Example (download)"
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"external_controller": "0.0.0.0:9090",
|
||||||
|
"external_ui": "dashboard"
|
||||||
|
// external_ui_download_detour: "direct"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note ""
|
||||||
|
|
||||||
|
You can ignore the JSON Array [] tag when the content is only one item
|
||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
@@ -63,6 +107,22 @@ Default mode in clash, `Rule` will be used if empty.
|
|||||||
|
|
||||||
This setting has no direct effect, but can be used in routing and DNS rules via the `clash_mode` rule item.
|
This setting has no direct effect, but can be used in routing and DNS rules via the `clash_mode` rule item.
|
||||||
|
|
||||||
|
#### access_control_allow_origin
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
|
CORS allowed origins, `*` will be used if empty.
|
||||||
|
|
||||||
|
To access the Clash API on a private network from a public website, you must explicitly specify it in `access_control_allow_origin` instead of using `*`.
|
||||||
|
|
||||||
|
#### access_control_allow_private_network
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
|
Allow access from private network.
|
||||||
|
|
||||||
|
To access the Clash API on a private network from a public website, `access_control_allow_private_network` must be enabled.
|
||||||
|
|
||||||
#### store_mode
|
#### store_mode
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.8.0"
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.10.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: [access_control_allow_origin](#access_control_allow_origin)
|
||||||
|
:material-plus: [access_control_allow_private_network](#access_control_allow_private_network)
|
||||||
|
|
||||||
!!! quote "sing-box 1.8.0 中的更改"
|
!!! quote "sing-box 1.8.0 中的更改"
|
||||||
|
|
||||||
:material-delete-alert: [store_mode](#store_mode)
|
:material-delete-alert: [store_mode](#store_mode)
|
||||||
@@ -8,24 +17,59 @@
|
|||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
```json
|
=== "结构"
|
||||||
{
|
|
||||||
"external_controller": "127.0.0.1:9090",
|
```json
|
||||||
"external_ui": "",
|
{
|
||||||
"external_ui_download_url": "",
|
"external_controller": "127.0.0.1:9090",
|
||||||
"external_ui_download_detour": "",
|
"external_ui": "",
|
||||||
"secret": "",
|
"external_ui_download_url": "",
|
||||||
"default_mode": "",
|
"external_ui_download_detour": "",
|
||||||
|
"secret": "",
|
||||||
// Deprecated
|
"default_mode": "",
|
||||||
|
"access_control_allow_origin": [],
|
||||||
"store_mode": false,
|
"access_control_allow_private_network": false,
|
||||||
"store_selected": false,
|
|
||||||
"store_fakeip": false,
|
// Deprecated
|
||||||
"cache_file": "",
|
|
||||||
"cache_id": ""
|
"store_mode": false,
|
||||||
}
|
"store_selected": false,
|
||||||
```
|
"store_fakeip": false,
|
||||||
|
"cache_file": "",
|
||||||
|
"cache_id": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "示例 (在线)"
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"external_controller": "127.0.0.1:9090",
|
||||||
|
"access_control_allow_origin": [
|
||||||
|
"http://127.0.0.1",
|
||||||
|
"http://yacd.haishan.me"
|
||||||
|
],
|
||||||
|
"access_control_allow_private_network": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "示例 (下载)"
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"external_controller": "0.0.0.0:9090",
|
||||||
|
"external_ui": "dashboard"
|
||||||
|
// external_ui_download_detour: "direct"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note ""
|
||||||
|
|
||||||
|
当内容只有一项时,可以忽略 JSON 数组 [] 标签
|
||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
@@ -61,6 +105,22 @@ Clash 中的默认模式,默认使用 `Rule`。
|
|||||||
|
|
||||||
此设置没有直接影响,但可以通过 `clash_mode` 规则项在路由和 DNS 规则中使用。
|
此设置没有直接影响,但可以通过 `clash_mode` 规则项在路由和 DNS 规则中使用。
|
||||||
|
|
||||||
|
#### access_control_allow_origin
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
允许的 CORS 来源,默认使用 `*`。
|
||||||
|
|
||||||
|
要从公共网站访问私有网络上的 Clash API,必须在 `access_control_allow_origin` 中明确指定它而不是使用 `*`。
|
||||||
|
|
||||||
|
#### access_control_allow_private_network
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
允许从私有网络访问。
|
||||||
|
|
||||||
|
要从公共网站访问私有网络上的 Clash API,必须启用 `access_control_allow_private_network`。
|
||||||
|
|
||||||
#### store_mode
|
#### store_mode
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.8.0 废弃"
|
!!! failure "已在 sing-box 1.8.0 废弃"
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
|||||||
|
|
||||||
#### fallback
|
#### fallback
|
||||||
|
|
||||||
!!! quote ""
|
!!! failure ""
|
||||||
|
|
||||||
There is no evidence that GFW detects and blocks Trojan servers based on HTTP responses, and opening the standard http/s port on the server is a much bigger signature.
|
There is no evidence that GFW detects and blocks Trojan servers based on HTTP responses, and opening the standard http/s port on the server is a much bigger signature.
|
||||||
|
|
||||||
|
|||||||
@@ -232,12 +232,12 @@ Automatically configure iptables/nftables to redirect connections.
|
|||||||
|
|
||||||
*In Android*:
|
*In Android*:
|
||||||
|
|
||||||
Only local connections are forwarded. To share your VPN connection over hotspot or repeater,
|
Only local IPv4 connections are forwarded. To share your VPN connection over hotspot or repeater,
|
||||||
use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
|
use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
|
||||||
|
|
||||||
*In Linux*:
|
*In Linux*:
|
||||||
|
|
||||||
`auto_route` with `auto_redirect` now works as expected on routers **without intervention**.
|
`auto_route` with `auto_redirect` works as expected on routers **without intervention**.
|
||||||
|
|
||||||
#### auto_redirect_input_mark
|
#### auto_redirect_input_mark
|
||||||
|
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
仅支持 Linux,且需要 `auto_route` 已启用。
|
仅支持 Linux,且需要 `auto_route` 已启用。
|
||||||
|
|
||||||
自动配置 iptables 以重定向 TCP 连接。
|
自动配置 iptables/nftables 以重定向连接。
|
||||||
|
|
||||||
*在 Android 中*:
|
*在 Android 中*:
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
*在 Linux 中*:
|
*在 Linux 中*:
|
||||||
|
|
||||||
带有 `auto_redirect `的 `auto_route` 现在可以在路由器上按预期工作,**无需干预**。
|
带有 `auto_redirect `的 `auto_route` 可以在路由器上按预期工作,**无需干预**。
|
||||||
|
|
||||||
#### auto_redirect_input_mark
|
#### auto_redirect_input_mark
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
!!! quote "Changes in sing-box 1.10.0"
|
!!! quote "Changes in sing-box 1.10.0"
|
||||||
|
|
||||||
:material-plus: [client](#client)
|
:material-plus: [client](#client)
|
||||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
: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_match_source](#rule_set_ip_cidr_match_source)
|
||||||
|
:material-plus: [process_path_regex](#process_path_regex)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.8.0"
|
!!! quote "Changes in sing-box 1.8.0"
|
||||||
|
|
||||||
@@ -101,6 +102,9 @@ icon: material/alert-decagram
|
|||||||
"process_path": [
|
"process_path": [
|
||||||
"/usr/bin/curl"
|
"/usr/bin/curl"
|
||||||
],
|
],
|
||||||
|
"process_path_regex": [
|
||||||
|
"^/usr/bin/.+"
|
||||||
|
],
|
||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
@@ -277,6 +281,16 @@ Match process name.
|
|||||||
|
|
||||||
Match process path.
|
Match process path.
|
||||||
|
|
||||||
|
#### process_path_regex
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
Match process path using regular expression.
|
||||||
|
|
||||||
#### package_name
|
#### package_name
|
||||||
|
|
||||||
Match android package name.
|
Match android package name.
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
:material-plus: [client](#client)
|
:material-plus: [client](#client)
|
||||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
: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: [process_path_regex](#process_path_regex)
|
||||||
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.8.0 中的更改"
|
!!! quote "sing-box 1.8.0 中的更改"
|
||||||
|
|
||||||
@@ -99,6 +100,9 @@ icon: material/alert-decagram
|
|||||||
"process_path": [
|
"process_path": [
|
||||||
"/usr/bin/curl"
|
"/usr/bin/curl"
|
||||||
],
|
],
|
||||||
|
"process_path_regex": [
|
||||||
|
"^/usr/bin/.+"
|
||||||
|
],
|
||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
@@ -275,6 +279,16 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
匹配进程路径。
|
匹配进程路径。
|
||||||
|
|
||||||
|
#### process_path_regex
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
|
使用正则表达式匹配进程路径。
|
||||||
|
|
||||||
#### package_name
|
#### package_name
|
||||||
|
|
||||||
匹配 Android 应用包名。
|
匹配 Android 应用包名。
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ icon: material/new-box
|
|||||||
:material-plus: QUIC client type detect support for QUIC
|
:material-plus: QUIC client type detect support for QUIC
|
||||||
:material-plus: Chromium support for QUIC
|
:material-plus: Chromium support for QUIC
|
||||||
:material-plus: BitTorrent support
|
:material-plus: BitTorrent support
|
||||||
:material-plus: DTLS support
|
:material-plus: DTLS support
|
||||||
|
:material-plus: SSH support
|
||||||
|
:material-plus: RDP support
|
||||||
|
|
||||||
If enabled in the inbound, the protocol and domain name (if present) of by the connection can be sniffed.
|
If enabled in the inbound, the protocol and domain name (if present) of by the connection can be sniffed.
|
||||||
|
|
||||||
@@ -22,6 +24,8 @@ If enabled in the inbound, the protocol and domain name (if present) of by the c
|
|||||||
| TCP/UDP | `dns` | / | / |
|
| TCP/UDP | `dns` | / | / |
|
||||||
| TCP/UDP | `bittorrent` | / | / |
|
| TCP/UDP | `bittorrent` | / | / |
|
||||||
| UDP | `dtls` | / | / |
|
| UDP | `dtls` | / | / |
|
||||||
|
| TCP | `ssh` | / | SSH Client Name |
|
||||||
|
| TCP | `rdp` | / | / |
|
||||||
|
|
||||||
| QUIC Client | Type |
|
| QUIC Client | Type |
|
||||||
|:------------------------:|:----------:|
|
|:------------------------:|:----------:|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ icon: material/new-box
|
|||||||
:material-plus: QUIC 的 客户端类型探测支持
|
:material-plus: QUIC 的 客户端类型探测支持
|
||||||
:material-plus: QUIC 的 Chromium 支持
|
:material-plus: QUIC 的 Chromium 支持
|
||||||
:material-plus: BitTorrent 支持
|
:material-plus: BitTorrent 支持
|
||||||
:material-plus: DTLS 支持
|
:material-plus: DTLS 支持
|
||||||
|
:material-plus: SSH 支持
|
||||||
|
:material-plus: RDP 支持
|
||||||
|
|
||||||
如果在入站中启用,则可以嗅探连接的协议和域名(如果存在)。
|
如果在入站中启用,则可以嗅探连接的协议和域名(如果存在)。
|
||||||
|
|
||||||
@@ -22,6 +24,8 @@ icon: material/new-box
|
|||||||
| TCP/UDP | `dns` | / | / |
|
| TCP/UDP | `dns` | / | / |
|
||||||
| TCP/UDP | `bittorrent` | / | / |
|
| TCP/UDP | `bittorrent` | / | / |
|
||||||
| UDP | `dtls` | / | / |
|
| UDP | `dtls` | / | / |
|
||||||
|
| TCP | `ssh` | / | SSH 客户端名称 |
|
||||||
|
| TCP | `rdp` | / | / |
|
||||||
|
|
||||||
| QUIC 客户端 | 类型 |
|
| QUIC 客户端 | 类型 |
|
||||||
|:------------------------:|:----------:|
|
|:------------------------:|:----------:|
|
||||||
|
|||||||
@@ -57,6 +57,9 @@
|
|||||||
"process_path": [
|
"process_path": [
|
||||||
"/usr/bin/curl"
|
"/usr/bin/curl"
|
||||||
],
|
],
|
||||||
|
"process_path_regex": [
|
||||||
|
"^/usr/bin/.+"
|
||||||
|
],
|
||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
@@ -160,6 +163,16 @@ Match process name.
|
|||||||
|
|
||||||
Match process path.
|
Match process path.
|
||||||
|
|
||||||
|
#### process_path_regex
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
Match process path using regular expression.
|
||||||
|
|
||||||
#### package_name
|
#### package_name
|
||||||
|
|
||||||
Match android package name.
|
Match android package name.
|
||||||
|
|||||||
@@ -83,7 +83,10 @@
|
|||||||
|
|
||||||
如果设置,域名将在请求发出之前解析为 IP。
|
如果设置,域名将在请求发出之前解析为 IP。
|
||||||
|
|
||||||
默认使用 `dns.strategy`。
|
| 出站 | 受影响的域名 | 默认回退值 |
|
||||||
|
|----------|--------------------------|-------------------------------------------|
|
||||||
|
| `direct` | 请求中的域名 | `inbound.domain_strategy` |
|
||||||
|
| others | 服务器地址中的域名 | / |
|
||||||
|
|
||||||
#### fallback_delay
|
#### fallback_delay
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
!!! quote "Changes in sing-box 1.8.0"
|
---
|
||||||
|
icon: material/alert-decagram
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.10.0"
|
||||||
|
|
||||||
:material-alert-decagram: [utls](#utls)
|
:material-alert-decagram: [utls](#utls)
|
||||||
|
|
||||||
@@ -210,28 +214,25 @@ The path to the server private key, in PEM format.
|
|||||||
|
|
||||||
==Client only==
|
==Client only==
|
||||||
|
|
||||||
!!! note ""
|
!!! failure ""
|
||||||
|
|
||||||
uTLS is poorly maintained and the effect may be unproven, use at your own risk.
|
There is no evidence that GFW detects and blocks servers based on TLS client fingerprinting, and using an imperfect emulation that has not been security reviewed could pose security risks.
|
||||||
|
|
||||||
uTLS is a fork of "crypto/tls", which provides ClientHello fingerprinting resistance.
|
uTLS is a fork of "crypto/tls", which provides ClientHello fingerprinting resistance.
|
||||||
|
|
||||||
Available fingerprint values:
|
Available fingerprint values:
|
||||||
|
|
||||||
!!! question "Since sing-box 1.8.0"
|
!!! warning "Removed since sing-box 1.10.0"
|
||||||
|
|
||||||
:material-plus: chrome_psk
|
Some legacy chrome fingerprints have been removed and will fallback to chrome:
|
||||||
:material-plus: chrome_psk_shuffle
|
|
||||||
:material-plus: chrome_padding_psk_shuffle
|
:material-close: chrome_psk
|
||||||
:material-plus: chrome_pq
|
:material-close: chrome_psk_shuffle
|
||||||
:material-plus: chrome_pq_psk
|
:material-close: chrome_padding_psk_shuffle
|
||||||
|
:material-close: chrome_pq
|
||||||
|
:material-close: chrome_pq_psk
|
||||||
|
|
||||||
* chrome
|
* chrome
|
||||||
* chrome_psk
|
|
||||||
* chrome_psk_shuffle
|
|
||||||
* chrome_padding_psk_shuffle
|
|
||||||
* chrome_pq
|
|
||||||
* chrome_pq_psk
|
|
||||||
* firefox
|
* firefox
|
||||||
* edge
|
* edge
|
||||||
* safari
|
* safari
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
!!! quote "sing-box 1.8.0 中的更改"
|
---
|
||||||
|
icon: material/alert-decagram
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.10.0 中的更改"
|
||||||
|
|
||||||
:material-alert-decagram: [utls](#utls)
|
:material-alert-decagram: [utls](#utls)
|
||||||
|
|
||||||
@@ -44,8 +48,8 @@
|
|||||||
"handshake": {
|
"handshake": {
|
||||||
"server": "google.com",
|
"server": "google.com",
|
||||||
"server_port": 443,
|
"server_port": 443,
|
||||||
|
...
|
||||||
... // 拨号字段
|
// 拨号字段
|
||||||
},
|
},
|
||||||
"private_key": "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
|
"private_key": "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
|
||||||
"short_id": [
|
"short_id": [
|
||||||
@@ -202,28 +206,25 @@ TLS 版本值:
|
|||||||
|
|
||||||
==仅客户端==
|
==仅客户端==
|
||||||
|
|
||||||
!!! note ""
|
!!! failure ""
|
||||||
|
|
||||||
uTLS 维护不善且其效果可能未经证实,使用风险自负。
|
没有证据表明 GFW 根据 TLS 客户端指纹检测并阻止服务器,并且,使用一个未经安全审查的不完美模拟可能带来安全隐患。
|
||||||
|
|
||||||
uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻力。
|
uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻力。
|
||||||
|
|
||||||
可用的指纹值:
|
可用的指纹值:
|
||||||
|
|
||||||
!!! question "自 sing-box 1.8.0 起"
|
!!! warning "已在 sing-box 1.10.0 移除"
|
||||||
|
|
||||||
:material-plus: chrome_psk
|
一些旧 chrome 指纹已被删除,并将会退到 chrome:
|
||||||
:material-plus: chrome_psk_shuffle
|
|
||||||
:material-plus: chrome_padding_psk_shuffle
|
:material-close: chrome_psk
|
||||||
:material-plus: chrome_pq
|
:material-close: chrome_psk_shuffle
|
||||||
:material-plus: chrome_pq_psk
|
:material-close: chrome_padding_psk_shuffle
|
||||||
|
:material-close: chrome_pq
|
||||||
|
:material-close: chrome_pq_psk
|
||||||
|
|
||||||
* chrome
|
* chrome
|
||||||
* chrome_psk
|
|
||||||
* chrome_psk_shuffle
|
|
||||||
* chrome_padding_psk_shuffle
|
|
||||||
* chrome_pq
|
|
||||||
* chrome_pq_psk
|
|
||||||
* firefox
|
* firefox
|
||||||
* edge
|
* edge
|
||||||
* safari
|
* safari
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ icon: material/delete-alert
|
|||||||
|
|
||||||
Old fields are deprecated and will be removed in sing-box 1.11.0.
|
Old fields are deprecated and will be removed in sing-box 1.11.0.
|
||||||
|
|
||||||
|
#### Match source rule items are renamed
|
||||||
|
|
||||||
|
`rule_set_ipcidr_match_source` route and DNS rule items are renamed to
|
||||||
|
`rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0.
|
||||||
|
|
||||||
#### Drop support for go1.18 and go1.19
|
#### Drop support for go1.18 and go1.19
|
||||||
|
|
||||||
Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.
|
Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.
|
||||||
|
|||||||
@@ -6,13 +6,18 @@ icon: material/delete-alert
|
|||||||
|
|
||||||
## 1.10.0
|
## 1.10.0
|
||||||
|
|
||||||
|
#### Match source 规则项已重命名
|
||||||
|
|
||||||
|
`rule_set_ipcidr_match_source` 路由和 DNS 规则项已被重命名为
|
||||||
|
`rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。
|
||||||
|
|
||||||
#### TUN 地址字段已合并
|
#### TUN 地址字段已合并
|
||||||
|
|
||||||
`inet4_address` 和 `inet6_address` 已合并为 `address`,
|
`inet4_address` 和 `inet6_address` 已合并为 `address`,
|
||||||
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
||||||
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
||||||
|
|
||||||
旧字段已废弃,且将在 sing-box 1.11.0 中移除。
|
旧字段已废弃,且将在 sing-box 1.11.0 中被移除。
|
||||||
|
|
||||||
#### 移除对 go1.18 和 go1.19 的支持
|
#### 移除对 go1.18 和 go1.19 的支持
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,6 @@ description: Welcome to the wiki page for the sing-box project.
|
|||||||
|
|
||||||
# :material-home: Home
|
# :material-home: Home
|
||||||
|
|
||||||
!!! failure "Help needed"
|
|
||||||
|
|
||||||
Due to problems with our Apple developer account, sing-box apps on Apple platforms are temporarily unavailable for download or update.
|
|
||||||
|
|
||||||
If your company or organization is willing to help us return to the App Store, please [contact us](mailto:contact@sagernet.org).
|
|
||||||
|
|
||||||
Welcome to the wiki page for the sing-box project.
|
Welcome to the wiki page for the sing-box project.
|
||||||
|
|
||||||
The universal proxy platform.
|
The universal proxy platform.
|
||||||
|
|||||||
@@ -6,26 +6,18 @@ icon: material/file-code
|
|||||||
|
|
||||||
## :material-graph: Requirements
|
## :material-graph: Requirements
|
||||||
|
|
||||||
Before sing-box 1.4.0:
|
### sing-box 1.10
|
||||||
|
|
||||||
* Go 1.18.5 - 1.20.x
|
* Go 1.20.0 - ~
|
||||||
|
|
||||||
Since sing-box 1.4.0:
|
|
||||||
|
|
||||||
* Go 1.18.5 - ~
|
|
||||||
* Go 1.20.0 - ~ with tag `with_quic` enabled
|
|
||||||
|
|
||||||
Since sing-box 1.5.0:
|
|
||||||
|
|
||||||
* Go 1.18.5 - ~
|
|
||||||
* Go 1.20.0 - ~ with tag `with_quic` or `with_ech` enabled
|
|
||||||
|
|
||||||
Since sing-box 1.8.0:
|
|
||||||
|
|
||||||
* Go 1.18.5 - ~
|
|
||||||
* Go 1.20.0 - ~ with tag `with_quic`, or `with_utls` enabled
|
* Go 1.20.0 - ~ with tag `with_quic`, or `with_utls` enabled
|
||||||
* Go 1.21.0 - ~ with tag `with_ech` enabled
|
* Go 1.21.0 - ~ with tag `with_ech` enabled
|
||||||
|
|
||||||
|
### sing-box 1.9
|
||||||
|
|
||||||
|
* Go 1.18.5 - 1.22.x
|
||||||
|
* Go 1.20.0 - 1.22.x with tag `with_quic`, or `with_utls` enabled
|
||||||
|
* Go 1.21.0 - 1.22.x with tag `with_ech` enabled
|
||||||
|
|
||||||
You can download and install Go from: https://go.dev/doc/install, latest version is recommended.
|
You can download and install Go from: https://go.dev/doc/install, latest version is recommended.
|
||||||
|
|
||||||
## :material-fast-forward: Simple Build
|
## :material-fast-forward: Simple Build
|
||||||
|
|||||||
@@ -6,25 +6,17 @@ icon: material/file-code
|
|||||||
|
|
||||||
## :material-graph: 要求
|
## :material-graph: 要求
|
||||||
|
|
||||||
sing-box 1.4.0 前:
|
### sing-box 1.10
|
||||||
|
|
||||||
* Go 1.18.5 - 1.20.x
|
* Go 1.20.0 - ~
|
||||||
|
* Go 1.20.0 - ~ with tag `with_quic`, or `with_utls` enabled
|
||||||
|
* Go 1.21.0 - ~ with tag `with_ech` enabled
|
||||||
|
|
||||||
从 sing-box 1.4.0:
|
### sing-box 1.9
|
||||||
|
|
||||||
* Go 1.18.5 - ~
|
* Go 1.18.5 - 1.22.x
|
||||||
* Go 1.20.0 - ~ 如果启用构建标记 `with_quic`
|
* Go 1.20.0 - 1.22.x with tag `with_quic`, or `with_utls` enabled
|
||||||
|
* Go 1.21.0 - 1.22.x with tag `with_ech` enabled
|
||||||
从 sing-box 1.5.0:
|
|
||||||
|
|
||||||
* Go 1.18.5 - ~
|
|
||||||
* Go 1.20.0 - ~ 如果启用构建标记 `with_quic` 或 `with_ech`
|
|
||||||
|
|
||||||
从 sing-box 1.8.0:
|
|
||||||
|
|
||||||
* Go 1.18.5 - ~
|
|
||||||
* Go 1.20.0 - ~ 如果启用构建标记 `with_quic` 或 `with_utls`
|
|
||||||
* Go 1.20.1 - ~ 如果启用构建标记 `with_ech`
|
|
||||||
|
|
||||||
您可以从 https://go.dev/doc/install 下载并安装 Go,推荐使用最新版本。
|
您可以从 https://go.dev/doc/install 下载并安装 Go,推荐使用最新版本。
|
||||||
|
|
||||||
|
|||||||
@@ -24,14 +24,7 @@ icon: material/package
|
|||||||
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo
|
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo
|
||||||
sudo dnf install sing-box # or sing-box-beta
|
sudo dnf install sing-box # or sing-box-beta
|
||||||
```
|
```
|
||||||
|
(This applies to any distribution that uses `dnf` as the package manager: Fedora, CentOS, even OpenSUSE with DNF installed.)
|
||||||
=== ":material-redhat: CentOS / YUM"
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo yum install -y yum-utils
|
|
||||||
sudo yum-config-manager --add-repo https://sing-box.app/sing-box.repo
|
|
||||||
sudo yum install sing-box # or sing-box-beta
|
|
||||||
```
|
|
||||||
|
|
||||||
## :material-download-box: Manual Installation
|
## :material-download-box: Manual Installation
|
||||||
|
|
||||||
@@ -46,6 +39,7 @@ icon: material/package
|
|||||||
```bash
|
```bash
|
||||||
bash <(curl -fsSL https://sing-box.app/rpm-install.sh)
|
bash <(curl -fsSL https://sing-box.app/rpm-install.sh)
|
||||||
```
|
```
|
||||||
|
(This applies to any distribution that uses `rpm` and `systemd`. Because of how `rpm` defines dependencies, if it installs, it probably works.)
|
||||||
|
|
||||||
=== ":simple-archlinux: Archlinux / PKG"
|
=== ":simple-archlinux: Archlinux / PKG"
|
||||||
|
|
||||||
@@ -63,6 +57,7 @@ icon: material/package
|
|||||||
| nixpkgs | NixOS | `nix-env -iA nixos.sing-box` | [][nixpkgs] |
|
| nixpkgs | NixOS | `nix-env -iA nixos.sing-box` | [][nixpkgs] |
|
||||||
| Homebrew | macOS / Linux | `brew install sing-box` | [][brew] |
|
| Homebrew | macOS / Linux | `brew install sing-box` | [][brew] |
|
||||||
| APK | Alpine | `apk add sing-box` | [][alpine] |
|
| APK | Alpine | `apk add sing-box` | [][alpine] |
|
||||||
|
| DEB | AOSC | `apt install sing-box` | [][aosc] |
|
||||||
|
|
||||||
=== ":material-apple: macOS"
|
=== ":material-apple: macOS"
|
||||||
|
|
||||||
@@ -90,6 +85,22 @@ icon: material/package
|
|||||||
|------------|----------|------------------------|--------------------------------------------------------------------------------------------|
|
|------------|----------|------------------------|--------------------------------------------------------------------------------------------|
|
||||||
| FreshPorts | FreeBSD | `pkg install sing-box` | [][ports] |
|
| FreshPorts | FreeBSD | `pkg install sing-box` | [][ports] |
|
||||||
|
|
||||||
|
## :material-alert: Problematic Sources
|
||||||
|
|
||||||
|
| Type | Platform | Link | Promblem(s) |
|
||||||
|
|------------|----------|-------------------------------------------------------------------------------------------|-----------------------------------------|
|
||||||
|
| DEB | AOSC | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | Problematic build tag list modification |
|
||||||
|
| Homebrew | / | [homebrew-core][brew] | Problematic build tag list modification |
|
||||||
|
| Termux | Android | [termux-packages][termux] | Problematic build tag list modification |
|
||||||
|
| FreshPorts | FreeBSD | [FreeBSD ports][ports] | Old Go (go1.20) |
|
||||||
|
|
||||||
|
If you are a user of them, please report issues to them:
|
||||||
|
|
||||||
|
1. Please do not modify release build tags without full understanding of the related functionality: enabling non-default
|
||||||
|
labels may result in decreased performance; the lack of default labels may cause user confusion.
|
||||||
|
2. sing-box supports compiling with some older Go versions, but it is not recommended (especially versions that are no
|
||||||
|
longer supported by Go).
|
||||||
|
|
||||||
## :material-book-multiple: Service Management
|
## :material-book-multiple: Service Management
|
||||||
|
|
||||||
For Linux systems with [systemd][systemd], usually the installation already includes a sing-box service,
|
For Linux systems with [systemd][systemd], usually the installation already includes a sing-box service,
|
||||||
@@ -128,4 +139,6 @@ you can manage the service using the following command:
|
|||||||
|
|
||||||
[ports]: https://www.freshports.org/net/sing-box
|
[ports]: https://www.freshports.org/net/sing-box
|
||||||
|
|
||||||
|
[aosc]: https://packages.aosc.io/packages/sing-box
|
||||||
|
|
||||||
[systemd]: https://systemd.io/
|
[systemd]: https://systemd.io/
|
||||||
|
|||||||
@@ -24,14 +24,7 @@ icon: material/package
|
|||||||
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo
|
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo
|
||||||
sudo dnf install sing-box # or sing-box-beta
|
sudo dnf install sing-box # or sing-box-beta
|
||||||
```
|
```
|
||||||
|
(这适用于任何使用 `dnf` 作为包管理器的发行版:Fedora、CentOS,甚至安装了 DNF 的 OpenSUSE。)
|
||||||
=== ":material-redhat: CentOS / YUM"
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo yum install -y yum-utils
|
|
||||||
sudo yum-config-manager --add-repo https://sing-box.app/sing-box.repo
|
|
||||||
sudo yum install sing-box # or sing-box-beta
|
|
||||||
```
|
|
||||||
|
|
||||||
## :material-download-box: 手动安装
|
## :material-download-box: 手动安装
|
||||||
|
|
||||||
@@ -46,6 +39,7 @@ icon: material/package
|
|||||||
```bash
|
```bash
|
||||||
bash <(curl -fsSL https://sing-box.app/rpm-install.sh)
|
bash <(curl -fsSL https://sing-box.app/rpm-install.sh)
|
||||||
```
|
```
|
||||||
|
(这适用于任何使用 `rpm` 和 `systemd` 的发行版。由于 `rpm` 定义依赖关系的方式,如果安装成功,就多半能用。)
|
||||||
|
|
||||||
=== ":simple-archlinux: Archlinux / PKG"
|
=== ":simple-archlinux: Archlinux / PKG"
|
||||||
|
|
||||||
@@ -63,6 +57,7 @@ icon: material/package
|
|||||||
| nixpkgs | NixOS | `nix-env -iA nixos.sing-box` | [][nixpkgs] |
|
| nixpkgs | NixOS | `nix-env -iA nixos.sing-box` | [][nixpkgs] |
|
||||||
| Homebrew | macOS / Linux | `brew install sing-box` | [][brew] |
|
| Homebrew | macOS / Linux | `brew install sing-box` | [][brew] |
|
||||||
| APK | Alpine | `apk add sing-box` | [][alpine] |
|
| APK | Alpine | `apk add sing-box` | [][alpine] |
|
||||||
|
| DEB | AOSC | `apt install sing-box` | [][aosc] |
|
||||||
|
|
||||||
=== ":material-apple: macOS"
|
=== ":material-apple: macOS"
|
||||||
|
|
||||||
@@ -90,6 +85,20 @@ icon: material/package
|
|||||||
|------------|---------|------------------------|--------------------------------------------------------------------------------------------|
|
|------------|---------|------------------------|--------------------------------------------------------------------------------------------|
|
||||||
| FreshPorts | FreeBSD | `pkg install sing-box` | [][ports] |
|
| FreshPorts | FreeBSD | `pkg install sing-box` | [][ports] |
|
||||||
|
|
||||||
|
## :material-alert: 存在问题的源
|
||||||
|
|
||||||
|
| 类型 | 平台 | 链接 | 原因 |
|
||||||
|
|------------|---------|-------------------------------------------------------------------------------------------|-----------------|
|
||||||
|
| DEB | AOSC | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | 存在问题的构建标志列表修改 |
|
||||||
|
| Homebrew | / | [homebrew-core][brew] | 存在问题的构建标志列表修改 |
|
||||||
|
| Termux | Android | [termux-packages][termux] | 存在问题的构建标志列表修改 |
|
||||||
|
| FreshPorts | FreeBSD | [FreeBSD ports][ports] | 太旧的 Go (go1.20) |
|
||||||
|
|
||||||
|
如果您是其用户,请向他们报告问题:
|
||||||
|
|
||||||
|
1. 在未完全了解相关功能的情况下,请勿修改发布版本标签:启用非默认标签可能会导致性能下降;缺少默认标签可能会引起用户混淆。
|
||||||
|
2. sing-box 支持使用一些较旧的 Go 版本进行编译,但不推荐使用(特别是已不再受 Go 支持的版本)。
|
||||||
|
|
||||||
## :material-book-multiple: 服务管理
|
## :material-book-multiple: 服务管理
|
||||||
|
|
||||||
对于带有 [systemd][systemd] 的 Linux 系统,通常安装已经包含 sing-box 服务,
|
对于带有 [systemd][systemd] 的 Linux 系统,通常安装已经包含 sing-box 服务,
|
||||||
@@ -124,4 +133,6 @@ icon: material/package
|
|||||||
|
|
||||||
[ports]: https://www.freshports.org/net/sing-box
|
[ports]: https://www.freshports.org/net/sing-box
|
||||||
|
|
||||||
|
[aosc]: https://packages.aosc.io/packages/sing-box
|
||||||
|
|
||||||
[systemd]: https://systemd.io/
|
[systemd]: https://systemd.io/
|
||||||
|
|||||||
@@ -4,16 +4,17 @@ icon: material/lightning-bolt
|
|||||||
|
|
||||||
# Hysteria 2
|
# Hysteria 2
|
||||||
|
|
||||||
The most popular Chinese-made simple protocol based on QUIC, the selling point is Brutal,
|
Hysteria 2 is a simple, Chinese-made protocol based on QUIC.
|
||||||
a congestion control algorithm that can resist packet loss by manually specifying the required rate by the user.
|
The selling point is Brutal, a congestion control algorithm that
|
||||||
|
tries to achieve a user-defined bandwidth despite packet loss.
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
|
|
||||||
Even though GFW rarely blocks UDP-based proxies, such protocols actually have far more characteristics than TCP based proxies.
|
Even though GFW rarely blocks UDP-based proxies, such protocols actually have far more obvious characteristics than TCP based proxies.
|
||||||
|
|
||||||
| Specification | Binary Characteristics | Active Detect Hiddenness |
|
| Specification | Resists passive detection | Resists active probes |
|
||||||
|---------------------------------------------------------------------------|------------------------|--------------------------|
|
|---------------------------------------------------------------------------|---------------------------|-----------------------|
|
||||||
| [hysteria.network](https://v2.hysteria.network/docs/developers/Protocol/) | :material-alert: | :material-check: |
|
| [hysteria.network](https://v2.hysteria.network/docs/developers/Protocol/) | :material-alert: | :material-check: |
|
||||||
|
|
||||||
## :material-text-box-check: Password Generator
|
## :material-text-box-check: Password Generator
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ To use sing-box with the official program, you need to fill in that combination
|
|||||||
Replace `up_mbps` and `down_mbps` values with the actual bandwidth of your server.
|
Replace `up_mbps` and `down_mbps` values with the actual bandwidth of your server.
|
||||||
|
|
||||||
=== ":material-harddisk: With local certificate"
|
=== ":material-harddisk: With local certificate"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
|
|||||||
@@ -4,15 +4,18 @@ icon: material/send
|
|||||||
|
|
||||||
# Shadowsocks
|
# Shadowsocks
|
||||||
|
|
||||||
As the most well-known Chinese-made proxy protocol,
|
Shadowsocks is the most well-known Chinese-made proxy protocol.
|
||||||
Shadowsocks exists in multiple versions,
|
It exists in multiple versions, but only AEAD 2022 ciphers
|
||||||
but only AEAD 2022 ciphers TCP with multiplexing is recommended.
|
over TCP with multiplexing is recommended.
|
||||||
|
|
||||||
| Ciphers | Specification | Cryptographic Security | Binary Characteristics | Active Detect Hiddenness |
|
| Ciphers | Specification | Cryptographically sound | Resists passive detection | Resists active probes |
|
||||||
|----------------|------------------------------------------------------------|------------------------|------------------------|--------------------------|
|
|----------------|------------------------------------------------------------|-------------------------|---------------------------|-----------------------|
|
||||||
| Stream Ciphers | [shadowsocks.org](https://shadowsocks.org/doc/stream.html) | :material-alert: | :material-alert: | :material-alert: |
|
| Stream Ciphers | [shadowsocks.org](https://shadowsocks.org/doc/stream.html) | :material-alert: | :material-alert: | :material-alert: |
|
||||||
| AEAD | [shadowsocks.org](https://shadowsocks.org/doc/aead.html) | :material-check: | :material-alert: | :material-alert: |
|
| AEAD | [shadowsocks.org](https://shadowsocks.org/doc/aead.html) | :material-check: | :material-alert: | :material-alert: |
|
||||||
| AEAD 2022 | [shadowsocks.org](https://shadowsocks.org/doc/sip022.html) | :material-check: | :material-check: | :material-help: |
|
| AEAD 2022 | [shadowsocks.org](https://shadowsocks.org/doc/sip022.html) | :material-check: | :material-check: | :material-help: |
|
||||||
|
|
||||||
|
(We strongly recommend using multiplexing to send UDP traffic over TCP, because
|
||||||
|
doing otherwise is vulnerable to passive detection.)
|
||||||
|
|
||||||
## :material-text-box-check: Password Generator
|
## :material-text-box-check: Password Generator
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ icon: material/horse
|
|||||||
|
|
||||||
# Trojan
|
# Trojan
|
||||||
|
|
||||||
As the most commonly used TLS proxy made in China, Trojan can be used in various combinations,
|
Torjan is the most commonly used TLS proxy made in China. It can be used in various combinations,
|
||||||
but only the combination of uTLS and multiplexing is recommended.
|
but only the combination of uTLS and multiplexing is recommended.
|
||||||
|
|
||||||
| Protocol and implementation combination | Specification | Binary Characteristics | Active Detect Hiddenness |
|
| Protocol and implementation combination | Specification | Resists passive detection | Resists active probes |
|
||||||
|-----------------------------------------|----------------------------------------------------------------------|------------------------|--------------------------|
|
|-----------------------------------------|----------------------------------------------------------------------|---------------------------|-----------------------|
|
||||||
| Origin / trojan-gfw | [trojan-gfw.github.io](https://trojan-gfw.github.io/trojan/protocol) | :material-check: | :material-check: |
|
| Origin / trojan-gfw | [trojan-gfw.github.io](https://trojan-gfw.github.io/trojan/protocol) | :material-check: | :material-check: |
|
||||||
| Basic Go implementation | / | :material-alert: | :material-check: |
|
| Basic Go implementation | / | :material-alert: | :material-check: |
|
||||||
| with privates transport by V2Ray | No formal definition | :material-alert: | :material-alert: |
|
| with privates transport by V2Ray | No formal definition | :material-alert: | :material-alert: |
|
||||||
| with uTLS enabled | No formal definition | :material-help: | :material-check: |
|
| with uTLS enabled | No formal definition | :material-help: | :material-check: |
|
||||||
|
|
||||||
## :material-text-box-check: Password Generator
|
## :material-text-box-check: Password Generator
|
||||||
|
|
||||||
@@ -211,4 +211,3 @@ but only the combination of uTLS and multiplexing is recommended.
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user