mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
79 Commits
dev-sentry
...
v1.8.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0083d37d71 | ||
|
|
b8ee698c55 | ||
|
|
3c5394aa93 | ||
|
|
392c4be55b | ||
|
|
efdb34de91 | ||
|
|
3bb709db18 | ||
|
|
62185946b5 | ||
|
|
644100357c | ||
|
|
f61ea0da89 | ||
|
|
ee086ea186 | ||
|
|
ff26fb020f | ||
|
|
d8fd51f5f1 | ||
|
|
b256e54a0b | ||
|
|
33881ebd8c | ||
|
|
4d6b7ff054 | ||
|
|
5f1e39a42c | ||
|
|
4f7770e254 | ||
|
|
e8c4c942c0 | ||
|
|
253976d6c0 | ||
|
|
f0571b4122 | ||
|
|
1b71e52e90 | ||
|
|
6d24be23da | ||
|
|
2a45c178fa | ||
|
|
81e214812f | ||
|
|
4d23773a25 | ||
|
|
40a0b69918 | ||
|
|
a7b37c5953 | ||
|
|
03663a5093 | ||
|
|
b08226a850 | ||
|
|
edbae5dc4d | ||
|
|
0f8ad0234b | ||
|
|
661eadc3bd | ||
|
|
50c1290567 | ||
|
|
eaccc9759a | ||
|
|
925214869b | ||
|
|
6a2bfd26d0 | ||
|
|
72a81afb76 | ||
|
|
240abe204c | ||
|
|
7c49196792 | ||
|
|
3a2808cff6 | ||
|
|
005d6cf4cf | ||
|
|
36dff630d6 | ||
|
|
1825869124 | ||
|
|
3cadc90375 | ||
|
|
2c6967d7f9 | ||
|
|
fe866b123a | ||
|
|
cbef1b1e59 | ||
|
|
e21f84932c | ||
|
|
7a679bc328 | ||
|
|
6635dd9abc | ||
|
|
ce164724ea | ||
|
|
a3ef7a7d88 | ||
|
|
71218ef0d3 | ||
|
|
e777b4c6dc | ||
|
|
6815f94180 | ||
|
|
b013acd89d | ||
|
|
f7c2eb6e76 | ||
|
|
3ef9b1b343 | ||
|
|
2224c68959 | ||
|
|
bb7d03d1db | ||
|
|
50036924e8 | ||
|
|
c2c3f7284f | ||
|
|
f6fee53676 | ||
|
|
63b8e8ed23 | ||
|
|
6ae86eda98 | ||
|
|
267d9617b7 | ||
|
|
0a06ccae50 | ||
|
|
8de0fad9f5 | ||
|
|
e05bf6308e | ||
|
|
a20a0cb455 | ||
|
|
d29f7475d2 | ||
|
|
aaa6702863 | ||
|
|
bb928f096a | ||
|
|
9f01d5c5b4 | ||
|
|
11629a931b | ||
|
|
126f825241 | ||
|
|
998cc7bd22 | ||
|
|
3efccaa8f5 | ||
|
|
d57b35ec30 |
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -46,6 +46,7 @@ body:
|
|||||||
description: If you are using the original command line program, please provide the output of the `sing-box version` command.
|
description: If you are using the original command line program, please provide the output of the `sing-box version` command.
|
||||||
value: |-
|
value: |-
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# Replace this line with the output
|
# Replace this line with the output
|
||||||
```
|
```
|
||||||
@@ -71,6 +72,7 @@ body:
|
|||||||
For the Android client, please check the `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` file for crash logs.
|
For the Android client, please check the `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` file for crash logs.
|
||||||
value: |-
|
value: |-
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# Replace this line with logs
|
# Replace this line with logs
|
||||||
```
|
```
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
@@ -46,6 +46,7 @@ body:
|
|||||||
description: 如果您使用原始命令行程序,请提供 `sing-box version` 命令的输出。
|
description: 如果您使用原始命令行程序,请提供 `sing-box version` 命令的输出。
|
||||||
value: |-
|
value: |-
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# 使用输出内容覆盖此行
|
# 使用输出内容覆盖此行
|
||||||
```
|
```
|
||||||
@@ -71,6 +72,7 @@ body:
|
|||||||
对于 Android 图形客户端程序,请检查 `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` 文件以导出崩溃日志。
|
对于 Android 图形客户端程序,请检查 `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` 文件以导出崩溃日志。
|
||||||
value: |-
|
value: |-
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# 使用日志内容覆盖此行
|
# 使用日志内容覆盖此行
|
||||||
```
|
```
|
||||||
|
|||||||
8
.github/workflows/debug.yml
vendored
8
.github/workflows/debug.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Get latest go version
|
- name: Get latest go version
|
||||||
@@ -50,7 +50,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -201,7 +201,7 @@ jobs:
|
|||||||
TAGS: with_clash_api,with_quic
|
TAGS: with_clash_api,with_quic
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Get latest go version
|
- name: Get latest go version
|
||||||
|
|||||||
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Setup QEMU for Docker Buildx
|
- name: Setup QEMU for Docker Buildx
|
||||||
@@ -39,6 +39,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
|
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
|
||||||
target: dist
|
target: dist
|
||||||
|
build-args: |
|
||||||
|
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
||||||
tags: |
|
tags: |
|
||||||
${{ steps.tag.outputs.latest }}
|
${{ steps.tag.outputs.latest }}
|
||||||
${{ steps.tag.outputs.versioned }}
|
${{ steps.tag.outputs.versioned }}
|
||||||
|
|||||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Get latest go version
|
- name: Get latest go version
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
/.idea/
|
/.idea/
|
||||||
/vendor/
|
/vendor/
|
||||||
/*.json
|
/*.json
|
||||||
|
/*.srs
|
||||||
/*.db
|
/*.db
|
||||||
/site/
|
/site/
|
||||||
/bin/
|
/bin/
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ builds:
|
|||||||
- with_ech
|
- with_ech
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_reality_server
|
- with_reality_server
|
||||||
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
targets:
|
targets:
|
||||||
|
- linux_386
|
||||||
- linux_amd64_v1
|
- linux_amd64_v1
|
||||||
- linux_amd64_v3
|
- linux_amd64_v3
|
||||||
- linux_arm64
|
- linux_arm64
|
||||||
@@ -55,11 +57,12 @@ builds:
|
|||||||
- with_ech
|
- with_ech
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_reality_server
|
- with_reality_server
|
||||||
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
- GOROOT=/nix/store/5h8gjl89zx8qxgc572wa3k81zplv8v4z-go-1.20.10/share/go
|
- GOROOT=/nix/store/kg6i737jjqs923jcijnm003h68c1dghj-go-1.20.11/share/go
|
||||||
gobinary: /nix/store/5h8gjl89zx8qxgc572wa3k81zplv8v4z-go-1.20.10/bin/go
|
gobinary: /nix/store/kg6i737jjqs923jcijnm003h68c1dghj-go-1.20.11/bin/go
|
||||||
targets:
|
targets:
|
||||||
- windows_amd64_v1
|
- windows_amd64_v1
|
||||||
- windows_386
|
- windows_386
|
||||||
@@ -83,6 +86,8 @@ builds:
|
|||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_ech
|
- with_ech
|
||||||
- with_utls
|
- with_utls
|
||||||
|
- with_reality_server
|
||||||
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=1
|
- CGO_ENABLED=1
|
||||||
@@ -153,6 +158,7 @@ nfpms:
|
|||||||
formats:
|
formats:
|
||||||
- deb
|
- deb
|
||||||
- rpm
|
- rpm
|
||||||
|
- archlinux
|
||||||
priority: extra
|
priority: extra
|
||||||
contents:
|
contents:
|
||||||
- src: release/config/config.json
|
- src: release/config/config.json
|
||||||
|
|||||||
10
Dockerfile
10
Dockerfile
@@ -1,19 +1,23 @@
|
|||||||
FROM golang:1.21-alpine AS builder
|
FROM --platform=$BUILDPLATFORM golang:1.21-alpine AS builder
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
COPY . /go/src/github.com/sagernet/sing-box
|
COPY . /go/src/github.com/sagernet/sing-box
|
||||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||||
|
ARG TARGETOS TARGETARCH
|
||||||
ARG GOPROXY=""
|
ARG GOPROXY=""
|
||||||
ENV GOPROXY ${GOPROXY}
|
ENV GOPROXY ${GOPROXY}
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
|
ENV GOOS=$TARGETOS
|
||||||
|
ENV GOARCH=$TARGETARCH
|
||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
&& apk add git build-base \
|
&& apk add git build-base \
|
||||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||||
&& go build -v -trimpath -tags with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_clash_api,with_acme \
|
&& go build -v -trimpath -tags \
|
||||||
|
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_clash_api" \
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
FROM alpine AS dist
|
FROM --platform=$TARGETPLATFORM alpine AS dist
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
&& apk upgrade \
|
&& apk upgrade \
|
||||||
|
|||||||
25
Makefile
25
Makefile
@@ -3,7 +3,7 @@ COMMIT = $(shell git rev-parse --short HEAD)
|
|||||||
TAGS_GO118 = with_gvisor,with_dhcp,with_wireguard,with_utls,with_reality_server,with_clash_api
|
TAGS_GO118 = with_gvisor,with_dhcp,with_wireguard,with_utls,with_reality_server,with_clash_api
|
||||||
TAGS_GO120 = with_quic,with_ech
|
TAGS_GO120 = with_quic,with_ech
|
||||||
TAGS ?= $(TAGS_GO118),$(TAGS_GO120)
|
TAGS ?= $(TAGS_GO118),$(TAGS_GO120)
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
|
||||||
|
|
||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||||
@@ -14,7 +14,7 @@ MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
|
|||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
PREFIX ?= $(shell go env GOPATH)
|
PREFIX ?= $(shell go env GOPATH)
|
||||||
|
|
||||||
.PHONY: test release
|
.PHONY: test release docs
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
@@ -61,7 +61,7 @@ proto_install:
|
|||||||
release:
|
release:
|
||||||
go run ./cmd/internal/build goreleaser release --clean --skip-publish || exit 1
|
go run ./cmd/internal/build goreleaser release --clean --skip-publish || exit 1
|
||||||
mkdir dist/release
|
mkdir dist/release
|
||||||
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
|
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/*.pkg.tar.zst dist/release
|
||||||
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release
|
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release
|
||||||
rm -r dist/release
|
rm -r dist/release
|
||||||
|
|
||||||
@@ -73,18 +73,21 @@ update_android_version:
|
|||||||
go run ./cmd/internal/update_android_version
|
go run ./cmd/internal/update_android_version
|
||||||
|
|
||||||
build_android:
|
build_android:
|
||||||
cd ../sing-box-for-android && ./gradlew :app:assembleRelease && ./gradlew --stop
|
cd ../sing-box-for-android && ./gradlew :app:assemblePlayRelease && ./gradlew --stop
|
||||||
|
|
||||||
upload_android:
|
upload_android:
|
||||||
mkdir -p dist/release_android
|
mkdir -p dist/release_android
|
||||||
cp ../sing-box-for-android/app/build/outputs/apk/release/*.apk dist/release_android
|
cp ../sing-box-for-android/app/build/outputs/apk/play/release/*.apk dist/release_android
|
||||||
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release_android
|
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release_android
|
||||||
rm -rf dist/release_android
|
rm -rf dist/release_android
|
||||||
|
|
||||||
release_android: lib_android update_android_version build_android upload_android
|
release_android: lib_android update_android_version build_android upload_android
|
||||||
|
|
||||||
publish_android:
|
publish_android:
|
||||||
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadRelease
|
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle
|
||||||
|
|
||||||
|
publish_android_appcenter:
|
||||||
|
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadPlayRelease
|
||||||
|
|
||||||
build_ios:
|
build_ios:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
@@ -149,10 +152,8 @@ update_apple_version:
|
|||||||
go run ./cmd/internal/update_apple_version
|
go run ./cmd/internal/update_apple_version
|
||||||
|
|
||||||
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_independent
|
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_independent
|
||||||
rm -rf dist
|
|
||||||
|
|
||||||
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
||||||
rm -rf dist
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@go test -v ./... && \
|
@go test -v ./... && \
|
||||||
@@ -181,6 +182,14 @@ lib_install:
|
|||||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20230915142329-c6740b6d2950
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20230915142329-c6740b6d2950
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230915142329-c6740b6d2950
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230915142329-c6740b6d2950
|
||||||
|
|
||||||
|
docs:
|
||||||
|
mkdocs serve
|
||||||
|
|
||||||
|
publish_docs:
|
||||||
|
mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
|
||||||
|
|
||||||
|
docs_install:
|
||||||
|
pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin dist sing-box
|
rm -rf bin dist sing-box
|
||||||
rm -f $(shell go env GOPATH)/sing-box
|
rm -f $(shell go env GOPATH)/sing-box
|
||||||
|
|||||||
104
adapter/conn_router.go
Normal file
104
adapter/conn_router.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConnectionRouter interface {
|
||||||
|
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
|
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouteHandler(
|
||||||
|
metadata InboundContext,
|
||||||
|
router ConnectionRouter,
|
||||||
|
logger logger.ContextLogger,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &routeHandlerWrapper{
|
||||||
|
metadata: metadata,
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouteContextHandler(
|
||||||
|
router ConnectionRouter,
|
||||||
|
logger logger.ContextLogger,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &routeContextHandlerWrapper{
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
|
||||||
|
|
||||||
|
type routeHandlerWrapper struct {
|
||||||
|
metadata InboundContext
|
||||||
|
router ConnectionRouter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
myMetadata := w.metadata
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RouteConnection(ctx, conn, myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
|
myMetadata := w.metadata
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.logger.ErrorContext(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
|
||||||
|
|
||||||
|
type routeContextHandlerWrapper struct {
|
||||||
|
router ConnectionRouter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
myMetadata := ContextFrom(ctx)
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RouteConnection(ctx, conn, *myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
|
myMetadata := ContextFrom(ctx)
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.logger.ErrorContext(ctx, err)
|
||||||
|
}
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClashServer interface {
|
type ClashServer interface {
|
||||||
@@ -13,22 +18,83 @@ type ClashServer interface {
|
|||||||
PreStarter
|
PreStarter
|
||||||
Mode() string
|
Mode() string
|
||||||
ModeList() []string
|
ModeList() []string
|
||||||
StoreSelected() bool
|
|
||||||
StoreFakeIP() bool
|
|
||||||
CacheFile() ClashCacheFile
|
|
||||||
HistoryStorage() *urltest.HistoryStorage
|
HistoryStorage() *urltest.HistoryStorage
|
||||||
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
||||||
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
|
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClashCacheFile interface {
|
type CacheFile interface {
|
||||||
|
Service
|
||||||
|
PreStarter
|
||||||
|
|
||||||
|
StoreFakeIP() bool
|
||||||
|
FakeIPStorage
|
||||||
|
|
||||||
LoadMode() string
|
LoadMode() string
|
||||||
StoreMode(mode string) error
|
StoreMode(mode string) error
|
||||||
LoadSelected(group string) string
|
LoadSelected(group string) string
|
||||||
StoreSelected(group string, selected string) error
|
StoreSelected(group string, selected string) error
|
||||||
LoadGroupExpand(group string) (isExpand bool, loaded bool)
|
LoadGroupExpand(group string) (isExpand bool, loaded bool)
|
||||||
StoreGroupExpand(group string, expand bool) error
|
StoreGroupExpand(group string, expand bool) error
|
||||||
FakeIPStorage
|
LoadRuleSet(tag string) *SavedRuleSet
|
||||||
|
SaveRuleSet(tag string, set *SavedRuleSet) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type SavedRuleSet struct {
|
||||||
|
Content []byte
|
||||||
|
LastUpdated time.Time
|
||||||
|
LastEtag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
err := binary.Write(&buffer, binary.BigEndian, uint8(1))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = rw.WriteUVariant(&buffer, uint64(len(s.Content)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buffer.Write(s.Content)
|
||||||
|
err = binary.Write(&buffer, binary.BigEndian, s.LastUpdated.Unix())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = rw.WriteVString(&buffer, s.LastEtag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
|
||||||
|
reader := bytes.NewReader(data)
|
||||||
|
var version uint8
|
||||||
|
err := binary.Read(reader, binary.BigEndian, &version)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
contentLen, err := rw.ReadUVariant(reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Content = make([]byte, contentLen)
|
||||||
|
_, err = io.ReadFull(reader, s.Content)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var lastUpdated int64
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &lastUpdated)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.LastUpdated = time.Unix(lastUpdated, 0)
|
||||||
|
s.LastEtag, err = rw.ReadVString(reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tracker interface {
|
type Tracker interface {
|
||||||
|
|||||||
@@ -47,6 +47,14 @@ type InboundContext struct {
|
|||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
ProcessInfo *process.Info
|
ProcessInfo *process.Info
|
||||||
FakeIP bool
|
FakeIP bool
|
||||||
|
IPCIDRMatchSource bool
|
||||||
|
|
||||||
|
// rule merge
|
||||||
|
|
||||||
|
SourceAddressMatch bool
|
||||||
|
SourcePortMatch bool
|
||||||
|
DestinationAddressMatch bool
|
||||||
|
DestinationPortMatch bool
|
||||||
|
|
||||||
// dns cache
|
// dns cache
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/geoip"
|
"github.com/sagernet/sing-box/common/geoip"
|
||||||
@@ -20,16 +20,17 @@ type Router interface {
|
|||||||
|
|
||||||
Outbounds() []Outbound
|
Outbounds() []Outbound
|
||||||
Outbound(tag string) (Outbound, bool)
|
Outbound(tag string) (Outbound, bool)
|
||||||
DefaultOutbound(network string) Outbound
|
DefaultOutbound(network string) (Outbound, error)
|
||||||
|
|
||||||
FakeIPStore() FakeIPStore
|
FakeIPStore() FakeIPStore
|
||||||
|
|
||||||
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
ConnectionRouter
|
||||||
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
|
||||||
|
|
||||||
GeoIPReader() *geoip.Reader
|
GeoIPReader() *geoip.Reader
|
||||||
LoadGeosite(code string) (Rule, error)
|
LoadGeosite(code string) (Rule, error)
|
||||||
|
|
||||||
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
|
|
||||||
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
|
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
|
||||||
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
|
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
|
||||||
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
||||||
@@ -44,6 +45,7 @@ type Router interface {
|
|||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
|
WIFIState() WIFIState
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
|
|
||||||
ClashServer() ClashServer
|
ClashServer() ClashServer
|
||||||
@@ -63,11 +65,15 @@ func RouterFromContext(ctx context.Context) Router {
|
|||||||
return service.FromContext[Router](ctx)
|
return service.FromContext[Router](ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HeadlessRule interface {
|
||||||
|
Match(metadata *InboundContext) bool
|
||||||
|
}
|
||||||
|
|
||||||
type Rule interface {
|
type Rule interface {
|
||||||
|
HeadlessRule
|
||||||
Service
|
Service
|
||||||
Type() string
|
Type() string
|
||||||
UpdateGeosite() error
|
UpdateGeosite() error
|
||||||
Match(metadata *InboundContext) bool
|
|
||||||
Outbound() string
|
Outbound() string
|
||||||
String() string
|
String() string
|
||||||
}
|
}
|
||||||
@@ -78,6 +84,22 @@ type DNSRule interface {
|
|||||||
RewriteTTL() *uint32
|
RewriteTTL() *uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RuleSet interface {
|
||||||
|
StartContext(ctx context.Context, startContext RuleSetStartContext) error
|
||||||
|
Close() error
|
||||||
|
HeadlessRule
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleSetStartContext interface {
|
||||||
|
HTTPClient(detour string, dialer N.Dialer) *http.Client
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
|
||||||
type InterfaceUpdateListener interface {
|
type InterfaceUpdateListener interface {
|
||||||
InterfaceUpdated()
|
InterfaceUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WIFIState struct {
|
||||||
|
SSID string
|
||||||
|
BSSID string
|
||||||
|
}
|
||||||
|
|||||||
58
box.go
58
box.go
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"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/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/inbound"
|
"github.com/sagernet/sing-box/inbound"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -32,7 +33,8 @@ type Box struct {
|
|||||||
outbounds []adapter.Outbound
|
outbounds []adapter.Outbound
|
||||||
logFactory log.Factory
|
logFactory log.Factory
|
||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
preServices map[string]adapter.Service
|
preServices1 map[string]adapter.Service
|
||||||
|
preServices2 map[string]adapter.Service
|
||||||
postServices map[string]adapter.Service
|
postServices map[string]adapter.Service
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
@@ -41,21 +43,26 @@ type Options struct {
|
|||||||
option.Options
|
option.Options
|
||||||
Context context.Context
|
Context context.Context
|
||||||
PlatformInterface platform.Interface
|
PlatformInterface platform.Interface
|
||||||
|
PlatformLogWriter log.PlatformWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(options Options) (*Box, error) {
|
func New(options Options) (*Box, error) {
|
||||||
|
createdAt := time.Now()
|
||||||
ctx := options.Context
|
ctx := options.Context
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
}
|
}
|
||||||
ctx = service.ContextWithDefaultRegistry(ctx)
|
ctx = service.ContextWithDefaultRegistry(ctx)
|
||||||
ctx = pause.ContextWithDefaultManager(ctx)
|
ctx = pause.ContextWithDefaultManager(ctx)
|
||||||
createdAt := time.Now()
|
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
||||||
|
var needCacheFile bool
|
||||||
var needClashAPI bool
|
var needClashAPI bool
|
||||||
var needV2RayAPI bool
|
var needV2RayAPI bool
|
||||||
if experimentalOptions.ClashAPI != nil || options.PlatformInterface != nil {
|
if experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled || options.PlatformLogWriter != nil {
|
||||||
|
needCacheFile = true
|
||||||
|
}
|
||||||
|
if experimentalOptions.ClashAPI != nil || options.PlatformLogWriter != nil {
|
||||||
needClashAPI = true
|
needClashAPI = true
|
||||||
}
|
}
|
||||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||||
@@ -71,7 +78,7 @@ func New(options Options) (*Box, error) {
|
|||||||
Observable: needClashAPI,
|
Observable: needClashAPI,
|
||||||
DefaultWriter: defaultLogWriter,
|
DefaultWriter: defaultLogWriter,
|
||||||
BaseTime: createdAt,
|
BaseTime: createdAt,
|
||||||
PlatformWriter: options.PlatformInterface,
|
PlatformWriter: options.PlatformLogWriter,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create log factory")
|
return nil, E.Cause(err, "create log factory")
|
||||||
@@ -144,8 +151,14 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize platform interface")
|
return nil, E.Cause(err, "initialize platform interface")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
preServices := make(map[string]adapter.Service)
|
preServices1 := make(map[string]adapter.Service)
|
||||||
|
preServices2 := make(map[string]adapter.Service)
|
||||||
postServices := make(map[string]adapter.Service)
|
postServices := make(map[string]adapter.Service)
|
||||||
|
if needCacheFile {
|
||||||
|
cacheFile := cachefile.NewCacheFile(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||||
|
preServices1["cache file"] = cacheFile
|
||||||
|
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||||
|
}
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
||||||
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
|
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
|
||||||
@@ -154,7 +167,7 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "create clash api server")
|
return nil, E.Cause(err, "create clash api server")
|
||||||
}
|
}
|
||||||
router.SetClashServer(clashServer)
|
router.SetClashServer(clashServer)
|
||||||
preServices["clash api"] = clashServer
|
preServices2["clash api"] = clashServer
|
||||||
}
|
}
|
||||||
if needV2RayAPI {
|
if needV2RayAPI {
|
||||||
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
||||||
@@ -162,7 +175,7 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "create v2ray api server")
|
return nil, E.Cause(err, "create v2ray api server")
|
||||||
}
|
}
|
||||||
router.SetV2RayServer(v2rayServer)
|
router.SetV2RayServer(v2rayServer)
|
||||||
preServices["v2ray api"] = v2rayServer
|
preServices2["v2ray api"] = v2rayServer
|
||||||
}
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
router: router,
|
router: router,
|
||||||
@@ -171,7 +184,8 @@ func New(options Options) (*Box, error) {
|
|||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
logFactory: logFactory,
|
logFactory: logFactory,
|
||||||
logger: logFactory.Logger(),
|
logger: logFactory.Logger(),
|
||||||
preServices: preServices,
|
preServices1: preServices1,
|
||||||
|
preServices2: preServices2,
|
||||||
postServices: postServices,
|
postServices: postServices,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}, nil
|
}, nil
|
||||||
@@ -216,7 +230,16 @@ func (s *Box) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) preStart() error {
|
func (s *Box) preStart() error {
|
||||||
for serviceName, service := range s.preServices {
|
for serviceName, service := range s.preServices1 {
|
||||||
|
if preService, isPreService := service.(adapter.PreStarter); isPreService {
|
||||||
|
s.logger.Trace("pre-start ", serviceName)
|
||||||
|
err := preService.PreStart()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "pre-starting ", serviceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for serviceName, service := range s.preServices2 {
|
||||||
if preService, isPreService := service.(adapter.PreStarter); isPreService {
|
if preService, isPreService := service.(adapter.PreStarter); isPreService {
|
||||||
s.logger.Trace("pre-start ", serviceName)
|
s.logger.Trace("pre-start ", serviceName)
|
||||||
err := preService.PreStart()
|
err := preService.PreStart()
|
||||||
@@ -237,7 +260,14 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for serviceName, service := range s.preServices {
|
for serviceName, service := range s.preServices1 {
|
||||||
|
s.logger.Trace("starting ", serviceName)
|
||||||
|
err = service.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "start ", serviceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for serviceName, service := range s.preServices2 {
|
||||||
s.logger.Trace("starting ", serviceName)
|
s.logger.Trace("starting ", serviceName)
|
||||||
err = service.Start()
|
err = service.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -312,7 +342,13 @@ func (s *Box) Close() error {
|
|||||||
return E.Cause(err, "close router")
|
return E.Cause(err, "close router")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for serviceName, service := range s.preServices {
|
for serviceName, service := range s.preServices1 {
|
||||||
|
s.logger.Trace("closing ", serviceName)
|
||||||
|
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close ", serviceName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for serviceName, service := range s.preServices2 {
|
||||||
s.logger.Trace("closing ", serviceName)
|
s.logger.Trace("closing ", serviceName)
|
||||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", serviceName)
|
return E.Cause(err, "close ", serviceName)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
build_shared.FindSDK()
|
build_shared.FindSDK()
|
||||||
|
|
||||||
if os.Getenv("build.Default.GOPATH") == "" {
|
if os.Getenv("GOPATH") == "" {
|
||||||
os.Setenv("GOPATH", build.Default.GOPATH)
|
os.Setenv("GOPATH", build.Default.GOPATH)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ func ReadTag() (string, error) {
|
|||||||
}
|
}
|
||||||
shortCommit, _ := shell.Exec("git", "rev-parse", "--short", "HEAD").ReadOutput()
|
shortCommit, _ := shell.Exec("git", "rev-parse", "--short", "HEAD").ReadOutput()
|
||||||
version := badversion.Parse(currentTagRev[1:])
|
version := badversion.Parse(currentTagRev[1:])
|
||||||
if version.PreReleaseIdentifier == "" {
|
|
||||||
version.Patch++
|
|
||||||
}
|
|
||||||
return version.String() + "-" + shortCommit, nil
|
return version.String() + "-" + shortCommit, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/common/json"
|
"github.com/sagernet/sing-box/common/json"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -69,41 +68,3 @@ func format() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatOne(configPath string) error {
|
|
||||||
configContent, err := os.ReadFile(configPath)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "read config")
|
|
||||||
}
|
|
||||||
var options option.Options
|
|
||||||
err = options.UnmarshalJSON(configContent)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "decode config")
|
|
||||||
}
|
|
||||||
buffer := new(bytes.Buffer)
|
|
||||||
encoder := json.NewEncoder(buffer)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
err = encoder.Encode(options)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "encode config")
|
|
||||||
}
|
|
||||||
if !commandFormatFlagWrite {
|
|
||||||
os.Stdout.WriteString(buffer.String() + "\n")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if bytes.Equal(configContent, buffer.Bytes()) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
output, err := os.Create(configPath)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "open output")
|
|
||||||
}
|
|
||||||
_, err = output.Write(buffer.Bytes())
|
|
||||||
output.Close()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "write output")
|
|
||||||
}
|
|
||||||
outputPath, _ := filepath.Abs(configPath)
|
|
||||||
os.Stderr.WriteString(outputPath + "\n")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
43
cmd/sing-box/cmd_geoip.go
Normal file
43
cmd/sing-box/cmd_geoip.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
"github.com/oschwald/maxminddb-golang"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
geoipReader *maxminddb.Reader
|
||||||
|
commandGeoIPFlagFile string
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandGeoip = &cobra.Command{
|
||||||
|
Use: "geoip",
|
||||||
|
Short: "GeoIP tools",
|
||||||
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := geoipPreRun()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandGeoip.PersistentFlags().StringVarP(&commandGeoIPFlagFile, "file", "f", "geoip.db", "geoip file")
|
||||||
|
mainCommand.AddCommand(commandGeoip)
|
||||||
|
}
|
||||||
|
|
||||||
|
func geoipPreRun() error {
|
||||||
|
reader, err := maxminddb.Open(commandGeoIPFlagFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if reader.Metadata.DatabaseType != "sing-geoip" {
|
||||||
|
reader.Close()
|
||||||
|
return E.New("incorrect database type, expected sing-geoip, got ", reader.Metadata.DatabaseType)
|
||||||
|
}
|
||||||
|
geoipReader = reader
|
||||||
|
return nil
|
||||||
|
}
|
||||||
98
cmd/sing-box/cmd_geoip_export.go
Normal file
98
cmd/sing-box/cmd_geoip_export.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
"github.com/oschwald/maxminddb-golang"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var flagGeoipExportOutput string
|
||||||
|
|
||||||
|
const flagGeoipExportDefaultOutput = "geoip-<country>.srs"
|
||||||
|
|
||||||
|
var commandGeoipExport = &cobra.Command{
|
||||||
|
Use: "export <country>",
|
||||||
|
Short: "Export geoip country as rule-set",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := geoipExport(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandGeoipExport.Flags().StringVarP(&flagGeoipExportOutput, "output", "o", flagGeoipExportDefaultOutput, "Output path")
|
||||||
|
commandGeoip.AddCommand(commandGeoipExport)
|
||||||
|
}
|
||||||
|
|
||||||
|
func geoipExport(countryCode string) error {
|
||||||
|
networks := geoipReader.Networks(maxminddb.SkipAliasedNetworks)
|
||||||
|
countryMap := make(map[string][]*net.IPNet)
|
||||||
|
var (
|
||||||
|
ipNet *net.IPNet
|
||||||
|
nextCountryCode string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for networks.Next() {
|
||||||
|
ipNet, err = networks.Network(&nextCountryCode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
countryMap[nextCountryCode] = append(countryMap[nextCountryCode], ipNet)
|
||||||
|
}
|
||||||
|
ipNets := countryMap[strings.ToLower(countryCode)]
|
||||||
|
if len(ipNets) == 0 {
|
||||||
|
return E.New("country code not found: ", countryCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
outputFile *os.File
|
||||||
|
outputWriter io.Writer
|
||||||
|
)
|
||||||
|
if flagGeoipExportOutput == "stdout" {
|
||||||
|
outputWriter = os.Stdout
|
||||||
|
} else if flagGeoipExportOutput == flagGeoipExportDefaultOutput {
|
||||||
|
outputFile, err = os.Create("geoip-" + countryCode + ".json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer outputFile.Close()
|
||||||
|
outputWriter = outputFile
|
||||||
|
} else {
|
||||||
|
outputFile, err = os.Create(flagGeoipExportOutput)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer outputFile.Close()
|
||||||
|
outputWriter = outputFile
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(outputWriter)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
var headlessRule option.DefaultHeadlessRule
|
||||||
|
headlessRule.IPCIDR = make([]string, 0, len(ipNets))
|
||||||
|
for _, cidr := range ipNets {
|
||||||
|
headlessRule.IPCIDR = append(headlessRule.IPCIDR, cidr.String())
|
||||||
|
}
|
||||||
|
var plainRuleSet option.PlainRuleSetCompat
|
||||||
|
plainRuleSet.Version = C.RuleSetVersion1
|
||||||
|
plainRuleSet.Options.Rules = []option.HeadlessRule{
|
||||||
|
{
|
||||||
|
Type: C.RuleTypeDefault,
|
||||||
|
DefaultOptions: headlessRule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return encoder.Encode(plainRuleSet)
|
||||||
|
}
|
||||||
31
cmd/sing-box/cmd_geoip_list.go
Normal file
31
cmd/sing-box/cmd_geoip_list.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandGeoipList = &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List geoip country codes",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := listGeoip()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandGeoip.AddCommand(commandGeoipList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listGeoip() error {
|
||||||
|
for _, code := range geoipReader.Metadata.Languages {
|
||||||
|
os.Stdout.WriteString(code + "\n")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
47
cmd/sing-box/cmd_geoip_lookup.go
Normal file
47
cmd/sing-box/cmd_geoip_lookup.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandGeoipLookup = &cobra.Command{
|
||||||
|
Use: "lookup <address>",
|
||||||
|
Short: "Lookup if an IP address is contained in the GeoIP database",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := geoipLookup(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandGeoip.AddCommand(commandGeoipLookup)
|
||||||
|
}
|
||||||
|
|
||||||
|
func geoipLookup(address string) error {
|
||||||
|
addr, err := netip.ParseAddr(address)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "parse address")
|
||||||
|
}
|
||||||
|
if !N.IsPublicAddr(addr) {
|
||||||
|
os.Stdout.WriteString("private\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var code string
|
||||||
|
_ = geoipReader.Lookup(addr.AsSlice(), &code)
|
||||||
|
if code != "" {
|
||||||
|
os.Stdout.WriteString(code + "\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
os.Stdout.WriteString("unknown\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
41
cmd/sing-box/cmd_geosite.go
Normal file
41
cmd/sing-box/cmd_geosite.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/common/geosite"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
commandGeoSiteFlagFile string
|
||||||
|
geositeReader *geosite.Reader
|
||||||
|
geositeCodeList []string
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandGeoSite = &cobra.Command{
|
||||||
|
Use: "geosite",
|
||||||
|
Short: "Geosite tools",
|
||||||
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := geositePreRun()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandGeoSite.PersistentFlags().StringVarP(&commandGeoSiteFlagFile, "file", "f", "geosite.db", "geosite file")
|
||||||
|
mainCommand.AddCommand(commandGeoSite)
|
||||||
|
}
|
||||||
|
|
||||||
|
func geositePreRun() error {
|
||||||
|
reader, codeList, err := geosite.Open(commandGeoSiteFlagFile)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open geosite file")
|
||||||
|
}
|
||||||
|
geositeReader = reader
|
||||||
|
geositeCodeList = codeList
|
||||||
|
return nil
|
||||||
|
}
|
||||||
81
cmd/sing-box/cmd_geosite_export.go
Normal file
81
cmd/sing-box/cmd_geosite_export.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/geosite"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandGeositeExportOutput string
|
||||||
|
|
||||||
|
const commandGeositeExportDefaultOutput = "geosite-<category>.json"
|
||||||
|
|
||||||
|
var commandGeositeExport = &cobra.Command{
|
||||||
|
Use: "export <category>",
|
||||||
|
Short: "Export geosite category as rule-set",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := geositeExport(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandGeositeExport.Flags().StringVarP(&commandGeositeExportOutput, "output", "o", commandGeositeExportDefaultOutput, "Output path")
|
||||||
|
commandGeoSite.AddCommand(commandGeositeExport)
|
||||||
|
}
|
||||||
|
|
||||||
|
func geositeExport(category string) error {
|
||||||
|
sourceSet, err := geositeReader.Read(category)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
outputFile *os.File
|
||||||
|
outputWriter io.Writer
|
||||||
|
)
|
||||||
|
if commandGeositeExportOutput == "stdout" {
|
||||||
|
outputWriter = os.Stdout
|
||||||
|
} else if commandGeositeExportOutput == commandGeositeExportDefaultOutput {
|
||||||
|
outputFile, err = os.Create("geosite-" + category + ".json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer outputFile.Close()
|
||||||
|
outputWriter = outputFile
|
||||||
|
} else {
|
||||||
|
outputFile, err = os.Create(commandGeositeExportOutput)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer outputFile.Close()
|
||||||
|
outputWriter = outputFile
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(outputWriter)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
var headlessRule option.DefaultHeadlessRule
|
||||||
|
defaultRule := geosite.Compile(sourceSet)
|
||||||
|
headlessRule.Domain = defaultRule.Domain
|
||||||
|
headlessRule.DomainSuffix = defaultRule.DomainSuffix
|
||||||
|
headlessRule.DomainKeyword = defaultRule.DomainKeyword
|
||||||
|
headlessRule.DomainRegex = defaultRule.DomainRegex
|
||||||
|
var plainRuleSet option.PlainRuleSetCompat
|
||||||
|
plainRuleSet.Version = C.RuleSetVersion1
|
||||||
|
plainRuleSet.Options.Rules = []option.HeadlessRule{
|
||||||
|
{
|
||||||
|
Type: C.RuleTypeDefault,
|
||||||
|
DefaultOptions: headlessRule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return encoder.Encode(plainRuleSet)
|
||||||
|
}
|
||||||
50
cmd/sing-box/cmd_geosite_list.go
Normal file
50
cmd/sing-box/cmd_geosite_list.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandGeositeList = &cobra.Command{
|
||||||
|
Use: "list <category>",
|
||||||
|
Short: "List geosite categories",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := geositeList()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandGeoSite.AddCommand(commandGeositeList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func geositeList() error {
|
||||||
|
var geositeEntry []struct {
|
||||||
|
category string
|
||||||
|
items int
|
||||||
|
}
|
||||||
|
for _, category := range geositeCodeList {
|
||||||
|
sourceSet, err := geositeReader.Read(category)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
geositeEntry = append(geositeEntry, struct {
|
||||||
|
category string
|
||||||
|
items int
|
||||||
|
}{category, len(sourceSet)})
|
||||||
|
}
|
||||||
|
sort.SliceStable(geositeEntry, func(i, j int) bool {
|
||||||
|
return geositeEntry[i].items < geositeEntry[j].items
|
||||||
|
})
|
||||||
|
for _, entry := range geositeEntry {
|
||||||
|
os.Stdout.WriteString(F.ToString(entry.category, " (", entry.items, ")\n"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
97
cmd/sing-box/cmd_geosite_lookup.go
Normal file
97
cmd/sing-box/cmd_geosite_lookup.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandGeositeLookup = &cobra.Command{
|
||||||
|
Use: "lookup [category] <domain>",
|
||||||
|
Short: "Check if a domain is in the geosite",
|
||||||
|
Args: cobra.RangeArgs(1, 2),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
var (
|
||||||
|
source string
|
||||||
|
target string
|
||||||
|
)
|
||||||
|
switch len(args) {
|
||||||
|
case 1:
|
||||||
|
target = args[0]
|
||||||
|
case 2:
|
||||||
|
source = args[0]
|
||||||
|
target = args[1]
|
||||||
|
}
|
||||||
|
err := geositeLookup(source, target)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandGeoSite.AddCommand(commandGeositeLookup)
|
||||||
|
}
|
||||||
|
|
||||||
|
func geositeLookup(source string, target string) error {
|
||||||
|
var sourceMatcherList []struct {
|
||||||
|
code string
|
||||||
|
matcher *searchGeositeMatcher
|
||||||
|
}
|
||||||
|
if source != "" {
|
||||||
|
sourceSet, err := geositeReader.Read(source)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sourceMatcher, err := newSearchGeositeMatcher(sourceSet)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "compile code: "+source)
|
||||||
|
}
|
||||||
|
sourceMatcherList = []struct {
|
||||||
|
code string
|
||||||
|
matcher *searchGeositeMatcher
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
code: source,
|
||||||
|
matcher: sourceMatcher,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
for _, code := range geositeCodeList {
|
||||||
|
sourceSet, err := geositeReader.Read(code)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sourceMatcher, err := newSearchGeositeMatcher(sourceSet)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "compile code: "+code)
|
||||||
|
}
|
||||||
|
sourceMatcherList = append(sourceMatcherList, struct {
|
||||||
|
code string
|
||||||
|
matcher *searchGeositeMatcher
|
||||||
|
}{
|
||||||
|
code: code,
|
||||||
|
matcher: sourceMatcher,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.SliceStable(sourceMatcherList, func(i, j int) bool {
|
||||||
|
return sourceMatcherList[i].code < sourceMatcherList[j].code
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, matcherItem := range sourceMatcherList {
|
||||||
|
if matchRule := matcherItem.matcher.Match(target); matchRule != "" {
|
||||||
|
os.Stdout.WriteString("Match code (")
|
||||||
|
os.Stdout.WriteString(matcherItem.code)
|
||||||
|
os.Stdout.WriteString(") ")
|
||||||
|
os.Stdout.WriteString(matchRule)
|
||||||
|
os.Stdout.WriteString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
56
cmd/sing-box/cmd_geosite_matcher.go
Normal file
56
cmd/sing-box/cmd_geosite_matcher.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/geosite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type searchGeositeMatcher struct {
|
||||||
|
domainMap map[string]bool
|
||||||
|
suffixList []string
|
||||||
|
keywordList []string
|
||||||
|
regexList []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSearchGeositeMatcher(items []geosite.Item) (*searchGeositeMatcher, error) {
|
||||||
|
options := geosite.Compile(items)
|
||||||
|
domainMap := make(map[string]bool)
|
||||||
|
for _, domain := range options.Domain {
|
||||||
|
domainMap[domain] = true
|
||||||
|
}
|
||||||
|
rule := &searchGeositeMatcher{
|
||||||
|
domainMap: domainMap,
|
||||||
|
suffixList: options.DomainSuffix,
|
||||||
|
keywordList: options.DomainKeyword,
|
||||||
|
regexList: options.DomainRegex,
|
||||||
|
}
|
||||||
|
return rule, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *searchGeositeMatcher) Match(domain string) string {
|
||||||
|
if r.domainMap[domain] {
|
||||||
|
return "domain=" + domain
|
||||||
|
}
|
||||||
|
for _, suffix := range r.suffixList {
|
||||||
|
if strings.HasSuffix(domain, suffix) {
|
||||||
|
return "domain_suffix=" + suffix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, keyword := range r.keywordList {
|
||||||
|
if strings.Contains(domain, keyword) {
|
||||||
|
return "domain_keyword=" + keyword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, regexStr := range r.regexList {
|
||||||
|
regex, err := regexp.Compile(regexStr)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if regex.MatchString(domain) {
|
||||||
|
return "domain_regex=" + regexStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var commandMerge = &cobra.Command{
|
var commandMerge = &cobra.Command{
|
||||||
Use: "merge [output]",
|
Use: "merge <output>",
|
||||||
Short: "Merge configurations",
|
Short: "Merge configurations",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
err := merge(args[0])
|
err := merge(args[0])
|
||||||
|
|||||||
14
cmd/sing-box/cmd_rule_set.go
Normal file
14
cmd/sing-box/cmd_rule_set.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandRuleSet = &cobra.Command{
|
||||||
|
Use: "rule-set",
|
||||||
|
Short: "Manage rule sets",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
mainCommand.AddCommand(commandRuleSet)
|
||||||
|
}
|
||||||
80
cmd/sing-box/cmd_rule_set_compile.go
Normal file
80
cmd/sing-box/cmd_rule_set_compile.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var flagRuleSetCompileOutput string
|
||||||
|
|
||||||
|
const flagRuleSetCompileDefaultOutput = "<file_name>.srs"
|
||||||
|
|
||||||
|
var commandRuleSetCompile = &cobra.Command{
|
||||||
|
Use: "compile [source-path]",
|
||||||
|
Short: "Compile rule-set json to binary",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := compileRuleSet(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandRuleSet.AddCommand(commandRuleSetCompile)
|
||||||
|
commandRuleSetCompile.Flags().StringVarP(&flagRuleSetCompileOutput, "output", "o", flagRuleSetCompileDefaultOutput, "Output file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func compileRuleSet(sourcePath string) error {
|
||||||
|
var (
|
||||||
|
reader io.Reader
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if sourcePath == "stdin" {
|
||||||
|
reader = os.Stdin
|
||||||
|
} else {
|
||||||
|
reader, err = os.Open(sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decoder := json.NewDecoder(json.NewCommentFilter(reader))
|
||||||
|
decoder.DisallowUnknownFields()
|
||||||
|
var plainRuleSet option.PlainRuleSetCompat
|
||||||
|
err = decoder.Decode(&plainRuleSet)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ruleSet := plainRuleSet.Upgrade()
|
||||||
|
var outputPath string
|
||||||
|
if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput {
|
||||||
|
if strings.HasSuffix(sourcePath, ".json") {
|
||||||
|
outputPath = sourcePath[:len(sourcePath)-5] + ".srs"
|
||||||
|
} else {
|
||||||
|
outputPath = sourcePath + ".srs"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
outputPath = flagRuleSetCompileOutput
|
||||||
|
}
|
||||||
|
outputFile, err := os.Create(outputPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = srs.Write(outputFile, ruleSet)
|
||||||
|
if err != nil {
|
||||||
|
outputFile.Close()
|
||||||
|
os.Remove(outputPath)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outputFile.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
87
cmd/sing-box/cmd_rule_set_format.go
Normal file
87
cmd/sing-box/cmd_rule_set_format.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandRuleSetFormatFlagWrite bool
|
||||||
|
|
||||||
|
var commandRuleSetFormat = &cobra.Command{
|
||||||
|
Use: "format <source-path>",
|
||||||
|
Short: "Format rule-set json",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := formatRuleSet(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandRuleSetFormat.Flags().BoolVarP(&commandRuleSetFormatFlagWrite, "write", "w", false, "write result to (source) file instead of stdout")
|
||||||
|
commandRuleSet.AddCommand(commandRuleSetFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatRuleSet(sourcePath string) error {
|
||||||
|
var (
|
||||||
|
reader io.Reader
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if sourcePath == "stdin" {
|
||||||
|
reader = os.Stdin
|
||||||
|
} else {
|
||||||
|
reader, err = os.Open(sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content, err := io.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
decoder := json.NewDecoder(json.NewCommentFilter(bytes.NewReader(content)))
|
||||||
|
decoder.DisallowUnknownFields()
|
||||||
|
var plainRuleSet option.PlainRuleSetCompat
|
||||||
|
err = decoder.Decode(&plainRuleSet)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ruleSet := plainRuleSet.Upgrade()
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
encoder := json.NewEncoder(buffer)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
err = encoder.Encode(ruleSet)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "encode config")
|
||||||
|
}
|
||||||
|
outputPath, _ := filepath.Abs(sourcePath)
|
||||||
|
if !commandRuleSetFormatFlagWrite || sourcePath == "stdin" {
|
||||||
|
os.Stdout.WriteString(buffer.String() + "\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if bytes.Equal(content, buffer.Bytes()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
output, err := os.Create(sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open output")
|
||||||
|
}
|
||||||
|
_, err = output.Write(buffer.Bytes())
|
||||||
|
output.Close()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "write output")
|
||||||
|
}
|
||||||
|
os.Stderr.WriteString(outputPath + "\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -38,11 +38,7 @@ func createPreStartedClient() (*box.Box, error) {
|
|||||||
|
|
||||||
func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) {
|
func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) {
|
||||||
if outboundTag == "" {
|
if outboundTag == "" {
|
||||||
outbound := instance.Router().DefaultOutbound(N.NetworkName(network))
|
return instance.Router().DefaultOutbound(N.NetworkName(network))
|
||||||
if outbound == nil {
|
|
||||||
return nil, E.New("missing default outbound")
|
|
||||||
}
|
|
||||||
return outbound, nil
|
|
||||||
} else {
|
} else {
|
||||||
outbound, loaded := instance.Router().Outbound(outboundTag)
|
outbound, loaded := instance.Router().Outbound(outboundTag)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
var commandConnectFlagNetwork string
|
var commandConnectFlagNetwork string
|
||||||
|
|
||||||
var commandConnect = &cobra.Command{
|
var commandConnect = &cobra.Command{
|
||||||
Use: "connect [address]",
|
Use: "connect <address>",
|
||||||
Short: "Connect to an address",
|
Short: "Connect to an address",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing/common/bufio/deadline"
|
||||||
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"
|
||||||
@@ -44,7 +45,14 @@ func (d *DetourDialer) DialContext(ctx context.Context, network string, destinat
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return dialer.DialContext(ctx, network, destination)
|
conn, err := dialer.DialContext(ctx, network, destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if deadline.NeedAdditionalReadDeadline(conn) {
|
||||||
|
conn = deadline.NewConn(conn)
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DetourDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *DetourDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
|||||||
@@ -18,11 +18,19 @@ func NewRouter(router adapter.Router) N.Dialer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *RouterDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (d *RouterDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
return d.router.DefaultOutbound(network).DialContext(ctx, network, destination)
|
dialer, err := d.router.DefaultOutbound(network)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dialer.DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *RouterDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *RouterDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
return d.router.DefaultOutbound(N.NetworkUDP).ListenPacket(ctx, destination)
|
dialer, err := d.router.DefaultOutbound(N.NetworkUDP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dialer.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *RouterDialer) Upstream() any {
|
func (d *RouterDialer) Upstream() any {
|
||||||
|
|||||||
@@ -1,21 +1,42 @@
|
|||||||
package mux
|
package mux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-mux"
|
"github.com/sagernet/sing-mux"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewClientWithOptions(dialer N.Dialer, options option.MultiplexOptions) (*Client, error) {
|
type Client = mux.Client
|
||||||
|
|
||||||
|
func NewClientWithOptions(dialer N.Dialer, logger logger.Logger, options option.OutboundMultiplexOptions) (*Client, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
var brutalOptions mux.BrutalOptions
|
||||||
|
if options.Brutal != nil && options.Brutal.Enabled {
|
||||||
|
brutalOptions = mux.BrutalOptions{
|
||||||
|
Enabled: true,
|
||||||
|
SendBPS: uint64(options.Brutal.UpMbps * C.MbpsToBps),
|
||||||
|
ReceiveBPS: uint64(options.Brutal.DownMbps * C.MbpsToBps),
|
||||||
|
}
|
||||||
|
if brutalOptions.SendBPS < mux.BrutalMinSpeedBPS {
|
||||||
|
return nil, E.New("brutal: invalid upload speed")
|
||||||
|
}
|
||||||
|
if brutalOptions.ReceiveBPS < mux.BrutalMinSpeedBPS {
|
||||||
|
return nil, E.New("brutal: invalid download speed")
|
||||||
|
}
|
||||||
|
}
|
||||||
return mux.NewClient(mux.Options{
|
return mux.NewClient(mux.Options{
|
||||||
Dialer: dialer,
|
Dialer: dialer,
|
||||||
|
Logger: logger,
|
||||||
Protocol: options.Protocol,
|
Protocol: options.Protocol,
|
||||||
MaxConnections: options.MaxConnections,
|
MaxConnections: options.MaxConnections,
|
||||||
MinStreams: options.MinStreams,
|
MinStreams: options.MinStreams,
|
||||||
MaxStreams: options.MaxStreams,
|
MaxStreams: options.MaxStreams,
|
||||||
Padding: options.Padding,
|
Padding: options.Padding,
|
||||||
|
Brutal: brutalOptions,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
Client = mux.Client
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
Destination = mux.Destination
|
|
||||||
HandleConnection = mux.HandleConnection
|
|
||||||
)
|
|
||||||
65
common/mux/router.go
Normal file
65
common/mux/router.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-mux"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Router struct {
|
||||||
|
router adapter.ConnectionRouter
|
||||||
|
service *mux.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouter, error) {
|
||||||
|
if !options.Enabled {
|
||||||
|
return router, nil
|
||||||
|
}
|
||||||
|
var brutalOptions mux.BrutalOptions
|
||||||
|
if options.Brutal != nil && options.Brutal.Enabled {
|
||||||
|
brutalOptions = mux.BrutalOptions{
|
||||||
|
Enabled: true,
|
||||||
|
SendBPS: uint64(options.Brutal.UpMbps * C.MbpsToBps),
|
||||||
|
ReceiveBPS: uint64(options.Brutal.DownMbps * C.MbpsToBps),
|
||||||
|
}
|
||||||
|
if brutalOptions.SendBPS < mux.BrutalMinSpeedBPS {
|
||||||
|
return nil, E.New("brutal: invalid upload speed")
|
||||||
|
}
|
||||||
|
if brutalOptions.ReceiveBPS < mux.BrutalMinSpeedBPS {
|
||||||
|
return nil, E.New("brutal: invalid download speed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
service, err := mux.NewService(mux.ServiceOptions{
|
||||||
|
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
|
||||||
|
return log.ContextWithNewID(ctx)
|
||||||
|
},
|
||||||
|
Logger: logger,
|
||||||
|
Handler: adapter.NewRouteContextHandler(router, logger),
|
||||||
|
Padding: options.Padding,
|
||||||
|
Brutal: brutalOptions,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Router{router, service}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
|
if metadata.Destination == mux.Destination {
|
||||||
|
return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata))
|
||||||
|
} else {
|
||||||
|
return r.router.RouteConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
|
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
32
common/mux/v2ray_legacy.go
Normal file
32
common/mux/v2ray_legacy.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
vmess "github.com/sagernet/sing-vmess"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type V2RayLegacyRouter struct {
|
||||||
|
router adapter.ConnectionRouter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewV2RayLegacyRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) adapter.ConnectionRouter {
|
||||||
|
return &V2RayLegacyRouter{router, logger}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *V2RayLegacyRouter) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
|
if metadata.Destination.Fqdn == vmess.MuxDestination.Fqdn {
|
||||||
|
r.logger.InfoContext(ctx, "inbound legacy multiplex connection")
|
||||||
|
return vmess.HandleMuxConnection(ctx, conn, adapter.NewRouteHandler(metadata, r.router, r.logger))
|
||||||
|
}
|
||||||
|
return r.router.RouteConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *V2RayLegacyRouter) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
|
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
@@ -182,11 +182,52 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
switch frameType {
|
switch frameType {
|
||||||
case 0x0:
|
case 0x00: // PADDING
|
||||||
continue
|
continue
|
||||||
case 0x1:
|
case 0x01: // PING
|
||||||
continue
|
continue
|
||||||
case 0x6:
|
case 0x02, 0x03: // ACK
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // Largest Acknowledged
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // ACK Delay
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ackRangeCount, err := qtls.ReadUvarint(decryptedReader) // ACK Range Count
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // First ACK Range
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := 0; i < int(ackRangeCount); i++ {
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // Gap
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // ACK Range Length
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if frameType == 0x03 {
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // ECT0 Count
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // ECT1 Count
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // ECN-CE Count
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 0x06: // CRYPTO
|
||||||
var offset uint64
|
var offset uint64
|
||||||
offset, err = qtls.ReadUvarint(decryptedReader)
|
offset, err = qtls.ReadUvarint(decryptedReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -208,8 +249,26 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
case 0x1c: // CONNECTION_CLOSE
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // Error Code
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = qtls.ReadUvarint(decryptedReader) // Frame Type
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var length uint64
|
||||||
|
length, err = qtls.ReadUvarint(decryptedReader) // Reason Phrase Length
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = decryptedReader.Seek(int64(length), io.SeekCurrent) // Reason Phrase
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// ignore unknown frame type
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tlsHdr := make([]byte, 5)
|
tlsHdr := make([]byte, 5)
|
||||||
|
|||||||
485
common/srs/binary.go
Normal file
485
common/srs/binary.go
Normal file
@@ -0,0 +1,485 @@
|
|||||||
|
package srs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/zlib"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/domain"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
|
||||||
|
"go4.org/netipx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var MagicBytes = [3]byte{0x53, 0x52, 0x53} // SRS
|
||||||
|
|
||||||
|
const (
|
||||||
|
ruleItemQueryType uint8 = iota
|
||||||
|
ruleItemNetwork
|
||||||
|
ruleItemDomain
|
||||||
|
ruleItemDomainKeyword
|
||||||
|
ruleItemDomainRegex
|
||||||
|
ruleItemSourceIPCIDR
|
||||||
|
ruleItemIPCIDR
|
||||||
|
ruleItemSourcePort
|
||||||
|
ruleItemSourcePortRange
|
||||||
|
ruleItemPort
|
||||||
|
ruleItemPortRange
|
||||||
|
ruleItemProcessName
|
||||||
|
ruleItemProcessPath
|
||||||
|
ruleItemPackageName
|
||||||
|
ruleItemWIFISSID
|
||||||
|
ruleItemWIFIBSSID
|
||||||
|
ruleItemFinal uint8 = 0xFF
|
||||||
|
)
|
||||||
|
|
||||||
|
func Read(reader io.Reader, recovery bool) (ruleSet option.PlainRuleSet, err error) {
|
||||||
|
var magicBytes [3]byte
|
||||||
|
_, err = io.ReadFull(reader, magicBytes[:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if magicBytes != MagicBytes {
|
||||||
|
err = E.New("invalid sing-box rule set file")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var version uint8
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &version)
|
||||||
|
if err != nil {
|
||||||
|
return ruleSet, err
|
||||||
|
}
|
||||||
|
if version != 1 {
|
||||||
|
return ruleSet, E.New("unsupported version: ", version)
|
||||||
|
}
|
||||||
|
zReader, err := zlib.NewReader(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
length, err := rw.ReadUVariant(zReader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ruleSet.Rules = make([]option.HeadlessRule, length)
|
||||||
|
for i := uint64(0); i < length; i++ {
|
||||||
|
ruleSet.Rules[i], err = readRule(zReader, recovery)
|
||||||
|
if err != nil {
|
||||||
|
err = E.Cause(err, "read rule[", i, "]")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Write(writer io.Writer, ruleSet option.PlainRuleSet) error {
|
||||||
|
_, err := writer.Write(MagicBytes[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, uint8(1))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
zWriter, err := zlib.NewWriterLevel(writer, zlib.BestCompression)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = rw.WriteUVariant(zWriter, uint64(len(ruleSet.Rules)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, rule := range ruleSet.Rules {
|
||||||
|
err = writeRule(zWriter, rule)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return zWriter.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func readRule(reader io.Reader, recovery bool) (rule option.HeadlessRule, err error) {
|
||||||
|
var ruleType uint8
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &ruleType)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch ruleType {
|
||||||
|
case 0:
|
||||||
|
rule.DefaultOptions, err = readDefaultRule(reader, recovery)
|
||||||
|
case 1:
|
||||||
|
rule.LogicalOptions, err = readLogicalRule(reader, recovery)
|
||||||
|
default:
|
||||||
|
err = E.New("unknown rule type: ", ruleType)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeRule(writer io.Writer, rule option.HeadlessRule) error {
|
||||||
|
switch rule.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
return writeDefaultRule(writer, rule.DefaultOptions)
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
return writeLogicalRule(writer, rule.LogicalOptions)
|
||||||
|
default:
|
||||||
|
panic("unknown rule type: " + rule.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadlessRule, err error) {
|
||||||
|
var lastItemType uint8
|
||||||
|
for {
|
||||||
|
var itemType uint8
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &itemType)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch itemType {
|
||||||
|
case ruleItemQueryType:
|
||||||
|
var rawQueryType []uint16
|
||||||
|
rawQueryType, err = readRuleItemUint16(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rule.QueryType = common.Map(rawQueryType, func(it uint16) option.DNSQueryType {
|
||||||
|
return option.DNSQueryType(it)
|
||||||
|
})
|
||||||
|
case ruleItemNetwork:
|
||||||
|
rule.Network, err = readRuleItemString(reader)
|
||||||
|
case ruleItemDomain:
|
||||||
|
var matcher *domain.Matcher
|
||||||
|
matcher, err = domain.ReadMatcher(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rule.DomainMatcher = matcher
|
||||||
|
case ruleItemDomainKeyword:
|
||||||
|
rule.DomainKeyword, err = readRuleItemString(reader)
|
||||||
|
case ruleItemDomainRegex:
|
||||||
|
rule.DomainRegex, err = readRuleItemString(reader)
|
||||||
|
case ruleItemSourceIPCIDR:
|
||||||
|
rule.SourceIPSet, err = readIPSet(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if recovery {
|
||||||
|
rule.SourceIPCIDR = common.Map(rule.SourceIPSet.Prefixes(), netip.Prefix.String)
|
||||||
|
}
|
||||||
|
case ruleItemIPCIDR:
|
||||||
|
rule.IPSet, err = readIPSet(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if recovery {
|
||||||
|
rule.IPCIDR = common.Map(rule.IPSet.Prefixes(), netip.Prefix.String)
|
||||||
|
}
|
||||||
|
case ruleItemSourcePort:
|
||||||
|
rule.SourcePort, err = readRuleItemUint16(reader)
|
||||||
|
case ruleItemSourcePortRange:
|
||||||
|
rule.SourcePortRange, err = readRuleItemString(reader)
|
||||||
|
case ruleItemPort:
|
||||||
|
rule.Port, err = readRuleItemUint16(reader)
|
||||||
|
case ruleItemPortRange:
|
||||||
|
rule.PortRange, err = readRuleItemString(reader)
|
||||||
|
case ruleItemProcessName:
|
||||||
|
rule.ProcessName, err = readRuleItemString(reader)
|
||||||
|
case ruleItemProcessPath:
|
||||||
|
rule.ProcessPath, err = readRuleItemString(reader)
|
||||||
|
case ruleItemPackageName:
|
||||||
|
rule.PackageName, err = readRuleItemString(reader)
|
||||||
|
case ruleItemWIFISSID:
|
||||||
|
rule.WIFISSID, err = readRuleItemString(reader)
|
||||||
|
case ruleItemWIFIBSSID:
|
||||||
|
rule.WIFIBSSID, err = readRuleItemString(reader)
|
||||||
|
case ruleItemFinal:
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &rule.Invert)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
err = E.New("unknown rule item type: ", itemType, ", last type: ", lastItemType)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastItemType = itemType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeDefaultRule(writer io.Writer, rule option.DefaultHeadlessRule) error {
|
||||||
|
err := binary.Write(writer, binary.BigEndian, uint8(0))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(rule.QueryType) > 0 {
|
||||||
|
err = writeRuleItemUint16(writer, ruleItemQueryType, common.Map(rule.QueryType, func(it option.DNSQueryType) uint16 {
|
||||||
|
return uint16(it)
|
||||||
|
}))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rule.Network) > 0 {
|
||||||
|
err = writeRuleItemString(writer, ruleItemNetwork, rule.Network)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rule.Domain) > 0 || len(rule.DomainSuffix) > 0 {
|
||||||
|
err = binary.Write(writer, binary.BigEndian, ruleItemDomain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = domain.NewMatcher(rule.Domain, rule.DomainSuffix).Write(writer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rule.DomainKeyword) > 0 {
|
||||||
|
err = writeRuleItemString(writer, ruleItemDomainKeyword, rule.DomainKeyword)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rule.DomainRegex) > 0 {
|
||||||
|
err = writeRuleItemString(writer, ruleItemDomainRegex, rule.DomainRegex)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rule.SourceIPCIDR) > 0 {
|
||||||
|
err = writeRuleItemCIDR(writer, ruleItemSourceIPCIDR, rule.SourceIPCIDR)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "source_ipcidr")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rule.IPCIDR) > 0 {
|
||||||
|
err = writeRuleItemCIDR(writer, ruleItemIPCIDR, rule.IPCIDR)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "ipcidr")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rule.SourcePort) > 0 {
|
||||||
|
err = writeRuleItemUint16(writer, ruleItemSourcePort, rule.SourcePort)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rule.SourcePortRange) > 0 {
|
||||||
|
err = writeRuleItemString(writer, ruleItemSourcePortRange, rule.SourcePortRange)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rule.Port) > 0 {
|
||||||
|
err = writeRuleItemUint16(writer, ruleItemPort, rule.Port)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rule.PortRange) > 0 {
|
||||||
|
err = writeRuleItemString(writer, ruleItemPortRange, rule.PortRange)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rule.ProcessName) > 0 {
|
||||||
|
err = writeRuleItemString(writer, ruleItemProcessName, rule.ProcessName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rule.ProcessPath) > 0 {
|
||||||
|
err = writeRuleItemString(writer, ruleItemProcessPath, rule.ProcessPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rule.PackageName) > 0 {
|
||||||
|
err = writeRuleItemString(writer, ruleItemPackageName, rule.PackageName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rule.WIFISSID) > 0 {
|
||||||
|
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rule.WIFIBSSID) > 0 {
|
||||||
|
err = writeRuleItemString(writer, ruleItemWIFIBSSID, rule.WIFIBSSID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, ruleItemFinal)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, rule.Invert)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readRuleItemString(reader io.Reader) ([]string, error) {
|
||||||
|
length, err := rw.ReadUVariant(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
value := make([]string, length)
|
||||||
|
for i := uint64(0); i < length; i++ {
|
||||||
|
value[i], err = rw.ReadVString(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeRuleItemString(writer io.Writer, itemType uint8, value []string) error {
|
||||||
|
err := binary.Write(writer, binary.BigEndian, itemType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = rw.WriteUVariant(writer, uint64(len(value)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, item := range value {
|
||||||
|
err = rw.WriteVString(writer, item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readRuleItemUint16(reader io.Reader) ([]uint16, error) {
|
||||||
|
length, err := rw.ReadUVariant(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
value := make([]uint16, length)
|
||||||
|
for i := uint64(0); i < length; i++ {
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &value[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeRuleItemUint16(writer io.Writer, itemType uint8, value []uint16) error {
|
||||||
|
err := binary.Write(writer, binary.BigEndian, itemType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = rw.WriteUVariant(writer, uint64(len(value)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, item := range value {
|
||||||
|
err = binary.Write(writer, binary.BigEndian, item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeRuleItemCIDR(writer io.Writer, itemType uint8, value []string) error {
|
||||||
|
var builder netipx.IPSetBuilder
|
||||||
|
for i, prefixString := range value {
|
||||||
|
prefix, err := netip.ParsePrefix(prefixString)
|
||||||
|
if err == nil {
|
||||||
|
builder.AddPrefix(prefix)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addr, addrErr := netip.ParseAddr(prefixString)
|
||||||
|
if addrErr == nil {
|
||||||
|
builder.Add(addr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return E.Cause(err, "parse [", i, "]")
|
||||||
|
}
|
||||||
|
ipSet, err := builder.IPSet()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, itemType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return writeIPSet(writer, ipSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readLogicalRule(reader io.Reader, recovery bool) (logicalRule option.LogicalHeadlessRule, err error) {
|
||||||
|
var mode uint8
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &mode)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch mode {
|
||||||
|
case 0:
|
||||||
|
logicalRule.Mode = C.LogicalTypeAnd
|
||||||
|
case 1:
|
||||||
|
logicalRule.Mode = C.LogicalTypeOr
|
||||||
|
default:
|
||||||
|
err = E.New("unknown logical mode: ", mode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
length, err := rw.ReadUVariant(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logicalRule.Rules = make([]option.HeadlessRule, length)
|
||||||
|
for i := uint64(0); i < length; i++ {
|
||||||
|
logicalRule.Rules[i], err = readRule(reader, recovery)
|
||||||
|
if err != nil {
|
||||||
|
err = E.Cause(err, "read logical rule [", i, "]")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &logicalRule.Invert)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeLogicalRule(writer io.Writer, logicalRule option.LogicalHeadlessRule) error {
|
||||||
|
err := binary.Write(writer, binary.BigEndian, uint8(1))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch logicalRule.Mode {
|
||||||
|
case C.LogicalTypeAnd:
|
||||||
|
err = binary.Write(writer, binary.BigEndian, uint8(0))
|
||||||
|
case C.LogicalTypeOr:
|
||||||
|
err = binary.Write(writer, binary.BigEndian, uint8(1))
|
||||||
|
default:
|
||||||
|
panic("unknown logical mode: " + logicalRule.Mode)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = rw.WriteUVariant(writer, uint64(len(logicalRule.Rules)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, rule := range logicalRule.Rules {
|
||||||
|
err = writeRule(writer, rule)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, logicalRule.Invert)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
116
common/srs/ip_set.go
Normal file
116
common/srs/ip_set.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package srs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net/netip"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
|
||||||
|
"go4.org/netipx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type myIPSet struct {
|
||||||
|
rr []myIPRange
|
||||||
|
}
|
||||||
|
|
||||||
|
type myIPRange struct {
|
||||||
|
from netip.Addr
|
||||||
|
to netip.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func readIPSet(reader io.Reader) (*netipx.IPSet, error) {
|
||||||
|
var version uint8
|
||||||
|
err := binary.Read(reader, binary.BigEndian, &version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var length uint64
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mySet := &myIPSet{
|
||||||
|
rr: make([]myIPRange, length),
|
||||||
|
}
|
||||||
|
for i := uint64(0); i < length; i++ {
|
||||||
|
var (
|
||||||
|
fromLen uint64
|
||||||
|
toLen uint64
|
||||||
|
fromAddr netip.Addr
|
||||||
|
toAddr netip.Addr
|
||||||
|
)
|
||||||
|
fromLen, err = rw.ReadUVariant(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fromBytes := make([]byte, fromLen)
|
||||||
|
_, err = io.ReadFull(reader, fromBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = fromAddr.UnmarshalBinary(fromBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
toLen, err = rw.ReadUVariant(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
toBytes := make([]byte, toLen)
|
||||||
|
_, err = io.ReadFull(reader, toBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = toAddr.UnmarshalBinary(toBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mySet.rr[i] = myIPRange{fromAddr, toAddr}
|
||||||
|
}
|
||||||
|
return (*netipx.IPSet)(unsafe.Pointer(mySet)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeIPSet(writer io.Writer, set *netipx.IPSet) error {
|
||||||
|
err := binary.Write(writer, binary.BigEndian, uint8(1))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mySet := (*myIPSet)(unsafe.Pointer(set))
|
||||||
|
err = binary.Write(writer, binary.BigEndian, uint64(len(mySet.rr)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, rr := range mySet.rr {
|
||||||
|
var (
|
||||||
|
fromBinary []byte
|
||||||
|
toBinary []byte
|
||||||
|
)
|
||||||
|
fromBinary, err = rr.from.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = rw.WriteUVariant(writer, uint64(len(fromBinary)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write(fromBinary)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
toBinary, err = rr.to.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = rw.WriteUVariant(writer, uint64(len(toBinary)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write(toBinary)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
53
common/uot/router.go
Normal file
53
common/uot/router.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package uot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/uot"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.ConnectionRouter = (*Router)(nil)
|
||||||
|
|
||||||
|
type Router struct {
|
||||||
|
router adapter.ConnectionRouter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) *Router {
|
||||||
|
return &Router{router, logger}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
|
switch metadata.Destination.Fqdn {
|
||||||
|
case uot.MagicAddress:
|
||||||
|
request, err := uot.ReadRequest(conn)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "read UoT request")
|
||||||
|
}
|
||||||
|
if request.IsConnect {
|
||||||
|
r.logger.InfoContext(ctx, "inbound UoT connect connection to ", request.Destination)
|
||||||
|
} else {
|
||||||
|
r.logger.InfoContext(ctx, "inbound UoT connection to ", request.Destination)
|
||||||
|
}
|
||||||
|
metadata.Domain = metadata.Destination.Fqdn
|
||||||
|
metadata.Destination = request.Destination
|
||||||
|
return r.router.RoutePacketConnection(ctx, uot.NewConn(conn, *request), metadata)
|
||||||
|
case uot.LegacyMagicAddress:
|
||||||
|
r.logger.InfoContext(ctx, "inbound legacy UoT connection")
|
||||||
|
metadata.Domain = metadata.Destination.Fqdn
|
||||||
|
metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
|
||||||
|
return r.RoutePacketConnection(ctx, uot.NewConn(conn, uot.Request{}), metadata)
|
||||||
|
}
|
||||||
|
return r.router.RouteConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
|
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
@@ -9,3 +9,11 @@ const (
|
|||||||
LogicalTypeAnd = "and"
|
LogicalTypeAnd = "and"
|
||||||
LogicalTypeOr = "or"
|
LogicalTypeOr = "or"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
RuleSetTypeLocal = "local"
|
||||||
|
RuleSetTypeRemote = "remote"
|
||||||
|
RuleSetVersion1 = 1
|
||||||
|
RuleSetFormatSource = "source"
|
||||||
|
RuleSetFormatBinary = "binary"
|
||||||
|
)
|
||||||
|
|||||||
3
constant/speed.go
Normal file
3
constant/speed.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
const MbpsToBps = 125000
|
||||||
@@ -5,4 +5,5 @@ const (
|
|||||||
V2RayTransportTypeWebsocket = "ws"
|
V2RayTransportTypeWebsocket = "ws"
|
||||||
V2RayTransportTypeQUIC = "quic"
|
V2RayTransportTypeQUIC = "quic"
|
||||||
V2RayTransportTypeGRPC = "grpc"
|
V2RayTransportTypeGRPC = "grpc"
|
||||||
|
V2RayTransportTypeHTTPUpgrade = "httpupgrade"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,3 +1,223 @@
|
|||||||
|
---
|
||||||
|
icon: material/alert-decagram
|
||||||
|
---
|
||||||
|
|
||||||
|
# ChangeLog
|
||||||
|
|
||||||
|
#### 1.8.0-alpha.6
|
||||||
|
|
||||||
|
* Fix rule-set matching logic **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Now the rules in the `rule_set` rule item can be logically considered to be merged into the rule using rule sets,
|
||||||
|
rather than completely following the AND logic.
|
||||||
|
|
||||||
|
#### 1.8.0-alpha.5
|
||||||
|
|
||||||
|
* Parallel rule-set initialization
|
||||||
|
* Independent `source_ip_is_private` and `ip_is_private` rules **1**
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
The `private` GeoIP country never existed and was actually implemented inside V2Ray.
|
||||||
|
Since GeoIP was deprecated, we made this rule independent, see [Migration](/migration/#migrate-geoip-to-rule-sets).
|
||||||
|
|
||||||
|
#### 1.8.0-alpha.1
|
||||||
|
|
||||||
|
* Migrate cache file from Clash API to independent options **1**
|
||||||
|
* Introducing [Rule Set](/configuration/rule-set) **2**
|
||||||
|
* Add `sing-box geoip`, `sing-box geosite` and `sing-box rule-set` commands **3**
|
||||||
|
* Allow nested logical rules **4**
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
See [Cache File](/configuration/experimental/cache-file) and
|
||||||
|
[Migration](/migration/#migrate-cache-file-from-clash-api-to-independent-options).
|
||||||
|
|
||||||
|
**2**:
|
||||||
|
|
||||||
|
Rule set is independent collections of rules that can be compiled into binaries to improve performance.
|
||||||
|
Compared to legacy GeoIP and Geosite resources,
|
||||||
|
it can include more types of rules, load faster,
|
||||||
|
use less memory, and update automatically.
|
||||||
|
|
||||||
|
See [Route#rule_set](/configuration/route/#rule_set),
|
||||||
|
[Route Rule](/configuration/route/rule),
|
||||||
|
[DNS Rule](/configuration/dns/rule),
|
||||||
|
[Rule Set](/configuration/rule-set),
|
||||||
|
[Source Format](/configuration/rule-set/source-format) and
|
||||||
|
[Headless Rule](/configuration/rule-set/headless-rule).
|
||||||
|
|
||||||
|
For GEO resources migration, see [Migrate GeoIP to rule sets](/migration/#migrate-geoip-to-rule-sets) and
|
||||||
|
[Migrate Geosite to rule sets](/migration/#migrate-geosite-to-rule-sets).
|
||||||
|
|
||||||
|
**3**:
|
||||||
|
|
||||||
|
New commands manage GeoIP, Geosite and rule set resources, and help you migrate GEO resources to rule sets.
|
||||||
|
|
||||||
|
**4**:
|
||||||
|
|
||||||
|
Logical rules in route rules, DNS rules, and the new headless rule now allow nesting of logical rules.
|
||||||
|
|
||||||
|
#### 1.7.0
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
Important changes since 1.6:
|
||||||
|
|
||||||
|
* Add [exclude route support](/configuration/inbound/tun) for TUN inbound
|
||||||
|
* Add `udp_disable_domain_unmapping` [inbound listen option](/configuration/shared/listen) **1**
|
||||||
|
* Add [HTTPUpgrade V2Ray transport](/configuration/shared/v2ray-transport#HTTPUpgrade) support **2**
|
||||||
|
* Migrate multiplex and UoT server to inbound **3**
|
||||||
|
* Add TCP Brutal support for multiplex **4**
|
||||||
|
* Add `wifi_ssid` and `wifi_bssid` route and DNS rules **5**
|
||||||
|
* Update quic-go to v0.40.0
|
||||||
|
* Update gVisor to 20231113.0
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
If enabled, for UDP proxy requests addressed to a domain,
|
||||||
|
the original packet address will be sent in the response instead of the mapped domain.
|
||||||
|
|
||||||
|
This option is used for compatibility with clients that
|
||||||
|
do not support receiving UDP packets with domain addresses, such as Surge.
|
||||||
|
|
||||||
|
**2**:
|
||||||
|
|
||||||
|
Introduced in V2Ray 5.10.0.
|
||||||
|
|
||||||
|
The new HTTPUpgrade transport has better performance than WebSocket and is better suited for CDN abuse.
|
||||||
|
|
||||||
|
**3**:
|
||||||
|
|
||||||
|
Starting in 1.7.0, multiplexing support is no longer enabled by default and needs to be turned on explicitly in inbound options.
|
||||||
|
|
||||||
|
**4**
|
||||||
|
|
||||||
|
Hysteria Brutal Congestion Control Algorithm in TCP. A kernel module needs to be installed on the Linux server, see [TCP Brutal](/configuration/shared/tcp-brutal) for details.
|
||||||
|
|
||||||
|
**5**:
|
||||||
|
|
||||||
|
Only supported in graphical clients on Android and iOS.
|
||||||
|
|
||||||
|
#### 1.7.0-rc.3
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.7
|
||||||
|
|
||||||
|
* macOS: Add button for uninstall SystemExtension in the standalone graphical client
|
||||||
|
* Fix missing UDP user context on TUIC/Hysteria2 inbounds
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-rc.2
|
||||||
|
|
||||||
|
* Fix missing UDP user context on TUIC/Hysteria2 inbounds
|
||||||
|
* macOS: Add button for uninstall SystemExtension in the standalone graphical client
|
||||||
|
|
||||||
|
#### 1.6.6
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-rc.1
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-beta.5
|
||||||
|
|
||||||
|
* Update gVisor to 20231113.0
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-beta.4
|
||||||
|
|
||||||
|
* Add `wifi_ssid` and `wifi_bssid` route and DNS rules **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Only supported in graphical clients on Android and iOS.
|
||||||
|
|
||||||
|
#### 1.7.0-beta.3
|
||||||
|
|
||||||
|
* Fix zero TTL was incorrectly reset
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.5
|
||||||
|
|
||||||
|
* Fix crash if TUIC inbound authentication failed
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-beta.2
|
||||||
|
|
||||||
|
* Fix crash if TUIC inbound authentication failed
|
||||||
|
* Update quic-go to v0.40.0
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.4
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-beta.1
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.3
|
||||||
|
|
||||||
|
* iOS/Android: Fix profile auto update
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-alpha.11
|
||||||
|
|
||||||
|
* iOS/Android: Fix profile auto update
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-alpha.10
|
||||||
|
|
||||||
|
* Fix tcp-brutal not working with TLS
|
||||||
|
* Fix Android client not closing in some cases
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.2
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.1
|
||||||
|
|
||||||
|
* Our [Android client](/installation/clients/sfa) is now available in the Google Play Store ▶️
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-alpha.6
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.7.0-alpha.4
|
||||||
|
|
||||||
|
* Migrate multiplex and UoT server to inbound **1**
|
||||||
|
* Add TCP Brutal support for multiplex **2**
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Starting in 1.7.0, multiplexing support is no longer enabled by default and needs to be turned on explicitly in inbound
|
||||||
|
options.
|
||||||
|
|
||||||
|
**2**
|
||||||
|
|
||||||
|
Hysteria Brutal Congestion Control Algorithm in TCP. A kernel module needs to be installed on the Linux server,
|
||||||
|
see [TCP Brutal](/configuration/shared/tcp-brutal) for details.
|
||||||
|
|
||||||
|
#### 1.7.0-alpha.3
|
||||||
|
|
||||||
|
* Add [HTTPUpgrade V2Ray transport](/configuration/shared/v2ray-transport#HTTPUpgrade) support **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Introduced in V2Ray 5.10.0.
|
||||||
|
|
||||||
|
The new HTTPUpgrade transport has better performance than WebSocket and is better suited for CDN abuse.
|
||||||
|
|
||||||
#### 1.6.0
|
#### 1.6.0
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
@@ -22,6 +242,23 @@ This update is intended to address the multi-send defects of the old implementat
|
|||||||
Based on discussions with the original author, the brutal CC and QUIC protocol parameters of
|
Based on discussions with the original author, the brutal CC and QUIC protocol parameters of
|
||||||
the old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2
|
the old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2
|
||||||
|
|
||||||
|
#### 1.7.0-alpha.2
|
||||||
|
|
||||||
|
* Fix bugs introduced in 1.7.0-alpha.1
|
||||||
|
|
||||||
|
#### 1.7.0-alpha.1
|
||||||
|
|
||||||
|
* Add [exclude route support](/configuration/inbound/tun) for TUN inbound
|
||||||
|
* Add `udp_disable_domain_unmapping` [inbound listen option](/configuration/shared/listen) **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
If enabled, for UDP proxy requests addressed to a domain,
|
||||||
|
the original packet address will be sent in the response instead of the mapped domain.
|
||||||
|
|
||||||
|
This option is used for compatibility with clients that
|
||||||
|
do not support receiving UDP packets with domain addresses, such as Surge.
|
||||||
|
|
||||||
#### 1.5.5
|
#### 1.5.5
|
||||||
|
|
||||||
@@ -35,8 +272,8 @@ When `auto_route` is enabled and `strict_route` is disabled, the device can now
|
|||||||
|
|
||||||
**2**:
|
**2**:
|
||||||
|
|
||||||
Built using Go 1.20, the last version that will run on Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High Sierra, 10.14 Mojave.
|
Built using Go 1.20, the last version that will run on Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High
|
||||||
|
Sierra, 10.14 Mojave.
|
||||||
|
|
||||||
#### 1.6.0-rc.4
|
#### 1.6.0-rc.4
|
||||||
|
|
||||||
@@ -49,7 +286,8 @@ Built using Go 1.20, the last version that will run on Windows 7, 8, Server 2008
|
|||||||
|
|
||||||
**1**:
|
**1**:
|
||||||
|
|
||||||
Built using Go 1.20, the last version that will run on Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High Sierra, 10.14 Mojave.
|
Built using Go 1.20, the last version that will run on Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High
|
||||||
|
Sierra, 10.14 Mojave.
|
||||||
|
|
||||||
#### 1.6.0-beta.4
|
#### 1.6.0-beta.4
|
||||||
|
|
||||||
@@ -83,6 +321,24 @@ the old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2
|
|||||||
* Update golang.org/x/net to v0.17.0
|
* Update golang.org/x/net to v0.17.0
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.0-beta.3
|
||||||
|
|
||||||
|
* Update the legacy Hysteria protocol **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**
|
||||||
|
|
||||||
|
Based on discussions with the original author, the brutal CC and QUIC protocol parameters of
|
||||||
|
the old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2
|
||||||
|
|
||||||
|
#### 1.6.0-beta.2
|
||||||
|
|
||||||
|
* Add TLS self sign key pair generate command
|
||||||
|
* Update brutal congestion control for Hysteria2
|
||||||
|
* Fix Clash cache crash on arm32 devices
|
||||||
|
* Update golang.org/x/net to v0.17.0
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.5.3
|
#### 1.5.3
|
||||||
|
|
||||||
* Fix compatibility with Android 14
|
* Fix compatibility with Android 14
|
||||||
|
|||||||
64
docs/clients/android/features.md
Normal file
64
docs/clients/android/features.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# :material-decagram: Features
|
||||||
|
|
||||||
|
#### UI options
|
||||||
|
|
||||||
|
* Display realtime network speed in the notification
|
||||||
|
|
||||||
|
#### Service
|
||||||
|
|
||||||
|
SFA allows you to run sing-box through ForegroundService or VpnService (when TUN is required).
|
||||||
|
|
||||||
|
#### TUN
|
||||||
|
|
||||||
|
SFA provides an unprivileged TUN implementation through Android VpnService.
|
||||||
|
|
||||||
|
| TUN inbound option | Available | Note |
|
||||||
|
|-------------------------------|------------------|--------------------|
|
||||||
|
| `interface_name` | :material-close: | Managed by Android |
|
||||||
|
| `inet4_address` | :material-check: | / |
|
||||||
|
| `inet6_address` | :material-check: | / |
|
||||||
|
| `mtu` | :material-check: | / |
|
||||||
|
| `auto_route` | :material-check: | / |
|
||||||
|
| `strict_route` | :material-close: | Not implemented |
|
||||||
|
| `inet4_route_address` | :material-check: | / |
|
||||||
|
| `inet6_route_address` | :material-check: | / |
|
||||||
|
| `inet4_route_exclude_address` | :material-check: | / |
|
||||||
|
| `inet6_route_exclude_address` | :material-check: | / |
|
||||||
|
| `endpoint_independent_nat` | :material-check: | / |
|
||||||
|
| `stack` | :material-check: | / |
|
||||||
|
| `include_interface` | :material-close: | No permission |
|
||||||
|
| `exclude_interface` | :material-close: | No permission |
|
||||||
|
| `include_uid` | :material-close: | No permission |
|
||||||
|
| `exclude_uid` | :material-close: | No permission |
|
||||||
|
| `include_android_user` | :material-close: | No permission |
|
||||||
|
| `include_package` | :material-check: | / |
|
||||||
|
| `exclude_package` | :material-check: | / |
|
||||||
|
| `platform` | :material-check: | / |
|
||||||
|
|
||||||
|
| Route/DNS rule option | Available | Note |
|
||||||
|
|-----------------------|------------------|-----------------------------------|
|
||||||
|
| `process_name` | :material-close: | No permission |
|
||||||
|
| `process_path` | :material-close: | No permission |
|
||||||
|
| `package_name` | :material-check: | / |
|
||||||
|
| `user` | :material-close: | Use `package_name` instead |
|
||||||
|
| `user_id` | :material-close: | Use `package_name` instead |
|
||||||
|
| `wifi_ssid` | :material-check: | Fine location permission required |
|
||||||
|
| `wifi_bssid` | :material-check: | Fine location permission required |
|
||||||
|
|
||||||
|
### Override
|
||||||
|
|
||||||
|
Overrides profile configuration items with platform-specific values.
|
||||||
|
|
||||||
|
#### Per-app proxy
|
||||||
|
|
||||||
|
SFA allows you to select a list of Android apps that require proxying or bypassing in the graphical interface to
|
||||||
|
override the `include_package` and `exclude_package` configuration items.
|
||||||
|
|
||||||
|
In particular, the selector also provides the “China apps” scanning feature, providing Chinese users with an excellent
|
||||||
|
experience to bypass apps that do not require a proxy. Specifically, by scanning China application or SDK
|
||||||
|
characteristics through dex class path and other means, there will be almost no missed reports.
|
||||||
|
|
||||||
|
### Chore
|
||||||
|
|
||||||
|
* The working directory is located at `/sdcard/Android/data/io.nekohasekai.sfa/files` (External files directory)
|
||||||
|
* Crash logs is located in `$working_directory/stderr.log`
|
||||||
22
docs/clients/android/index.md
Normal file
22
docs/clients/android/index.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
icon: material/android
|
||||||
|
---
|
||||||
|
|
||||||
|
# sing-box for Android
|
||||||
|
|
||||||
|
SFA allows users to manage and run local or remote sing-box configuration files, and provides
|
||||||
|
platform-specific function implementation, such as TUN transparent proxy implementation.
|
||||||
|
|
||||||
|
## :material-graph: Requirements
|
||||||
|
|
||||||
|
* Android 5.0+
|
||||||
|
|
||||||
|
## :material-download: Download
|
||||||
|
|
||||||
|
* [Play Store](https://play.google.com/store/apps/details?id=io.nekohasekai.sfa)
|
||||||
|
* [Play Store (Beta)](https://play.google.com/apps/testing/io.nekohasekai.sfa)
|
||||||
|
* [GitHub Releases](https://github.com/SagerNet/sing-box/releases)
|
||||||
|
|
||||||
|
## :material-source-repository: Source code
|
||||||
|
|
||||||
|
* [GitHub](https://github.com/SagerNet/sing-box-for-android)
|
||||||
52
docs/clients/apple/features.md
Normal file
52
docs/clients/apple/features.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# :material-decagram: Features
|
||||||
|
|
||||||
|
#### UI options
|
||||||
|
|
||||||
|
* Always On
|
||||||
|
* Include All Networks (Proxy traffic for LAN and cellular services)
|
||||||
|
* (Apple tvOS) Import profile from iPhone/iPad
|
||||||
|
|
||||||
|
#### Service
|
||||||
|
|
||||||
|
SFI/SFM/SFT allows you to run sing-box through NetworkExtension with Application Extension or System Extension.
|
||||||
|
|
||||||
|
#### TUN
|
||||||
|
|
||||||
|
SFI/SFM/SFT provides an unprivileged TUN implementation through NetworkExtension.
|
||||||
|
|
||||||
|
| TUN inbound option | Available | Note |
|
||||||
|
|-------------------------------|-----------|-------------------|
|
||||||
|
| `interface_name` | ✖️ | Managed by Darwin |
|
||||||
|
| `inet4_address` | ✔️ | / |
|
||||||
|
| `inet6_address` | ✔️ | / |
|
||||||
|
| `mtu` | ✔️ | / |
|
||||||
|
| `auto_route` | ✔️ | / |
|
||||||
|
| `strict_route` | ✖️ | Not implemented |
|
||||||
|
| `inet4_route_address` | ✔️ | / |
|
||||||
|
| `inet6_route_address` | ✔️ | / |
|
||||||
|
| `inet4_route_exclude_address` | ✔️ | / |
|
||||||
|
| `inet6_route_exclude_address` | ✔️ | / |
|
||||||
|
| `endpoint_independent_nat` | ✔️ | / |
|
||||||
|
| `stack` | ✔️ | / |
|
||||||
|
| `include_interface` | ✖️ | Not implemented |
|
||||||
|
| `exclude_interface` | ✖️ | Not implemented |
|
||||||
|
| `include_uid` | ✖️ | Not implemented |
|
||||||
|
| `exclude_uid` | ✖️ | Not implemented |
|
||||||
|
| `include_android_user` | ✖️ | Not implemented |
|
||||||
|
| `include_package` | ✖️ | Not implemented |
|
||||||
|
| `exclude_package` | ✖️ | Not implemented |
|
||||||
|
| `platform` | ✔️ | / |
|
||||||
|
|
||||||
|
| Route/DNS rule option | Available | Note |
|
||||||
|
|-----------------------|------------------|-----------------------|
|
||||||
|
| `process_name` | :material-close: | No permission |
|
||||||
|
| `process_path` | :material-close: | No permission |
|
||||||
|
| `package_name` | :material-close: | / |
|
||||||
|
| `user` | :material-close: | No permission |
|
||||||
|
| `user_id` | :material-close: | No permission |
|
||||||
|
| `wifi_ssid` | :material-alert: | Only supported on iOS |
|
||||||
|
| `wifi_bssid` | :material-alert: | Only supported on iOS |
|
||||||
|
|
||||||
|
### Chore
|
||||||
|
|
||||||
|
* Crash logs is located in `Settings` -> `View Service Log`
|
||||||
32
docs/clients/apple/index.md
Normal file
32
docs/clients/apple/index.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
icon: material/apple
|
||||||
|
---
|
||||||
|
|
||||||
|
# sing-box for Apple platforms
|
||||||
|
|
||||||
|
SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides
|
||||||
|
platform-specific function implementation, such as TUN transparent proxy implementation.
|
||||||
|
|
||||||
|
## :material-graph: Requirements
|
||||||
|
|
||||||
|
* iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+
|
||||||
|
* An Apple account outside of mainland China
|
||||||
|
|
||||||
|
## :material-download: Download
|
||||||
|
|
||||||
|
* [App Store](https://apps.apple.com/us/app/sing-box/id6451272673)
|
||||||
|
* [TestFlight (Beta)](https://testflight.apple.com/join/AcqO44FH)
|
||||||
|
|
||||||
|
## :material-file-download: Download (macOS standalone version)
|
||||||
|
|
||||||
|
* [Homebrew Cask](https://formulae.brew.sh/cask/sfm)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install sfm
|
||||||
|
```
|
||||||
|
|
||||||
|
* [GitHub Releases](https://github.com/SagerNet/sing-box/releases)
|
||||||
|
|
||||||
|
## :material-source-repository: Source code
|
||||||
|
|
||||||
|
* [GitHub](https://github.com/SagerNet/sing-box-for-apple)
|
||||||
63
docs/clients/general.md
Normal file
63
docs/clients/general.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
icon: material/pencil-ruler
|
||||||
|
---
|
||||||
|
|
||||||
|
# General
|
||||||
|
|
||||||
|
Describes and explains the functions implemented uniformly by sing-box graphical clients.
|
||||||
|
|
||||||
|
### Profile
|
||||||
|
|
||||||
|
Profile describes a sing-box configuration file and its state.
|
||||||
|
|
||||||
|
#### Local
|
||||||
|
|
||||||
|
* Local Profile represents a local sing-box configuration with minimal state
|
||||||
|
* The graphical client must provide an editor to modify configuration content
|
||||||
|
|
||||||
|
#### iCloud (on iOS and macOS)
|
||||||
|
|
||||||
|
* iCloud Profile represents a remote sing-box configuration with iCloud as the update source
|
||||||
|
* The configuration file is stored in the sing-box folder under iCloud
|
||||||
|
* The graphical client must provide an editor to modify configuration content
|
||||||
|
|
||||||
|
#### Remote
|
||||||
|
|
||||||
|
* Remote Profile represents a remote sing-box configuration with a URL as the update source.
|
||||||
|
* The graphical client should provide a configuration content viewer
|
||||||
|
* The graphical client must implement automatic profile update (default interval is 60 minutes) and HTTP Basic
|
||||||
|
authorization.
|
||||||
|
|
||||||
|
At the same time, the graphical client must provide support for importing remote profiles
|
||||||
|
through a specific URL Scheme. The URL is defined as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
sing-box://import-remote-profile?url=urlEncodedURL#urlEncodedName
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dashboard
|
||||||
|
|
||||||
|
While the sing-box service is running, the graphical client should provide a Dashboard interface to manage the service.
|
||||||
|
|
||||||
|
#### Status
|
||||||
|
|
||||||
|
Dashboard should display status information such as memory, connection, and traffic.
|
||||||
|
|
||||||
|
#### Mode
|
||||||
|
|
||||||
|
Dashboard should provide a Mode selector for switching when the configuration uses at least two `clash_mode` values.
|
||||||
|
|
||||||
|
#### Groups
|
||||||
|
|
||||||
|
When the configuration includes group outbounds (specifically, Selector or URLTest),
|
||||||
|
the dashboard should provide a Group selector for status display or switching.
|
||||||
|
|
||||||
|
### Chore
|
||||||
|
|
||||||
|
#### Core
|
||||||
|
|
||||||
|
Graphical clients should provide a Core region:
|
||||||
|
|
||||||
|
* Display the current sing-box version
|
||||||
|
* Provides a button to clean the working directory
|
||||||
|
* Provides a memory limiter switch
|
||||||
13
docs/clients/index.md
Normal file
13
docs/clients/index.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# :material-cellphone-link: Graphical Clients
|
||||||
|
|
||||||
|
Maintained by Project S to provide a unified experience and platform-specific functionality.
|
||||||
|
|
||||||
|
| Platform | Client |
|
||||||
|
|---------------------------------------|-----------------------------------------|
|
||||||
|
| :material-android: Android | [sing-box for Android](./android) |
|
||||||
|
| :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple) |
|
||||||
|
| :material-laptop: Desktop | Working in progress |
|
||||||
|
|
||||||
|
Some third-party projects that claim to use sing-box or use sing-box as a selling point are not listed here. The core
|
||||||
|
motivation of the maintainers of such projects is to acquire more users, and even though they provide friendly VPN
|
||||||
|
client features, the code is usually of poor quality and contains ads.
|
||||||
12
docs/clients/index.zh.md
Normal file
12
docs/clients/index.zh.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# :material-cellphone-link: 图形界面客户端
|
||||||
|
|
||||||
|
由 Project S 维护,提供统一的体验与平台特定的功能。
|
||||||
|
|
||||||
|
| 平台 | 客户端 |
|
||||||
|
|---------------------------------------|-----------------------------------------|
|
||||||
|
| :material-android: Android | [sing-box for Android](./android) |
|
||||||
|
| :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple) |
|
||||||
|
| :material-laptop: Desktop | 施工中 |
|
||||||
|
|
||||||
|
此处没有列出一些声称使用或以 sing-box 为卖点的第三方项目。此类项目维护者的动机是获得更多用户,即使它们提供友好的商业
|
||||||
|
VPN 客户端功能, 但代码质量很差且包含广告。
|
||||||
8
docs/clients/privacy.md
Normal file
8
docs/clients/privacy.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
icon: material/security
|
||||||
|
---
|
||||||
|
|
||||||
|
# Privacy policy
|
||||||
|
|
||||||
|
sing-box and official graphics clients do not collect or share personal data,
|
||||||
|
and the data generated by the software is always on your device.
|
||||||
@@ -1,3 +1,14 @@
|
|||||||
|
---
|
||||||
|
icon: material/alert-decagram
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.8.0"
|
||||||
|
|
||||||
|
:material-plus: [rule_set](#rule_set)
|
||||||
|
:material-plus: [source_ip_is_private](#source_ip_is_private)
|
||||||
|
:material-delete-clock: [geoip](#geoip)
|
||||||
|
:material-delete-clock: [geosite](#geosite)
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -46,6 +57,7 @@
|
|||||||
"10.0.0.0/24",
|
"10.0.0.0/24",
|
||||||
"192.168.0.1"
|
"192.168.0.1"
|
||||||
],
|
],
|
||||||
|
"source_ip_is_private": false,
|
||||||
"source_port": [
|
"source_port": [
|
||||||
12345
|
12345
|
||||||
],
|
],
|
||||||
@@ -79,6 +91,16 @@
|
|||||||
1000
|
1000
|
||||||
],
|
],
|
||||||
"clash_mode": "direct",
|
"clash_mode": "direct",
|
||||||
|
"wifi_ssid": [
|
||||||
|
"My WIFI"
|
||||||
|
],
|
||||||
|
"wifi_bssid": [
|
||||||
|
"00:00:00:00:00:00"
|
||||||
|
],
|
||||||
|
"rule_set": [
|
||||||
|
"geoip-cn",
|
||||||
|
"geosite-cn"
|
||||||
|
],
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"outbound": [
|
"outbound": [
|
||||||
"direct"
|
"direct"
|
||||||
@@ -160,15 +182,29 @@ Match domain using regular expression.
|
|||||||
|
|
||||||
#### geosite
|
#### geosite
|
||||||
|
|
||||||
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
|
Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets).
|
||||||
|
|
||||||
Match geosite.
|
Match geosite.
|
||||||
|
|
||||||
#### source_geoip
|
#### source_geoip
|
||||||
|
|
||||||
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
|
GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
||||||
|
|
||||||
Match source geoip.
|
Match source geoip.
|
||||||
|
|
||||||
#### source_ip_cidr
|
#### source_ip_cidr
|
||||||
|
|
||||||
Match source ip cidr.
|
Match source IP CIDR.
|
||||||
|
|
||||||
|
#### source_ip_is_private
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.8.0"
|
||||||
|
|
||||||
|
Match non-public source IP.
|
||||||
|
|
||||||
#### source_port
|
#### source_port
|
||||||
|
|
||||||
@@ -188,7 +224,7 @@ Match port range.
|
|||||||
|
|
||||||
#### process_name
|
#### process_name
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported on Linux, Windows, and macOS.
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
@@ -196,7 +232,7 @@ Match process name.
|
|||||||
|
|
||||||
#### process_path
|
#### process_path
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported on Linux, Windows, and macOS.
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
@@ -208,7 +244,7 @@ Match android package name.
|
|||||||
|
|
||||||
#### user
|
#### user
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported on Linux.
|
Only supported on Linux.
|
||||||
|
|
||||||
@@ -216,7 +252,7 @@ Match user name.
|
|||||||
|
|
||||||
#### user_id
|
#### user_id
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported on Linux.
|
Only supported on Linux.
|
||||||
|
|
||||||
@@ -226,6 +262,30 @@ Match user id.
|
|||||||
|
|
||||||
Match Clash mode.
|
Match Clash mode.
|
||||||
|
|
||||||
|
#### wifi_ssid
|
||||||
|
|
||||||
|
<!-- md:version 1.7.0-beta.4 -->
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported in graphical clients on Android and iOS.
|
||||||
|
|
||||||
|
Match WiFi SSID.
|
||||||
|
|
||||||
|
#### wifi_bssid
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported in graphical clients on Android and iOS.
|
||||||
|
|
||||||
|
Match WiFi BSSID.
|
||||||
|
|
||||||
|
#### rule_set
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.8.0"
|
||||||
|
|
||||||
|
Match [Rule Set](/configuration/route/#rule_set).
|
||||||
|
|
||||||
#### invert
|
#### invert
|
||||||
|
|
||||||
Invert match result.
|
Invert match result.
|
||||||
@@ -262,4 +322,4 @@ Rewrite TTL in DNS responses.
|
|||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
Included default rules.
|
Included rules.
|
||||||
@@ -1,3 +1,14 @@
|
|||||||
|
---
|
||||||
|
icon: material/alert-decagram
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.8.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: [rule_set](#rule_set)
|
||||||
|
:material-plus: [source_ip_is_private](#source_ip_is_private)
|
||||||
|
:material-delete-clock: [geoip](#geoip)
|
||||||
|
:material-delete-clock: [geosite](#geosite)
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -45,6 +56,7 @@
|
|||||||
"source_ip_cidr": [
|
"source_ip_cidr": [
|
||||||
"10.0.0.0/24"
|
"10.0.0.0/24"
|
||||||
],
|
],
|
||||||
|
"source_ip_is_private": false,
|
||||||
"source_port": [
|
"source_port": [
|
||||||
12345
|
12345
|
||||||
],
|
],
|
||||||
@@ -78,6 +90,16 @@
|
|||||||
1000
|
1000
|
||||||
],
|
],
|
||||||
"clash_mode": "direct",
|
"clash_mode": "direct",
|
||||||
|
"wifi_ssid": [
|
||||||
|
"My WIFI"
|
||||||
|
],
|
||||||
|
"wifi_bssid": [
|
||||||
|
"00:00:00:00:00:00"
|
||||||
|
],
|
||||||
|
"rule_set": [
|
||||||
|
"geoip-cn",
|
||||||
|
"geosite-cn"
|
||||||
|
],
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"outbound": [
|
"outbound": [
|
||||||
"direct"
|
"direct"
|
||||||
@@ -157,16 +179,30 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
#### geosite
|
#### geosite
|
||||||
|
|
||||||
匹配 GeoSite。
|
!!! failure "已在 sing-box 1.8.0 废弃"
|
||||||
|
|
||||||
|
Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/migration/#migrate-geosite-to-rule-sets)。
|
||||||
|
|
||||||
|
匹配 Geosite。
|
||||||
|
|
||||||
#### source_geoip
|
#### source_geoip
|
||||||
|
|
||||||
|
!!! failure "已在 sing-box 1.8.0 废弃"
|
||||||
|
|
||||||
|
GeoIp 已废弃且可能在不久的将来移除,参阅 [迁移指南](/migration/#migrate-geoip-to-rule-sets)。
|
||||||
|
|
||||||
匹配源 GeoIP。
|
匹配源 GeoIP。
|
||||||
|
|
||||||
#### source_ip_cidr
|
#### source_ip_cidr
|
||||||
|
|
||||||
匹配源 IP CIDR。
|
匹配源 IP CIDR。
|
||||||
|
|
||||||
|
#### source_ip_is_private
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.8.0 起"
|
||||||
|
|
||||||
|
匹配非公开源 IP。
|
||||||
|
|
||||||
#### source_port
|
#### source_port
|
||||||
|
|
||||||
匹配源端口。
|
匹配源端口。
|
||||||
@@ -185,7 +221,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
#### process_name
|
#### process_name
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
仅支持 Linux、Windows 和 macOS.
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
@@ -193,7 +229,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
#### process_path
|
#### process_path
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
仅支持 Linux、Windows 和 macOS.
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
@@ -205,7 +241,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
#### user
|
#### user
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
仅支持 Linux。
|
仅支持 Linux。
|
||||||
|
|
||||||
@@ -213,7 +249,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
#### user_id
|
#### user_id
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
仅支持 Linux。
|
仅支持 Linux。
|
||||||
|
|
||||||
@@ -223,6 +259,28 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
匹配 Clash 模式。
|
匹配 Clash 模式。
|
||||||
|
|
||||||
|
#### wifi_ssid
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅在 Android 与 iOS 的图形客户端中支持。
|
||||||
|
|
||||||
|
匹配 WiFi SSID。
|
||||||
|
|
||||||
|
#### wifi_bssid
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅在 Android 与 iOS 的图形客户端中支持。
|
||||||
|
|
||||||
|
匹配 WiFi BSSID。
|
||||||
|
|
||||||
|
#### rule_set
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.8.0 起"
|
||||||
|
|
||||||
|
匹配[规则集](/zh/configuration/route/#rule_set)。
|
||||||
|
|
||||||
#### invert
|
#### invert
|
||||||
|
|
||||||
反选匹配结果。
|
反选匹配结果。
|
||||||
@@ -259,4 +317,4 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
包括的默认规则。
|
包括的规则。
|
||||||
@@ -49,7 +49,7 @@ The address of the dns server.
|
|||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
QUIC and HTTP3 transport is not included by default, see [Installation](/#installation).
|
QUIC and HTTP3 transport is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
!!! info ""
|
!!! info ""
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ The address of the dns server.
|
|||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
DHCP transport is not included by default, see [Installation](/#installation).
|
DHCP transport is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
| RCode | Description |
|
| RCode | Description |
|
||||||
|-------------------|-----------------------|
|
|-------------------|-----------------------|
|
||||||
|
|||||||
34
docs/configuration/experimental/cache-file.md
Normal file
34
docs/configuration/experimental/cache-file.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.8.0"
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"path": "",
|
||||||
|
"cache_id": "",
|
||||||
|
"store_fakeip": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### enabled
|
||||||
|
|
||||||
|
Enable cache file.
|
||||||
|
|
||||||
|
#### path
|
||||||
|
|
||||||
|
Path to the cache file.
|
||||||
|
|
||||||
|
`cache.db` will be used if empty.
|
||||||
|
|
||||||
|
#### cache_id
|
||||||
|
|
||||||
|
Identifier in cache file.
|
||||||
|
|
||||||
|
If not empty, configuration specified data will use a separate store keyed by it.
|
||||||
121
docs/configuration/experimental/clash-api.md
Normal file
121
docs/configuration/experimental/clash-api.md
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
---
|
||||||
|
icon: material/alert-decagram
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.8.0"
|
||||||
|
|
||||||
|
:material-delete-alert: [store_mode](#store_mode)
|
||||||
|
:material-delete-alert: [store_selected](#store_selected)
|
||||||
|
:material-delete-alert: [store_fakeip](#store_fakeip)
|
||||||
|
:material-delete-alert: [cache_file](#cache_file)
|
||||||
|
:material-delete-alert: [cache_id](#cache_id)
|
||||||
|
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Clash API is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"external_controller": "127.0.0.1:9090",
|
||||||
|
"external_ui": "",
|
||||||
|
"external_ui_download_url": "",
|
||||||
|
"external_ui_download_detour": "",
|
||||||
|
"secret": "",
|
||||||
|
"default_mode": "",
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
|
||||||
|
"store_mode": false,
|
||||||
|
"store_selected": false,
|
||||||
|
"store_fakeip": false,
|
||||||
|
"cache_file": "",
|
||||||
|
"cache_id": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### external_controller
|
||||||
|
|
||||||
|
RESTful web API listening address. Clash API will be disabled if empty.
|
||||||
|
|
||||||
|
#### external_ui
|
||||||
|
|
||||||
|
A relative path to the configuration directory or an absolute path to a
|
||||||
|
directory in which you put some static web resource. sing-box will then
|
||||||
|
serve it at `http://{{external-controller}}/ui`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### external_ui_download_url
|
||||||
|
|
||||||
|
ZIP download URL for the external UI, will be used if the specified `external_ui` directory is empty.
|
||||||
|
|
||||||
|
`https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip` will be used if empty.
|
||||||
|
|
||||||
|
#### external_ui_download_detour
|
||||||
|
|
||||||
|
The tag of the outbound to download the external UI.
|
||||||
|
|
||||||
|
Default outbound will be used if empty.
|
||||||
|
|
||||||
|
#### secret
|
||||||
|
|
||||||
|
Secret for the RESTful API (optional)
|
||||||
|
Authenticate by spedifying HTTP header `Authorization: Bearer ${secret}`
|
||||||
|
ALWAYS set a secret if RESTful API is listening on 0.0.0.0
|
||||||
|
|
||||||
|
#### default_mode
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### store_mode
|
||||||
|
|
||||||
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
|
`store_mode` is deprecated in Clash API and enabled by default if `cache_file.enabled`.
|
||||||
|
|
||||||
|
Store Clash mode in cache file.
|
||||||
|
|
||||||
|
#### store_selected
|
||||||
|
|
||||||
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
|
`store_selected` is deprecated in Clash API and enabled by default if `cache_file.enabled`.
|
||||||
|
|
||||||
|
!!! note ""
|
||||||
|
|
||||||
|
The tag must be set for target outbounds.
|
||||||
|
|
||||||
|
Store selected outbound for the `Selector` outbound in cache file.
|
||||||
|
|
||||||
|
#### store_fakeip
|
||||||
|
|
||||||
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
|
`store_selected` is deprecated in Clash API and migrated to `cache_file.store_fakeip`.
|
||||||
|
|
||||||
|
Store fakeip in cache file.
|
||||||
|
|
||||||
|
#### cache_file
|
||||||
|
|
||||||
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
|
`cache_file` is deprecated in Clash API and migrated to `cache_file.enabled` and `cache_file.path`.
|
||||||
|
|
||||||
|
Cache file path, `cache.db` will be used if empty.
|
||||||
|
|
||||||
|
#### cache_id
|
||||||
|
|
||||||
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
|
`cache_id` is deprecated in Clash API and migrated to `cache_file.cache_id`.
|
||||||
|
|
||||||
|
Identifier in cache file.
|
||||||
|
|
||||||
|
If not empty, configuration specified data will use a separate store keyed by it.
|
||||||
@@ -1,139 +1,30 @@
|
|||||||
|
---
|
||||||
|
icon: material/alert-decagram
|
||||||
|
---
|
||||||
|
|
||||||
# Experimental
|
# Experimental
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.8.0"
|
||||||
|
|
||||||
|
:material-plus: [cache_file](#cache_file)
|
||||||
|
:material-alert-decagram: [clash_api](#clash_api)
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"experimental": {
|
"experimental": {
|
||||||
"clash_api": {
|
"cache_file": {},
|
||||||
"external_controller": "127.0.0.1:9090",
|
"clash_api": {},
|
||||||
"external_ui": "",
|
"v2ray_api": {}
|
||||||
"external_ui_download_url": "",
|
|
||||||
"external_ui_download_detour": "",
|
|
||||||
"secret": "",
|
|
||||||
"default_mode": "",
|
|
||||||
"store_mode": false,
|
|
||||||
"store_selected": false,
|
|
||||||
"store_fakeip": false,
|
|
||||||
"cache_file": "",
|
|
||||||
"cache_id": ""
|
|
||||||
},
|
|
||||||
"v2ray_api": {
|
|
||||||
"listen": "127.0.0.1:8080",
|
|
||||||
"stats": {
|
|
||||||
"enabled": true,
|
|
||||||
"inbounds": [
|
|
||||||
"socks-in"
|
|
||||||
],
|
|
||||||
"outbounds": [
|
|
||||||
"proxy",
|
|
||||||
"direct"
|
|
||||||
],
|
|
||||||
"users": [
|
|
||||||
"sekai"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! note ""
|
### Fields
|
||||||
|
|
||||||
Traffic statistics and connection management can degrade performance.
|
| Key | Format |
|
||||||
|
|--------------|----------------------------|
|
||||||
### Clash API Fields
|
| `cache_file` | [Cache File](./cache-file) |
|
||||||
|
| `clash_api` | [Clash API](./clash-api) |
|
||||||
!!! error ""
|
| `v2ray_api` | [V2Ray API](./v2ray-api) |
|
||||||
|
|
||||||
Clash API is not included by default, see [Installation](/#installation).
|
|
||||||
|
|
||||||
#### external_controller
|
|
||||||
|
|
||||||
RESTful web API listening address. Clash API will be disabled if empty.
|
|
||||||
|
|
||||||
#### external_ui
|
|
||||||
|
|
||||||
A relative path to the configuration directory or an absolute path to a
|
|
||||||
directory in which you put some static web resource. sing-box will then
|
|
||||||
serve it at `http://{{external-controller}}/ui`.
|
|
||||||
|
|
||||||
#### external_ui_download_url
|
|
||||||
|
|
||||||
ZIP download URL for the external UI, will be used if the specified `external_ui` directory is empty.
|
|
||||||
|
|
||||||
`https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip` will be used if empty.
|
|
||||||
|
|
||||||
#### external_ui_download_detour
|
|
||||||
|
|
||||||
The tag of the outbound to download the external UI.
|
|
||||||
|
|
||||||
Default outbound will be used if empty.
|
|
||||||
|
|
||||||
#### secret
|
|
||||||
|
|
||||||
Secret for the RESTful API (optional)
|
|
||||||
Authenticate by spedifying HTTP header `Authorization: Bearer ${secret}`
|
|
||||||
ALWAYS set a secret if RESTful API is listening on 0.0.0.0
|
|
||||||
|
|
||||||
#### default_mode
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
#### store_mode
|
|
||||||
|
|
||||||
Store Clash mode in cache file.
|
|
||||||
|
|
||||||
#### store_selected
|
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
The tag must be set for target outbounds.
|
|
||||||
|
|
||||||
Store selected outbound for the `Selector` outbound in cache file.
|
|
||||||
|
|
||||||
#### store_fakeip
|
|
||||||
|
|
||||||
Store fakeip in cache file.
|
|
||||||
|
|
||||||
#### cache_file
|
|
||||||
|
|
||||||
Cache file path, `cache.db` will be used if empty.
|
|
||||||
|
|
||||||
#### cache_id
|
|
||||||
|
|
||||||
Cache ID.
|
|
||||||
|
|
||||||
If not empty, `store_selected` will use a separate store keyed by it.
|
|
||||||
|
|
||||||
### V2Ray API Fields
|
|
||||||
|
|
||||||
!!! error ""
|
|
||||||
|
|
||||||
V2Ray API is not included by default, see [Installation](/#installation).
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
gRPC API listening address. V2Ray API will be disabled if empty.
|
|
||||||
|
|
||||||
#### stats
|
|
||||||
|
|
||||||
Traffic statistics service settings.
|
|
||||||
|
|
||||||
#### stats.enabled
|
|
||||||
|
|
||||||
Enable statistics service.
|
|
||||||
|
|
||||||
#### stats.inbounds
|
|
||||||
|
|
||||||
Inbound list to count traffic.
|
|
||||||
|
|
||||||
#### stats.outbounds
|
|
||||||
|
|
||||||
Outbound list to count traffic.
|
|
||||||
|
|
||||||
#### stats.users
|
|
||||||
|
|
||||||
User list to count traffic.
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
# 实验性
|
|
||||||
|
|
||||||
### 结构
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"experimental": {
|
|
||||||
"clash_api": {
|
|
||||||
"external_controller": "127.0.0.1:9090",
|
|
||||||
"external_ui": "",
|
|
||||||
"external_ui_download_url": "",
|
|
||||||
"external_ui_download_detour": "",
|
|
||||||
"secret": "",
|
|
||||||
"default_mode": "",
|
|
||||||
"store_mode": false,
|
|
||||||
"store_selected": false,
|
|
||||||
"store_fakeip": false,
|
|
||||||
"cache_file": "",
|
|
||||||
"cache_id": ""
|
|
||||||
},
|
|
||||||
"v2ray_api": {
|
|
||||||
"listen": "127.0.0.1:8080",
|
|
||||||
"stats": {
|
|
||||||
"enabled": true,
|
|
||||||
"inbounds": [
|
|
||||||
"socks-in"
|
|
||||||
],
|
|
||||||
"outbounds": [
|
|
||||||
"proxy",
|
|
||||||
"direct"
|
|
||||||
],
|
|
||||||
"users": [
|
|
||||||
"sekai"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
流量统计和连接管理会降低性能。
|
|
||||||
|
|
||||||
### Clash API 字段
|
|
||||||
|
|
||||||
!!! error ""
|
|
||||||
|
|
||||||
默认安装不包含 Clash API,参阅 [安装](/zh/#_2)。
|
|
||||||
|
|
||||||
#### external_controller
|
|
||||||
|
|
||||||
RESTful web API 监听地址。如果为空,则禁用 Clash API。
|
|
||||||
|
|
||||||
#### external_ui
|
|
||||||
|
|
||||||
到静态网页资源目录的相对路径或绝对路径。sing-box 会在 `http://{{external-controller}}/ui` 下提供它。
|
|
||||||
|
|
||||||
#### external_ui_download_url
|
|
||||||
|
|
||||||
静态网页资源的 ZIP 下载 URL,如果指定的 `external_ui` 目录为空,将使用。
|
|
||||||
|
|
||||||
默认使用 `https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip`。
|
|
||||||
|
|
||||||
#### external_ui_download_detour
|
|
||||||
|
|
||||||
用于下载静态网页资源的出站的标签。
|
|
||||||
|
|
||||||
如果为空,将使用默认出站。
|
|
||||||
|
|
||||||
#### secret
|
|
||||||
|
|
||||||
RESTful API 的密钥(可选)
|
|
||||||
通过指定 HTTP 标头 `Authorization: Bearer ${secret}` 进行身份验证
|
|
||||||
如果 RESTful API 正在监听 0.0.0.0,请始终设置一个密钥。
|
|
||||||
|
|
||||||
#### default_mode
|
|
||||||
|
|
||||||
Clash 中的默认模式,默认使用 `Rule`。
|
|
||||||
|
|
||||||
此设置没有直接影响,但可以通过 `clash_mode` 规则项在路由和 DNS 规则中使用。
|
|
||||||
|
|
||||||
#### store_mode
|
|
||||||
|
|
||||||
将 Clash 模式存储在缓存文件中。
|
|
||||||
|
|
||||||
#### store_selected
|
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
必须为目标出站设置标签。
|
|
||||||
|
|
||||||
将 `Selector` 中出站的选定的目标出站存储在缓存文件中。
|
|
||||||
|
|
||||||
#### store_fakeip
|
|
||||||
|
|
||||||
将 fakeip 存储在缓存文件中。
|
|
||||||
|
|
||||||
#### cache_file
|
|
||||||
|
|
||||||
缓存文件路径,默认使用`cache.db`。
|
|
||||||
|
|
||||||
#### cache_id
|
|
||||||
|
|
||||||
缓存 ID。
|
|
||||||
|
|
||||||
如果不为空,`store_selected` 将会使用以此为键的独立存储。
|
|
||||||
|
|
||||||
### V2Ray API 字段
|
|
||||||
|
|
||||||
!!! error ""
|
|
||||||
|
|
||||||
默认安装不包含 V2Ray API,参阅 [安装](/zh/#_2)。
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
gRPC API 监听地址。如果为空,则禁用 V2Ray API。
|
|
||||||
|
|
||||||
#### stats
|
|
||||||
|
|
||||||
流量统计服务设置。
|
|
||||||
|
|
||||||
#### stats.enabled
|
|
||||||
|
|
||||||
启用统计服务。
|
|
||||||
|
|
||||||
#### stats.inbounds
|
|
||||||
|
|
||||||
统计流量的入站列表。
|
|
||||||
|
|
||||||
#### stats.outbounds
|
|
||||||
|
|
||||||
统计流量的出站列表。
|
|
||||||
|
|
||||||
#### stats.users
|
|
||||||
|
|
||||||
统计流量的用户列表。
|
|
||||||
50
docs/configuration/experimental/v2ray-api.md
Normal file
50
docs/configuration/experimental/v2ray-api.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
### Structure
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
V2Ray API is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"listen": "127.0.0.1:8080",
|
||||||
|
"stats": {
|
||||||
|
"enabled": true,
|
||||||
|
"inbounds": [
|
||||||
|
"socks-in"
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
"proxy",
|
||||||
|
"direct"
|
||||||
|
],
|
||||||
|
"users": [
|
||||||
|
"sekai"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### listen
|
||||||
|
|
||||||
|
gRPC API listening address. V2Ray API will be disabled if empty.
|
||||||
|
|
||||||
|
#### stats
|
||||||
|
|
||||||
|
Traffic statistics service settings.
|
||||||
|
|
||||||
|
#### stats.enabled
|
||||||
|
|
||||||
|
Enable statistics service.
|
||||||
|
|
||||||
|
#### stats.inbounds
|
||||||
|
|
||||||
|
Inbound list to count traffic.
|
||||||
|
|
||||||
|
#### stats.outbounds
|
||||||
|
|
||||||
|
Outbound list to count traffic.
|
||||||
|
|
||||||
|
#### stats.users
|
||||||
|
|
||||||
|
User list to count traffic.
|
||||||
@@ -36,7 +36,7 @@ No authentication required if empty.
|
|||||||
|
|
||||||
#### set_system_proxy
|
#### set_system_proxy
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported on Linux, Android, Windows, and macOS.
|
Only supported on Linux, Android, Windows, and macOS.
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ HTTP 用户
|
|||||||
|
|
||||||
#### set_system_proxy
|
#### set_system_proxy
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
仅支持 Linux、Android、Windows 和 macOS。
|
仅支持 Linux、Android、Windows 和 macOS。
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
QUIC, which is required by hysteria is not included by default, see [Installation](/#installation).
|
QUIC, which is required by hysteria is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
### Listen Fields
|
### Listen Fields
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
{
|
{
|
||||||
"type": "hysteria2",
|
"type": "hysteria2",
|
||||||
"tag": "hy2-in",
|
"tag": "hy2-in",
|
||||||
|
...
|
||||||
... // Listen Fields
|
// Listen Fields
|
||||||
|
|
||||||
"up_mbps": 100,
|
"up_mbps": 100,
|
||||||
"down_mbps": 100,
|
"down_mbps": 100,
|
||||||
@@ -28,7 +28,14 @@
|
|||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
QUIC, which is required by Hysteria2 is not included by default, see [Installation](/#installation).
|
QUIC, which is required by Hysteria2 is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
|
!!! warning "Difference from official Hysteria2"
|
||||||
|
|
||||||
|
The official program supports an authentication method called **userpass**,
|
||||||
|
which essentially uses a combination of `<username>:<password>` as the actual password,
|
||||||
|
while sing-box does not provide this alias.
|
||||||
|
To use sing-box with the official program, you need to fill in that combination as the actual password.
|
||||||
|
|
||||||
### Listen Fields
|
### Listen Fields
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
{
|
{
|
||||||
"type": "hysteria2",
|
"type": "hysteria2",
|
||||||
"tag": "hy2-in",
|
"tag": "hy2-in",
|
||||||
|
...
|
||||||
... // 监听字段
|
// 监听字段
|
||||||
|
|
||||||
"up_mbps": 100,
|
"up_mbps": 100,
|
||||||
"down_mbps": 100,
|
"down_mbps": 100,
|
||||||
@@ -30,6 +30,12 @@
|
|||||||
|
|
||||||
默认安装不包含被 Hysteria2 依赖的 QUIC,参阅 [安装](/zh/#_2)。
|
默认安装不包含被 Hysteria2 依赖的 QUIC,参阅 [安装](/zh/#_2)。
|
||||||
|
|
||||||
|
!!! warning "与官方 Hysteria2 的区别"
|
||||||
|
|
||||||
|
官方程序支持一种名为 **userpass** 的验证方式,
|
||||||
|
本质上上是将用户名与密码的组合 `<username>:<password>` 作为实际上的密码,而 sing-box 不提供此别名。
|
||||||
|
要将 sing-box 与官方程序一起使用, 您需要填写该组合作为实际密码。
|
||||||
|
|
||||||
### 监听字段
|
### 监听字段
|
||||||
|
|
||||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||||
@@ -62,7 +68,7 @@ Hysteria 用户
|
|||||||
|
|
||||||
#### ignore_client_bandwidth
|
#### ignore_client_bandwidth
|
||||||
|
|
||||||
命令客户端使用 BBR 流量控制算法而不是 Hysteria CC。
|
命令客户端使用 BBR 拥塞控制算法而不是 Hysteria CC。
|
||||||
|
|
||||||
与 `up_mbps` 和 `down_mbps` 冲突。
|
与 `up_mbps` 和 `down_mbps` 冲突。
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ No authentication required if empty.
|
|||||||
|
|
||||||
#### set_system_proxy
|
#### set_system_proxy
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported on Linux, Android, Windows, and macOS.
|
Only supported on Linux, Android, Windows, and macOS.
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ SOCKS 和 HTTP 用户
|
|||||||
|
|
||||||
#### set_system_proxy
|
#### set_system_proxy
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
仅支持 Linux、Android、Windows 和 macOS。
|
仅支持 Linux、Android、Windows 和 macOS。
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
HTTP3 transport is not included by default, see [Installation](/#installation).
|
HTTP3 transport is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
### Listen Fields
|
### Listen Fields
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported on Linux and macOS.
|
Only supported on Linux and macOS.
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
仅支持 Linux 和 macOS。
|
仅支持 Linux 和 macOS。
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
... // Listen Fields
|
... // Listen Fields
|
||||||
|
|
||||||
"method": "2022-blake3-aes-128-gcm",
|
"method": "2022-blake3-aes-128-gcm",
|
||||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||||
|
"multiplex": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -23,7 +24,8 @@
|
|||||||
"name": "sekai",
|
"name": "sekai",
|
||||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"multiplex": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -41,7 +43,8 @@
|
|||||||
"server_port": 8080,
|
"server_port": 8080,
|
||||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"multiplex": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -82,3 +85,7 @@ Both if empty.
|
|||||||
| none | / |
|
| none | / |
|
||||||
| 2022 methods | `sing-box generate rand --base64 <Key Length>` |
|
| 2022 methods | `sing-box generate rand --base64 <Key Length>` |
|
||||||
| other methods | any string |
|
| other methods | any string |
|
||||||
|
|
||||||
|
#### multiplex
|
||||||
|
|
||||||
|
See [Multiplex](/configuration/shared/multiplex#inbound) for details.
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
... // 监听字段
|
... // 监听字段
|
||||||
|
|
||||||
"method": "2022-blake3-aes-128-gcm",
|
"method": "2022-blake3-aes-128-gcm",
|
||||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||||
|
"multiplex": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -23,7 +24,8 @@
|
|||||||
"name": "sekai",
|
"name": "sekai",
|
||||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"multiplex": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -41,7 +43,8 @@
|
|||||||
"server_port": 8080,
|
"server_port": 8080,
|
||||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"multiplex": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -82,3 +85,7 @@ See [Listen Fields](/configuration/shared/listen) for details.
|
|||||||
| none | / |
|
| none | / |
|
||||||
| 2022 methods | `sing-box generate rand --base64 <密钥长度>` |
|
| 2022 methods | `sing-box generate rand --base64 <密钥长度>` |
|
||||||
| other methods | 任意字符串 |
|
| other methods | 任意字符串 |
|
||||||
|
|
||||||
|
#### multiplex
|
||||||
|
|
||||||
|
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported on Linux.
|
Only supported on Linux.
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
仅支持 Linux。
|
仅支持 Linux。
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"server_port": 8081
|
"server_port": 8081
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"multiplex": {},
|
||||||
"transport": {}
|
"transport": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -46,7 +47,7 @@ TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
|||||||
|
|
||||||
#### fallback
|
#### fallback
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
@@ -58,6 +59,10 @@ Fallback server configuration for specified ALPN.
|
|||||||
|
|
||||||
If not empty, TLS fallback requests with ALPN not in this table will be rejected.
|
If not empty, TLS fallback requests with ALPN not in this table will be rejected.
|
||||||
|
|
||||||
|
#### multiplex
|
||||||
|
|
||||||
|
See [Multiplex](/configuration/shared/multiplex#inbound) for details.
|
||||||
|
|
||||||
#### transport
|
#### transport
|
||||||
|
|
||||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"server_port": 8081
|
"server_port": 8081
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"multiplex": {},
|
||||||
"transport": {}
|
"transport": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -48,7 +49,7 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
|||||||
|
|
||||||
#### fallback
|
#### fallback
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
没有证据表明 GFW 基于 HTTP 响应检测并阻止 Trojan 服务器,并且在服务器上打开标准 http/s 端口是一个更大的特征。
|
没有证据表明 GFW 基于 HTTP 响应检测并阻止 Trojan 服务器,并且在服务器上打开标准 http/s 端口是一个更大的特征。
|
||||||
|
|
||||||
@@ -60,6 +61,10 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
|||||||
|
|
||||||
如果不为空,ALPN 不在此列表中的 TLS 回退请求将被拒绝。
|
如果不为空,ALPN 不在此列表中的 TLS 回退请求将被拒绝。
|
||||||
|
|
||||||
|
#### multiplex
|
||||||
|
|
||||||
|
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
|
||||||
|
|
||||||
#### transport
|
#### transport
|
||||||
|
|
||||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
QUIC, which is required by TUIC is not included by default, see [Installation](/#installation).
|
QUIC, which is required by TUIC is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
### Listen Fields
|
### Listen Fields
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ TUIC 用户密码
|
|||||||
|
|
||||||
#### congestion_control
|
#### congestion_control
|
||||||
|
|
||||||
QUIC 流量控制算法
|
QUIC 拥塞控制算法
|
||||||
|
|
||||||
可选值: `cubic`, `new_reno`, `bbr`
|
可选值: `cubic`, `new_reno`, `bbr`
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported on Linux, Windows and macOS.
|
Only supported on Linux, Windows and macOS.
|
||||||
|
|
||||||
@@ -22,6 +22,12 @@
|
|||||||
"::/1",
|
"::/1",
|
||||||
"8000::/1"
|
"8000::/1"
|
||||||
],
|
],
|
||||||
|
"inet4_route_exclude_address": [
|
||||||
|
"192.168.0.0/16"
|
||||||
|
],
|
||||||
|
"inet6_route_exclude_address": [
|
||||||
|
"fc00::/7"
|
||||||
|
],
|
||||||
"endpoint_independent_nat": false,
|
"endpoint_independent_nat": false,
|
||||||
"stack": "system",
|
"stack": "system",
|
||||||
"include_interface": [
|
"include_interface": [
|
||||||
@@ -96,7 +102,7 @@ The maximum transmission unit.
|
|||||||
|
|
||||||
Set the default route to the Tun.
|
Set the default route to the Tun.
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
To avoid traffic loopback, set `route.auto_detect_interface` or `route.default_interface` or `outbound.bind_interface`
|
To avoid traffic loopback, set `route.auto_detect_interface` or `route.default_interface` or `outbound.bind_interface`
|
||||||
|
|
||||||
@@ -130,6 +136,14 @@ Use custom routes instead of default when `auto_route` is enabled.
|
|||||||
|
|
||||||
Use custom routes instead of default when `auto_route` is enabled.
|
Use custom routes instead of default when `auto_route` is enabled.
|
||||||
|
|
||||||
|
#### inet4_route_exclude_address
|
||||||
|
|
||||||
|
Exclude custom routes when `auto_route` is enabled.
|
||||||
|
|
||||||
|
#### inet6_route_exclude_address
|
||||||
|
|
||||||
|
Exclude custom routes when `auto_route` is enabled.
|
||||||
|
|
||||||
#### endpoint_independent_nat
|
#### endpoint_independent_nat
|
||||||
|
|
||||||
!!! info ""
|
!!! info ""
|
||||||
@@ -157,11 +171,11 @@ TCP/IP stack.
|
|||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
gVisor and LWIP stacks is not included by default, see [Installation](/#installation).
|
gVisor and LWIP stacks is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
#### include_interface
|
#### include_interface
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
Interface rules are only supported on Linux and require auto_route.
|
Interface rules are only supported on Linux and require auto_route.
|
||||||
|
|
||||||
@@ -177,7 +191,7 @@ Conflict with `include_interface`.
|
|||||||
|
|
||||||
#### include_uid
|
#### include_uid
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
UID rules are only supported on Linux and require auto_route.
|
UID rules are only supported on Linux and require auto_route.
|
||||||
|
|
||||||
@@ -197,7 +211,7 @@ Exclude users in route, but in range.
|
|||||||
|
|
||||||
#### include_android_user
|
#### include_android_user
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
Android user and package rules are only supported on Android and require auto_route.
|
Android user and package rules are only supported on Android and require auto_route.
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
仅支持 Linux、Windows 和 macOS。
|
仅支持 Linux、Windows 和 macOS。
|
||||||
|
|
||||||
@@ -22,6 +22,12 @@
|
|||||||
"::/1",
|
"::/1",
|
||||||
"8000::/1"
|
"8000::/1"
|
||||||
],
|
],
|
||||||
|
"inet4_route_exclude_address": [
|
||||||
|
"192.168.0.0/16"
|
||||||
|
],
|
||||||
|
"inet6_route_exclude_address": [
|
||||||
|
"fc00::/7"
|
||||||
|
],
|
||||||
"endpoint_independent_nat": false,
|
"endpoint_independent_nat": false,
|
||||||
"stack": "system",
|
"stack": "system",
|
||||||
"include_interface": [
|
"include_interface": [
|
||||||
@@ -96,7 +102,7 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
设置到 Tun 的默认路由。
|
设置到 Tun 的默认路由。
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
为避免流量环回,请设置 `route.auto_detect_interface` 或 `route.default_interface` 或 `outbound.bind_interface`。
|
为避免流量环回,请设置 `route.auto_detect_interface` 或 `route.default_interface` 或 `outbound.bind_interface`。
|
||||||
|
|
||||||
@@ -131,6 +137,14 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
||||||
|
|
||||||
|
#### inet4_route_exclude_address
|
||||||
|
|
||||||
|
启用 `auto_route` 时排除自定义路由。
|
||||||
|
|
||||||
|
#### inet6_route_exclude_address
|
||||||
|
|
||||||
|
启用 `auto_route` 时排除自定义路由。
|
||||||
|
|
||||||
#### endpoint_independent_nat
|
#### endpoint_independent_nat
|
||||||
|
|
||||||
启用独立于端点的 NAT。
|
启用独立于端点的 NAT。
|
||||||
@@ -157,7 +171,7 @@ TCP/IP 栈。
|
|||||||
|
|
||||||
#### include_interface
|
#### include_interface
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
接口规则仅在 Linux 下被支持,并且需要 `auto_route`。
|
接口规则仅在 Linux 下被支持,并且需要 `auto_route`。
|
||||||
|
|
||||||
@@ -173,7 +187,7 @@ TCP/IP 栈。
|
|||||||
|
|
||||||
#### include_uid
|
#### include_uid
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
UID 规则仅在 Linux 下被支持,并且需要 `auto_route`。
|
UID 规则仅在 Linux 下被支持,并且需要 `auto_route`。
|
||||||
|
|
||||||
@@ -193,7 +207,7 @@ TCP/IP 栈。
|
|||||||
|
|
||||||
#### include_android_user
|
#### include_android_user
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
Android 用户和应用规则仅在 Android 下被支持,并且需要 `auto_route`。
|
Android 用户和应用规则仅在 Android 下被支持,并且需要 `auto_route`。
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tls": {},
|
"tls": {},
|
||||||
|
"multiplex": {},
|
||||||
"transport": {}
|
"transport": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -49,6 +50,10 @@ Available values:
|
|||||||
|
|
||||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||||
|
|
||||||
|
#### multiplex
|
||||||
|
|
||||||
|
See [Multiplex](/configuration/shared/multiplex#inbound) for details.
|
||||||
|
|
||||||
#### transport
|
#### transport
|
||||||
|
|
||||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tls": {},
|
"tls": {},
|
||||||
|
"multiplex": {},
|
||||||
"transport": {}
|
"transport": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -49,6 +50,10 @@ VLESS 子协议。
|
|||||||
|
|
||||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||||
|
|
||||||
|
#### multiplex
|
||||||
|
|
||||||
|
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
|
||||||
|
|
||||||
#### transport
|
#### transport
|
||||||
|
|
||||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tls": {},
|
"tls": {},
|
||||||
|
"multiplex": {},
|
||||||
"transport": {}
|
"transport": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -44,6 +45,10 @@ VMess users.
|
|||||||
|
|
||||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||||
|
|
||||||
|
#### multiplex
|
||||||
|
|
||||||
|
See [Multiplex](/configuration/shared/multiplex#inbound) for details.
|
||||||
|
|
||||||
#### transport
|
#### transport
|
||||||
|
|
||||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tls": {},
|
"tls": {},
|
||||||
|
"multiplex": {},
|
||||||
"transport": {}
|
"transport": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -44,6 +45,10 @@ VMess 用户。
|
|||||||
|
|
||||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||||
|
|
||||||
|
#### multiplex
|
||||||
|
|
||||||
|
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
|
||||||
|
|
||||||
#### transport
|
#### transport
|
||||||
|
|
||||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
QUIC, which is required by hysteria is not included by default, see [Installation](/#installation).
|
QUIC, which is required by hysteria is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,15 @@
|
|||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
QUIC, which is required by Hysteria2 is not included by default, see [Installation](/#installation).
|
QUIC, which is required by Hysteria2 is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
|
!!! warning "Difference from official Hysteria2"
|
||||||
|
|
||||||
|
The official Hysteria2 supports an authentication method called **userpass**,
|
||||||
|
which essentially uses a combination of `<username>:<password>` as the actual password,
|
||||||
|
while sing-box does not provide this alias.
|
||||||
|
If you are planning to use sing-box with the official program,
|
||||||
|
please note that you will need to fill the combination as the password.
|
||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,12 @@
|
|||||||
|
|
||||||
默认安装不包含被 Hysteria2 依赖的 QUIC,参阅 [安装](/zh/#_2)。
|
默认安装不包含被 Hysteria2 依赖的 QUIC,参阅 [安装](/zh/#_2)。
|
||||||
|
|
||||||
|
!!! warning "与官方 Hysteria2 的区别"
|
||||||
|
|
||||||
|
官方程序支持一种名为 **userpass** 的验证方式,
|
||||||
|
本质上上是将用户名与密码的组合 `<username>:<password>` 作为实际上的密码,而 sing-box 不提供此别名。
|
||||||
|
要将 sing-box 与官方程序一起使用, 您需要填写该组合作为实际密码。
|
||||||
|
|
||||||
### 字段
|
### 字段
|
||||||
|
|
||||||
#### server
|
#### server
|
||||||
@@ -44,7 +50,7 @@
|
|||||||
|
|
||||||
最大带宽。
|
最大带宽。
|
||||||
|
|
||||||
如果为空,将使用 BBR 流量控制算法而不是 Hysteria CC。
|
如果为空,将使用 BBR 拥塞控制算法而不是 Hysteria CC。
|
||||||
|
|
||||||
#### obfs.type
|
#### obfs.type
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
The selector can only be controlled through the [Clash API](/configuration/experimental#clash-api-fields) currently.
|
The selector can only be controlled through the [Clash API](/configuration/experimental#clash-api-fields) currently.
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! error ""
|
!!! quote ""
|
||||||
|
|
||||||
选择器目前只能通过 [Clash API](/zh/configuration/experimental#clash-api) 来控制。
|
选择器目前只能通过 [Clash API](/zh/configuration/experimental#clash-api) 来控制。
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ Conflict with `multiplex`.
|
|||||||
|
|
||||||
#### multiplex
|
#### multiplex
|
||||||
|
|
||||||
Multiplex configuration, see [Multiplex](/configuration/shared/multiplex).
|
See [Multiplex](/configuration/shared/multiplex#outbound) for details.
|
||||||
|
|
||||||
### Dial Fields
|
### Dial Fields
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ UDP over TCP 配置。
|
|||||||
|
|
||||||
#### multiplex
|
#### multiplex
|
||||||
|
|
||||||
多路复用配置, 参阅 [多路复用](/zh/configuration/shared/multiplex)。
|
参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。
|
||||||
|
|
||||||
### 拨号字段
|
### 拨号字段
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
ShadowsocksR is not included by default, see [Installation](/#installation).
|
ShadowsocksR is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
!!! info ""
|
!!! info ""
|
||||||
|
|
||||||
Embedded tor is not included by default, see [Installation](/#installation).
|
Embedded tor is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
|||||||
|
|
||||||
#### multiplex
|
#### multiplex
|
||||||
|
|
||||||
Multiplex configuration, see [Multiplex](/configuration/shared/multiplex).
|
See [Multiplex](/configuration/shared/multiplex#outbound) for details.
|
||||||
|
|
||||||
#### transport
|
#### transport
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
|||||||
|
|
||||||
#### multiplex
|
#### multiplex
|
||||||
|
|
||||||
多路复用配置, 参阅 [多路复用](/zh/configuration/shared/multiplex)。
|
参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。
|
||||||
|
|
||||||
#### transport
|
#### transport
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
QUIC, which is required by TUIC is not included by default, see [Installation](/#installation).
|
QUIC, which is required by TUIC is not included by default, see [Installation](./#installation).
|
||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ TUIC 用户密码
|
|||||||
|
|
||||||
#### congestion_control
|
#### congestion_control
|
||||||
|
|
||||||
QUIC 流量控制算法
|
QUIC 拥塞控制算法
|
||||||
|
|
||||||
可选值: `cubic`, `new_reno`, `bbr`
|
可选值: `cubic`, `new_reno`, `bbr`
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"network": "tcp",
|
"network": "tcp",
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"packet_encoding": "",
|
"packet_encoding": "",
|
||||||
|
"multiplex": {},
|
||||||
"transport": {},
|
"transport": {},
|
||||||
|
|
||||||
... // Dial Fields
|
... // Dial Fields
|
||||||
@@ -68,6 +69,10 @@ UDP packet encoding, xudp is used by default.
|
|||||||
| packetaddr | Supported by v2ray 5+ |
|
| packetaddr | Supported by v2ray 5+ |
|
||||||
| xudp | Supported by xray |
|
| xudp | Supported by xray |
|
||||||
|
|
||||||
|
#### multiplex
|
||||||
|
|
||||||
|
See [Multiplex](/configuration/shared/multiplex#outbound) for details.
|
||||||
|
|
||||||
#### transport
|
#### transport
|
||||||
|
|
||||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user