Compare commits

..

73 Commits

Author SHA1 Message Date
世界
0a749723e0 documentation: Bump version 2023-11-30 17:30:18 +08:00
世界
d505895447 documentation: Update rule-set example 2023-11-30 17:23:53 +08:00
世界
9cbbb16145 Independent source_ip_is_private and ip_is_private rules 2023-11-30 17:23:53 +08:00
世界
cbe2848d8f Make rule-set initialization parallel 2023-11-30 17:23:53 +08:00
世界
bbc1d12015 documentation: Bump version 2023-11-30 10:31:18 +08:00
世界
249e501754 documentation: Add rule set 2023-11-30 10:30:58 +08:00
世界
4e4c0820d5 Add rule set 2023-11-30 10:30:58 +08:00
世界
33881ebd8c Allow nested logical rules 2023-11-30 10:30:57 +08:00
世界
4d6b7ff054 Migrate to independent cache file 2023-11-30 10:30:57 +08:00
世界
5f1e39a42c documentation: Bump version 2023-11-29 21:01:28 +08:00
世界
4f7770e254 Update dependencies 2023-11-29 21:01:19 +08:00
世界
e8c4c942c0 documentation: Bump version & Refactor docs 2023-11-28 11:21:57 +08:00
世界
253976d6c0 Add wifi_ssid and wifi_bssid route and DNS rules 2023-11-28 11:21:44 +08:00
世界
f0571b4122 Update quic-go to v0.40.0 2023-11-28 11:21:44 +08:00
世界
1b71e52e90 Migrate multiplex and UoT server to inbound & Add tcp-brutal support for multiplex 2023-11-28 11:21:44 +08:00
世界
6d24be23da Add support for v2ray http upgrade transport 2023-11-28 11:21:44 +08:00
世界
2a45c178fa Add exclude route support for tun &
Update gVisor to 20231113.0
2023-11-28 11:21:43 +08:00
世界
81e214812f Add udp_disable_domain_unmapping inbound listen option 2023-11-28 11:21:43 +08:00
世界
4d23773a25 Migrate to gobwas/ws 2023-11-28 11:21:43 +08:00
世界
40a0b69918 Fix dhcp reset 2023-11-28 11:20:48 +08:00
世界
a7b37c5953 documentation: Bump version 2023-11-24 20:58:48 +08:00
世界
03663a5093 Fix cachefile permission 2023-11-24 20:58:48 +08:00
世界
b08226a850 Fix "Fix HTTP server leak" 2023-11-24 19:58:31 +08:00
世界
edbae5dc4d Fix missing UDP user context on TUIC/Hysteria2 inbounds 2023-11-24 19:56:43 +08:00
世界
0f8ad0234b Remove unused code 2023-11-24 19:55:53 +08:00
世界
661eadc3bd documentation: Bump version 2023-11-21 10:22:44 +08:00
世界
50c1290567 Update dependencies 2023-11-21 10:22:44 +08:00
世界
eaccc9759a Fix platform API check 2023-11-21 10:22:44 +08:00
世界
925214869b Add test for ss2022 EIH 2023-11-20 18:36:44 +08:00
世界
6a2bfd26d0 Fix QUIC sniffer 2023-11-16 22:48:16 +08:00
世界
72a81afb76 Fix "Fix Linux IPv6 auto route rules" 2023-11-16 18:25:07 +08:00
世界
240abe204c Fix zero TTL was incorrectly reset 2023-11-16 18:25:07 +08:00
世界
7c49196792 build: Fix bad environment key 2023-11-16 01:42:24 +08:00
嫦悅
3a2808cff6 documentation: Fix typo
The old meaning is wrong. Correct the meaning according to the English documentation and the actual effect of the option.

Signed-off-by: 嫦悅 <lomombwlo@gmail.com>
2023-11-16 01:12:26 +08:00
guangwu
005d6cf4cf chore: unnecessary use of fmt.Sprintf 2023-11-16 01:10:52 +08:00
世界
36dff630d6 documentation: Bump version 2023-11-15 14:10:37 +08:00
世界
1825869124 platform: Refactor log interface 2023-11-15 14:10:37 +08:00
世界
3cadc90375 Fix TUIC authentication failed error message 2023-11-14 20:15:41 +08:00
renovate[bot]
2c6967d7f9 dependencies: Update actions/checkout digest to b4ffde6
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-14 20:15:41 +08:00
世界
fe866b123a Fix sing-tun version 2023-11-14 20:15:41 +08:00
世界
cbef1b1e59 Remove apk build due to missing openrc configuration 2023-11-14 20:15:41 +08:00
世界
e21f84932c build: Fix missing linux/386 2023-11-14 10:35:27 +08:00
0x7d274284
7a679bc328 Fix Dockerfile
Keep the .git folder at compile time to get the correct version

Signed-off-by: 0x7d274284 <112329548+0x7d274284@users.noreply.github.com>
2023-11-14 10:34:38 +08:00
世界
6635dd9abc documentation: Bump version 2023-11-13 21:50:36 +08:00
世界
ce164724ea build: Add apk and archlinux package builds 2023-11-13 15:28:28 +08:00
世界
a3ef7a7d88 Fix trojan-go mux context
Co-authored-by: maskedeken <maskedeken@yahoo.com>
Co-authored-by: 世界 <i@sekai.icu>
2023-11-13 14:12:35 +08:00
世界
71218ef0d3 build: Unify build tags 2023-11-13 14:12:35 +08:00
世界
e777b4c6dc Fix not closing outConn
Co-authored-by: Mahdi-zarei  <mahdi.zrei@gmail.com>
Co-authored-by: 世界 <i@sekai.icu>
2023-11-13 13:54:03 +08:00
世界
6815f94180 build: Update Go to 1.20.11 for legacy builds 2023-11-13 13:47:15 +08:00
世界
b013acd89d tun: Fix broadcast filter not applied to mixed stack 2023-11-13 13:35:10 +08:00
世界
f7c2eb6e76 Fix v2ray ws crash 2023-11-13 13:34:31 +08:00
世界
3ef9b1b343 Update protobuf generated binary 2023-11-10 10:56:25 +08:00
SakuraWald
2224c68959 Fix issue template 2023-11-10 10:36:46 +08:00
Kumiko as a Service
bb7d03d1db Use golang's cross-compilation capabilities 2023-11-10 10:36:32 +08:00
世界
50036924e8 documentation: Bump version 2023-11-10 10:24:18 +08:00
世界
c2c3f7284f Revert "Fix Host ignored in v2ray websocket transport"
This reverts commit aaa6702863.
2023-11-09 16:55:47 +08:00
世界
f6fee53676 Fix mux client close 2023-11-09 16:55:47 +08:00
世界
63b8e8ed23 platform: Increase HTTP timeout to 15s 2023-11-09 16:55:47 +08:00
世界
6ae86eda98 build: Update gradle command 2023-11-09 16:55:47 +08:00
世界
267d9617b7 build: Fix tag calculate 2023-11-07 22:27:37 +08:00
世界
0a06ccae50 platform: Fix legacy code 2023-11-07 22:14:23 +08:00
世界
8de0fad9f5 documentation: Bump version 2023-11-07 10:20:05 +08:00
世界
e05bf6308e Fix build script 2023-11-07 10:20:05 +08:00
世界
a20a0cb455 Add broadcast filter 2023-11-07 10:20:05 +08:00
世界
d29f7475d2 documentation: Bump version 2023-11-06 19:37:45 +08:00
世界
aaa6702863 Fix Host ignored in v2ray websocket transport 2023-11-05 23:27:11 +08:00
世界
bb928f096a Fix missing default next proto in hysteria2 2023-11-05 23:11:40 +08:00
johnthecoderpro
9f01d5c5b4 Fix download geo resources 2023-11-05 16:03:15 +08:00
世界
11629a931b Update release script 2023-11-05 16:02:18 +08:00
世界
126f825241 Update dependencies 2023-11-05 16:02:10 +08:00
世界
998cc7bd22 Add multicast filter for tun 2023-11-04 08:04:17 +08:00
世界
3efccaa8f5 Update dependencies 2023-10-31 18:24:58 +08:00
世界
d57b35ec30 documentation: Add privacy policy for android 2023-10-31 17:32:18 +08:00
286 changed files with 8585 additions and 3115 deletions

View File

@@ -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.
value: |-
<details>
```console
# 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.
value: |-
<details>
```console
# Replace this line with logs
```

View File

@@ -46,6 +46,7 @@ body:
description: 如果您使用原始命令行程序,请提供 `sing-box version` 命令的输出。
value: |-
<details>
```console
# 使用输出内容覆盖此行
```
@@ -71,6 +72,7 @@ body:
对于 Android 图形客户端程序,请检查 `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` 文件以导出崩溃日志。
value: |-
<details>
```console
# 使用日志内容覆盖此行
```

View File

@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
fetch-depth: 0
- name: Get latest go version
@@ -50,7 +50,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
fetch-depth: 0
- name: Setup Go
@@ -70,7 +70,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
fetch-depth: 0
- name: Setup Go
@@ -201,7 +201,7 @@ jobs:
TAGS: with_clash_api,with_quic
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
fetch-depth: 0
- name: Get latest go version

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Setup QEMU for Docker Buildx
@@ -39,6 +39,8 @@ jobs:
with:
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
target: dist
build-args: |
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
tags: |
${{ steps.tag.outputs.latest }}
${{ steps.tag.outputs.versioned }}

View File

@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
fetch-depth: 0
- name: Get latest go version

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
/.idea/
/vendor/
/*.json
/*.srs
/*.db
/site/
/bin/

View File

@@ -19,10 +19,12 @@ builds:
- with_ech
- with_utls
- with_reality_server
- with_acme
- with_clash_api
env:
- CGO_ENABLED=0
targets:
- linux_386
- linux_amd64_v1
- linux_amd64_v3
- linux_arm64
@@ -55,11 +57,12 @@ builds:
- with_ech
- with_utls
- with_reality_server
- with_acme
- with_clash_api
env:
- CGO_ENABLED=0
- GOROOT=/nix/store/5h8gjl89zx8qxgc572wa3k81zplv8v4z-go-1.20.10/share/go
gobinary: /nix/store/5h8gjl89zx8qxgc572wa3k81zplv8v4z-go-1.20.10/bin/go
- GOROOT=/nix/store/kg6i737jjqs923jcijnm003h68c1dghj-go-1.20.11/share/go
gobinary: /nix/store/kg6i737jjqs923jcijnm003h68c1dghj-go-1.20.11/bin/go
targets:
- windows_amd64_v1
- windows_386
@@ -83,6 +86,8 @@ builds:
- with_wireguard
- with_ech
- with_utls
- with_reality_server
- with_acme
- with_clash_api
env:
- CGO_ENABLED=1
@@ -153,6 +158,7 @@ nfpms:
formats:
- deb
- rpm
- archlinux
priority: extra
contents:
- src: release/config/config.json

View File

@@ -1,23 +1,27 @@
FROM golang:1.21-alpine AS builder
FROM --platform=$BUILDPLATFORM golang:1.21-alpine AS builder
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
COPY . /go/src/github.com/sagernet/sing-box
WORKDIR /go/src/github.com/sagernet/sing-box
ARG TARGETOS TARGETARCH
ARG GOPROXY=""
ENV GOPROXY ${GOPROXY}
ENV CGO_ENABLED=0
ENV GOOS=$TARGETOS
ENV GOARCH=$TARGETARCH
RUN set -ex \
&& apk add git build-base \
&& export COMMIT=$(git rev-parse --short HEAD) \
&& 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 \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
./cmd/sing-box
FROM alpine AS dist
FROM --platform=$TARGETPLATFORM alpine AS dist
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
RUN set -ex \
&& apk upgrade \
&& apk add bash tzdata ca-certificates \
&& rm -rf /var/cache/apk/*
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
ENTRYPOINT ["sing-box"]
ENTRYPOINT ["sing-box"]

View File

@@ -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_GO120 = with_quic,with_ech
TAGS ?= $(TAGS_GO118),$(TAGS_GO120)
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
GOHOSTOS = $(shell go env GOHOSTOS)
GOHOSTARCH = $(shell go env GOHOSTARCH)
@@ -14,7 +14,7 @@ MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
MAIN = ./cmd/sing-box
PREFIX ?= $(shell go env GOPATH)
.PHONY: test release
.PHONY: test release docs
build:
go build $(MAIN_PARAMS) $(MAIN)
@@ -61,7 +61,7 @@ proto_install:
release:
go run ./cmd/internal/build goreleaser release --clean --skip-publish || exit 1
mkdir dist/release
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/*.pkg.tar.zst dist/release
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release
rm -r dist/release
@@ -73,18 +73,21 @@ update_android_version:
go run ./cmd/internal/update_android_version
build_android:
cd ../sing-box-for-android && ./gradlew :app:assembleRelease && ./gradlew --stop
cd ../sing-box-for-android && ./gradlew :app:assemblePlayRelease && ./gradlew --stop
upload_android:
mkdir -p dist/release_android
cp ../sing-box-for-android/app/build/outputs/apk/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
rm -rf dist/release_android
release_android: lib_android update_android_version build_android upload_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:
cd ../sing-box-for-apple && \
@@ -149,10 +152,8 @@ 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
rm -rf dist
release_apple_beta: update_apple_version release_ios release_macos release_tvos
rm -rf dist
test:
@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/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:
rm -rf bin dist sing-box
rm -f $(shell go env GOPATH)/sing-box

104
adapter/conn_router.go Normal file
View 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)
}

View File

@@ -1,11 +1,16 @@
package adapter
import (
"bytes"
"context"
"encoding/binary"
"io"
"net"
"time"
"github.com/sagernet/sing-box/common/urltest"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/rw"
)
type ClashServer interface {
@@ -13,22 +18,83 @@ type ClashServer interface {
PreStarter
Mode() string
ModeList() []string
StoreSelected() bool
StoreFakeIP() bool
CacheFile() ClashCacheFile
HistoryStorage() *urltest.HistoryStorage
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)
}
type ClashCacheFile interface {
type CacheFile interface {
Service
PreStarter
StoreFakeIP() bool
FakeIPStorage
LoadMode() string
StoreMode(mode string) error
LoadSelected(group string) string
StoreSelected(group string, selected string) error
LoadGroupExpand(group string) (isExpand bool, loaded bool)
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 {

View File

@@ -47,6 +47,7 @@ type InboundContext struct {
GeoIPCode string
ProcessInfo *process.Info
FakeIP bool
IPCIDRMatchSource bool
// dns cache

View File

@@ -2,7 +2,7 @@ package adapter
import (
"context"
"net"
"net/http"
"net/netip"
"github.com/sagernet/sing-box/common/geoip"
@@ -20,16 +20,17 @@ type Router interface {
Outbounds() []Outbound
Outbound(tag string) (Outbound, bool)
DefaultOutbound(network string) Outbound
DefaultOutbound(network string) (Outbound, error)
FakeIPStore() FakeIPStore
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
ConnectionRouter
GeoIPReader() *geoip.Reader
LoadGeosite(code string) (Rule, error)
RuleSet(tag string) (RuleSet, bool)
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
@@ -44,6 +45,7 @@ type Router interface {
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager
WIFIState() WIFIState
Rules() []Rule
ClashServer() ClashServer
@@ -63,11 +65,15 @@ func RouterFromContext(ctx context.Context) Router {
return service.FromContext[Router](ctx)
}
type HeadlessRule interface {
Match(metadata *InboundContext) bool
}
type Rule interface {
HeadlessRule
Service
Type() string
UpdateGeosite() error
Match(metadata *InboundContext) bool
Outbound() string
String() string
}
@@ -78,6 +84,22 @@ type DNSRule interface {
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 {
InterfaceUpdated()
}
type WIFIState struct {
SSID string
BSSID string
}

58
box.go
View File

@@ -10,6 +10,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"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/inbound"
"github.com/sagernet/sing-box/log"
@@ -32,7 +33,8 @@ type Box struct {
outbounds []adapter.Outbound
logFactory log.Factory
logger log.ContextLogger
preServices map[string]adapter.Service
preServices1 map[string]adapter.Service
preServices2 map[string]adapter.Service
postServices map[string]adapter.Service
done chan struct{}
}
@@ -41,21 +43,26 @@ type Options struct {
option.Options
Context context.Context
PlatformInterface platform.Interface
PlatformLogWriter log.PlatformWriter
}
func New(options Options) (*Box, error) {
createdAt := time.Now()
ctx := options.Context
if ctx == nil {
ctx = context.Background()
}
ctx = service.ContextWithDefaultRegistry(ctx)
ctx = pause.ContextWithDefaultManager(ctx)
createdAt := time.Now()
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
var needCacheFile bool
var needClashAPI 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
}
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
@@ -71,7 +78,7 @@ func New(options Options) (*Box, error) {
Observable: needClashAPI,
DefaultWriter: defaultLogWriter,
BaseTime: createdAt,
PlatformWriter: options.PlatformInterface,
PlatformWriter: options.PlatformLogWriter,
})
if err != nil {
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")
}
}
preServices := make(map[string]adapter.Service)
preServices1 := make(map[string]adapter.Service)
preServices2 := 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 {
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
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")
}
router.SetClashServer(clashServer)
preServices["clash api"] = clashServer
preServices2["clash api"] = clashServer
}
if needV2RayAPI {
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")
}
router.SetV2RayServer(v2rayServer)
preServices["v2ray api"] = v2rayServer
preServices2["v2ray api"] = v2rayServer
}
return &Box{
router: router,
@@ -171,7 +184,8 @@ func New(options Options) (*Box, error) {
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
preServices: preServices,
preServices1: preServices1,
preServices2: preServices2,
postServices: postServices,
done: make(chan struct{}),
}, nil
@@ -216,7 +230,16 @@ func (s *Box) Start() 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 {
s.logger.Trace("pre-start ", serviceName)
err := preService.PreStart()
@@ -237,7 +260,14 @@ func (s *Box) start() error {
if err != nil {
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)
err = service.Start()
if err != nil {
@@ -312,7 +342,13 @@ func (s *Box) Close() error {
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)
errors = E.Append(errors, service.Close(), func(err error) error {
return E.Cause(err, "close ", serviceName)

View File

@@ -12,7 +12,7 @@ import (
func main() {
build_shared.FindSDK()
if os.Getenv("build.Default.GOPATH") == "" {
if os.Getenv("GOPATH") == "" {
os.Setenv("GOPATH", build.Default.GOPATH)
}

View File

@@ -17,9 +17,6 @@ func ReadTag() (string, error) {
}
shortCommit, _ := shell.Exec("git", "rev-parse", "--short", "HEAD").ReadOutput()
version := badversion.Parse(currentTagRev[1:])
if version.PreReleaseIdentifier == "" {
version.Patch++
}
return version.String() + "-" + shortCommit, nil
}

View File

@@ -7,7 +7,6 @@ import (
"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"
@@ -69,41 +68,3 @@ func format() error {
}
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
View 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
}

View 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)
}

View 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
}

View 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
}

View 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
}

View 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)
}

View 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
}

View 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
}

View 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 ""
}

View File

@@ -18,7 +18,7 @@ import (
)
var commandMerge = &cobra.Command{
Use: "merge [output]",
Use: "merge <output>",
Short: "Merge configurations",
Run: func(cmd *cobra.Command, args []string) {
err := merge(args[0])

View 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)
}

View 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
}

View 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
}

View File

@@ -38,11 +38,7 @@ func createPreStartedClient() (*box.Box, error) {
func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) {
if outboundTag == "" {
outbound := instance.Router().DefaultOutbound(N.NetworkName(network))
if outbound == nil {
return nil, E.New("missing default outbound")
}
return outbound, nil
return instance.Router().DefaultOutbound(N.NetworkName(network))
} else {
outbound, loaded := instance.Router().Outbound(outboundTag)
if !loaded {

View File

@@ -18,7 +18,7 @@ import (
var commandConnectFlagNetwork string
var commandConnect = &cobra.Command{
Use: "connect [address]",
Use: "connect <address>",
Short: "Connect to an address",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {

View File

@@ -1,7 +1,6 @@
package main
import (
"github.com/getsentry/sentry-go"
"os"
"time"
@@ -16,7 +15,6 @@ var (
configDirectories []string
workingDir string
disableColor bool
enableDebug bool
)
var mainCommand = &cobra.Command{
@@ -29,25 +27,12 @@ func init() {
mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path")
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
mainCommand.PersistentFlags().BoolVarP(&enableDebug, "debug", "", false, "enable sentry debug mode")
}
func main() {
if err := mainCommand.Execute(); err != nil {
log.Fatal(err)
}
if enableDebug {
err := sentry.Init(sentry.ClientOptions{
Dsn: "",
})
if err != nil {
log.Fatal("sentry.Init: %s", err)
}
defer sentry.Flush(2 * time.Second)
}
}
func preRun(cmd *cobra.Command, args []string) {

View File

@@ -6,6 +6,7 @@ import (
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common/bufio/deadline"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -44,7 +45,14 @@ func (d *DetourDialer) DialContext(ctx context.Context, network string, destinat
if err != nil {
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) {

View File

@@ -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) {
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) {
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 {

View File

@@ -1,21 +1,42 @@
package mux
import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-mux"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
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 {
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{
Dialer: dialer,
Logger: logger,
Protocol: options.Protocol,
MaxConnections: options.MaxConnections,
MinStreams: options.MinStreams,
MaxStreams: options.MaxStreams,
Padding: options.Padding,
Brutal: brutalOptions,
})
}

View File

@@ -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
View 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)
}

View 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)
}

View File

@@ -182,11 +182,52 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex
break
}
switch frameType {
case 0x0:
case 0x00: // PADDING
continue
case 0x1:
case 0x01: // PING
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
offset, err = qtls.ReadUvarint(decryptedReader)
if err != nil {
@@ -208,8 +249,26 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex
if err != nil {
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:
// ignore unknown frame type
return nil, os.ErrInvalid
}
}
tlsHdr := make([]byte, 5)

485
common/srs/binary.go Normal file
View 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
View 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
View 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)
}

View File

@@ -9,3 +9,11 @@ const (
LogicalTypeAnd = "and"
LogicalTypeOr = "or"
)
const (
RuleSetTypeLocal = "local"
RuleSetTypeRemote = "remote"
RuleSetVersion1 = 1
RuleSetFormatSource = "source"
RuleSetFormatBinary = "binary"
)

3
constant/speed.go Normal file
View File

@@ -0,0 +1,3 @@
package constant
const MbpsToBps = 125000

View File

@@ -1,8 +1,9 @@
package constant
const (
V2RayTransportTypeHTTP = "http"
V2RayTransportTypeWebsocket = "ws"
V2RayTransportTypeQUIC = "quic"
V2RayTransportTypeGRPC = "grpc"
V2RayTransportTypeHTTP = "http"
V2RayTransportTypeWebsocket = "ws"
V2RayTransportTypeQUIC = "quic"
V2RayTransportTypeGRPC = "grpc"
V2RayTransportTypeHTTPUpgrade = "httpupgrade"
)

View File

@@ -1,3 +1,213 @@
---
icon: material/alert-decagram
---
# ChangeLog
#### 1.8.0-alpha.3
* 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
* Fixes and improvements
@@ -22,6 +232,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
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
@@ -35,8 +262,8 @@ When `auto_route` is enabled and `strict_route` is disabled, the device can now
**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
@@ -49,7 +276,8 @@ Built using Go 1.20, the last version that will run on Windows 7, 8, Server 2008
**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
@@ -83,6 +311,24 @@ the old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2
* Update golang.org/x/net to v0.17.0
* Fixes and improvements
#### 1.6.0-beta.3
* Update the legacy Hysteria protocol **1**
* Fixes and improvements
**1**
Based on discussions with the original author, the brutal CC and QUIC protocol parameters of
the old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2
#### 1.6.0-beta.2
* Add TLS self sign key pair generate command
* Update brutal congestion control for Hysteria2
* Fix Clash cache crash on arm32 devices
* Update golang.org/x/net to v0.17.0
* Fixes and improvements
#### 1.5.3
* Fix compatibility with Android 14

View 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`

View 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)

View 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`

View 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
View 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
View 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
View 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
View 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.

View File

@@ -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
```json
@@ -46,6 +57,7 @@
"10.0.0.0/24",
"192.168.0.1"
],
"source_ip_is_private": false,
"source_port": [
12345
],
@@ -79,6 +91,16 @@
1000
],
"clash_mode": "direct",
"wifi_ssid": [
"My WIFI"
],
"wifi_bssid": [
"00:00:00:00:00:00"
],
"rule_set": [
"geoip-cn",
"geosite-cn"
],
"invert": false,
"outbound": [
"direct"
@@ -160,15 +182,29 @@ Match domain using regular expression.
#### 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-set).
Match geosite.
#### 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-set).
Match source geoip.
#### 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
@@ -188,7 +224,7 @@ Match port range.
#### process_name
!!! error ""
!!! quote ""
Only supported on Linux, Windows, and macOS.
@@ -196,7 +232,7 @@ Match process name.
#### process_path
!!! error ""
!!! quote ""
Only supported on Linux, Windows, and macOS.
@@ -208,7 +244,7 @@ Match android package name.
#### user
!!! error ""
!!! quote ""
Only supported on Linux.
@@ -216,7 +252,7 @@ Match user name.
#### user_id
!!! error ""
!!! quote ""
Only supported on Linux.
@@ -226,6 +262,30 @@ Match user id.
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 match result.
@@ -262,4 +322,4 @@ Rewrite TTL in DNS responses.
#### rules
Included default rules.
Included rules.

View File

@@ -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
@@ -45,6 +56,7 @@
"source_ip_cidr": [
"10.0.0.0/24"
],
"source_ip_is_private": false,
"source_port": [
12345
],
@@ -78,6 +90,16 @@
1000
],
"clash_mode": "direct",
"wifi_ssid": [
"My WIFI"
],
"wifi_bssid": [
"00:00:00:00:00:00"
],
"rule_set": [
"geoip-cn",
"geosite-cn"
],
"invert": false,
"outbound": [
"direct"
@@ -157,16 +179,30 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
#### geosite
匹配 GeoSite。
!!! failure "已在 sing-box 1.8.0 废弃"
Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/migration/#migrate-geosite-to-rule-set)。
匹配 Geosite。
#### source_geoip
!!! failure "已在 sing-box 1.8.0 废弃"
GeoIp 已废弃且可能在不久的将来移除,参阅 [迁移指南](/migration/#migrate-geoip-to-rule-set)。
匹配源 GeoIP。
#### source_ip_cidr
匹配源 IP CIDR。
#### source_ip_is_private
!!! question "自 sing-box 1.8.0 起"
匹配非公开源 IP。
#### source_port
匹配源端口。
@@ -185,7 +221,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
#### process_name
!!! error ""
!!! quote ""
仅支持 Linux、Windows 和 macOS.
@@ -193,7 +229,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
#### process_path
!!! error ""
!!! quote ""
仅支持 Linux、Windows 和 macOS.
@@ -205,7 +241,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
#### user
!!! error ""
!!! quote ""
仅支持 Linux。
@@ -213,7 +249,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
#### user_id
!!! error ""
!!! quote ""
仅支持 Linux。
@@ -223,6 +259,28 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
匹配 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
反选匹配结果。
@@ -259,4 +317,4 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
#### rules
包括的默认规则。
包括的规则。

View File

@@ -49,7 +49,7 @@ The address of the dns server.
!!! 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 ""
@@ -57,7 +57,7 @@ The address of the dns server.
!!! warning ""
DHCP transport is not included by default, see [Installation](/#installation).
DHCP transport is not included by default, see [Installation](./#installation).
| RCode | Description |
|-------------------|-----------------------|

View 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.

View 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.

View File

@@ -1,139 +1,30 @@
---
icon: material/alert-decagram
---
# Experimental
!!! quote "Changes in sing-box 1.8.0"
:material-plus: [cache_file](#cache_file)
:material-alert-decagram: [clash_api](#clash_api)
### Structure
```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"
]
}
}
"cache_file": {},
"clash_api": {},
"v2ray_api": {}
}
}
```
!!! note ""
### Fields
Traffic statistics and connection management can degrade performance.
### Clash API Fields
!!! error ""
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.
| Key | Format |
|--------------|----------------------------|
| `cache_file` | [Cache File](./cache-file) |
| `clash_api` | [Clash API](./clash-api) |
| `v2ray_api` | [V2Ray API](./v2ray-api) |

View File

@@ -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
统计流量的用户列表。

View 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.

View File

@@ -36,7 +36,7 @@ No authentication required if empty.
#### set_system_proxy
!!! error ""
!!! quote ""
Only supported on Linux, Android, Windows, and macOS.

View File

@@ -36,7 +36,7 @@ HTTP 用户
#### set_system_proxy
!!! error ""
!!! quote ""
仅支持 Linux、Android、Windows 和 macOS。

View File

@@ -31,7 +31,7 @@
!!! 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

View File

@@ -4,8 +4,8 @@
{
"type": "hysteria2",
"tag": "hy2-in",
... // Listen Fields
...
// Listen Fields
"up_mbps": 100,
"down_mbps": 100,
@@ -28,7 +28,14 @@
!!! 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

View File

@@ -4,8 +4,8 @@
{
"type": "hysteria2",
"tag": "hy2-in",
... // 监听字段
...
// 监听字段
"up_mbps": 100,
"down_mbps": 100,
@@ -30,6 +30,12 @@
默认安装不包含被 Hysteria2 依赖的 QUIC参阅 [安装](/zh/#_2)。
!!! warning "与官方 Hysteria2 的区别"
官方程序支持一种名为 **userpass** 的验证方式,
本质上上是将用户名与密码的组合 `<username>:<password>` 作为实际上的密码,而 sing-box 不提供此别名。
要将 sing-box 与官方程序一起使用, 您需要填写该组合作为实际密码。
### 监听字段
参阅 [监听字段](/zh/configuration/shared/listen/)。
@@ -62,7 +68,7 @@ Hysteria 用户
#### ignore_client_bandwidth
命令客户端使用 BBR 流量控制算法而不是 Hysteria CC。
命令客户端使用 BBR 拥塞控制算法而不是 Hysteria CC。
`up_mbps``down_mbps` 冲突。

View File

@@ -33,7 +33,7 @@ No authentication required if empty.
#### set_system_proxy
!!! error ""
!!! quote ""
Only supported on Linux, Android, Windows, and macOS.

View File

@@ -33,7 +33,7 @@ SOCKS 和 HTTP 用户
#### set_system_proxy
!!! error ""
!!! quote ""
仅支持 Linux、Android、Windows 和 macOS。

View File

@@ -20,7 +20,7 @@
!!! warning ""
HTTP3 transport is not included by default, see [Installation](/#installation).
HTTP3 transport is not included by default, see [Installation](./#installation).
### Listen Fields

View File

@@ -1,4 +1,4 @@
!!! error ""
!!! quote ""
Only supported on Linux and macOS.

View File

@@ -1,4 +1,4 @@
!!! error ""
!!! quote ""
仅支持 Linux 和 macOS。

View File

@@ -8,7 +8,8 @@
... // Listen Fields
"method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg=="
"password": "8JCsPssfgS8tiRwiMlhARg==",
"multiplex": {}
}
```
@@ -23,7 +24,8 @@
"name": "sekai",
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
}
]
],
"multiplex": {}
}
```
@@ -41,7 +43,8 @@
"server_port": 8080,
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
}
]
],
"multiplex": {}
}
```
@@ -82,3 +85,7 @@ Both if empty.
| none | / |
| 2022 methods | `sing-box generate rand --base64 <Key Length>` |
| other methods | any string |
#### multiplex
See [Multiplex](/configuration/shared/multiplex#inbound) for details.

View File

@@ -8,7 +8,8 @@
... // 监听字段
"method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg=="
"password": "8JCsPssfgS8tiRwiMlhARg==",
"multiplex": {}
}
```
@@ -23,7 +24,8 @@
"name": "sekai",
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
}
]
],
"multiplex": {}
}
```
@@ -41,7 +43,8 @@
"server_port": 8080,
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
}
]
],
"multiplex": {}
}
```
@@ -81,4 +84,8 @@ See [Listen Fields](/configuration/shared/listen) for details.
|---------------|------------------------------------------|
| none | / |
| 2022 methods | `sing-box generate rand --base64 <密钥长度>` |
| other methods | 任意字符串 |
| other methods | 任意字符串 |
#### multiplex
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。

View File

@@ -1,4 +1,4 @@
!!! error ""
!!! quote ""
Only supported on Linux.

View File

@@ -1,4 +1,4 @@
!!! error ""
!!! quote ""
仅支持 Linux。

View File

@@ -24,6 +24,7 @@
"server_port": 8081
}
},
"multiplex": {},
"transport": {}
}
```
@@ -46,7 +47,7 @@ TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
#### 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.
@@ -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.
#### multiplex
See [Multiplex](/configuration/shared/multiplex#inbound) for details.
#### transport
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).

View File

@@ -24,6 +24,7 @@
"server_port": 8081
}
},
"multiplex": {},
"transport": {}
}
```
@@ -48,7 +49,7 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
#### fallback
!!! error ""
!!! quote ""
没有证据表明 GFW 基于 HTTP 响应检测并阻止 Trojan 服务器,并且在服务器上打开标准 http/s 端口是一个更大的特征。
@@ -60,6 +61,10 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
如果不为空ALPN 不在此列表中的 TLS 回退请求将被拒绝。
#### multiplex
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
#### transport
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。

View File

@@ -24,7 +24,7 @@
!!! 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

View File

@@ -48,7 +48,7 @@ TUIC 用户密码
#### congestion_control
QUIC 流量控制算法
QUIC 拥塞控制算法
可选值: `cubic`, `new_reno`, `bbr`

View File

@@ -1,4 +1,4 @@
!!! error ""
!!! quote ""
Only supported on Linux, Windows and macOS.
@@ -22,6 +22,12 @@
"::/1",
"8000::/1"
],
"inet4_route_exclude_address": [
"192.168.0.0/16"
],
"inet6_route_exclude_address": [
"fc00::/7"
],
"endpoint_independent_nat": false,
"stack": "system",
"include_interface": [
@@ -96,7 +102,7 @@ The maximum transmission unit.
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`
@@ -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.
#### 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
!!! info ""
@@ -157,11 +171,11 @@ TCP/IP stack.
!!! 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
!!! error ""
!!! quote ""
Interface rules are only supported on Linux and require auto_route.
@@ -177,7 +191,7 @@ Conflict with `include_interface`.
#### include_uid
!!! error ""
!!! quote ""
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
!!! error ""
!!! quote ""
Android user and package rules are only supported on Android and require auto_route.

View File

@@ -1,4 +1,4 @@
!!! error ""
!!! quote ""
仅支持 Linux、Windows 和 macOS。
@@ -22,6 +22,12 @@
"::/1",
"8000::/1"
],
"inet4_route_exclude_address": [
"192.168.0.0/16"
],
"inet6_route_exclude_address": [
"fc00::/7"
],
"endpoint_independent_nat": false,
"stack": "system",
"include_interface": [
@@ -96,7 +102,7 @@ tun 接口的 IPv6 前缀。
设置到 Tun 的默认路由。
!!! error ""
!!! quote ""
为避免流量环回,请设置 `route.auto_detect_interface``route.default_interface``outbound.bind_interface`
@@ -131,6 +137,14 @@ tun 接口的 IPv6 前缀。
启用 `auto_route` 时使用自定义路由而不是默认路由。
#### inet4_route_exclude_address
启用 `auto_route` 时排除自定义路由。
#### inet6_route_exclude_address
启用 `auto_route` 时排除自定义路由。
#### endpoint_independent_nat
启用独立于端点的 NAT。
@@ -157,7 +171,7 @@ TCP/IP 栈。
#### include_interface
!!! error ""
!!! quote ""
接口规则仅在 Linux 下被支持,并且需要 `auto_route`
@@ -173,7 +187,7 @@ TCP/IP 栈。
#### include_uid
!!! error ""
!!! quote ""
UID 规则仅在 Linux 下被支持,并且需要 `auto_route`
@@ -193,7 +207,7 @@ TCP/IP 栈。
#### include_android_user
!!! error ""
!!! quote ""
Android 用户和应用规则仅在 Android 下被支持,并且需要 `auto_route`

View File

@@ -15,6 +15,7 @@
}
],
"tls": {},
"multiplex": {},
"transport": {}
}
```
@@ -49,6 +50,10 @@ Available values:
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
#### multiplex
See [Multiplex](/configuration/shared/multiplex#inbound) for details.
#### transport
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).

View File

@@ -15,6 +15,7 @@
}
],
"tls": {},
"multiplex": {},
"transport": {}
}
```
@@ -49,6 +50,10 @@ VLESS 子协议。
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
#### multiplex
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
#### transport
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。

View File

@@ -15,6 +15,7 @@
}
],
"tls": {},
"multiplex": {},
"transport": {}
}
```
@@ -44,6 +45,10 @@ VMess users.
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
#### multiplex
See [Multiplex](/configuration/shared/multiplex#inbound) for details.
#### transport
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).

View File

@@ -15,6 +15,7 @@
}
],
"tls": {},
"multiplex": {},
"transport": {}
}
```
@@ -44,6 +45,10 @@ VMess 用户。
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
#### multiplex
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
#### transport
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。

View File

@@ -26,7 +26,7 @@
!!! 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

View File

@@ -24,7 +24,15 @@
!!! 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

View File

@@ -26,6 +26,12 @@
默认安装不包含被 Hysteria2 依赖的 QUIC参阅 [安装](/zh/#_2)。
!!! warning "与官方 Hysteria2 的区别"
官方程序支持一种名为 **userpass** 的验证方式,
本质上上是将用户名与密码的组合 `<username>:<password>` 作为实际上的密码,而 sing-box 不提供此别名。
要将 sing-box 与官方程序一起使用, 您需要填写该组合作为实际密码。
### 字段
#### server
@@ -44,7 +50,7 @@
最大带宽。
如果为空,将使用 BBR 流量控制算法而不是 Hysteria CC。
如果为空,将使用 BBR 拥塞控制算法而不是 Hysteria CC。
#### obfs.type

View File

@@ -15,7 +15,7 @@
}
```
!!! error ""
!!! quote ""
The selector can only be controlled through the [Clash API](/configuration/experimental#clash-api-fields) currently.

View File

@@ -15,7 +15,7 @@
}
```
!!! error ""
!!! quote ""
选择器目前只能通过 [Clash API](/zh/configuration/experimental#clash-api) 来控制。

View File

@@ -95,7 +95,7 @@ Conflict with `multiplex`.
#### multiplex
Multiplex configuration, see [Multiplex](/configuration/shared/multiplex).
See [Multiplex](/configuration/shared/multiplex#outbound) for details.
### Dial Fields

View File

@@ -95,7 +95,7 @@ UDP over TCP 配置。
#### multiplex
多路复用配置, 参阅 [多路复用](/zh/configuration/shared/multiplex)。
参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。
### 拨号字段

View File

@@ -25,7 +25,7 @@
!!! warning ""
ShadowsocksR is not included by default, see [Installation](/#installation).
ShadowsocksR is not included by default, see [Installation](./#installation).
### Fields

View File

@@ -18,7 +18,7 @@
!!! info ""
Embedded tor is not included by default, see [Installation](/#installation).
Embedded tor is not included by default, see [Installation](./#installation).
### Fields

View File

@@ -51,7 +51,7 @@ TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
#### multiplex
Multiplex configuration, see [Multiplex](/configuration/shared/multiplex).
See [Multiplex](/configuration/shared/multiplex#outbound) for details.
#### transport

View File

@@ -51,7 +51,7 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
#### multiplex
多路复用配置, 参阅 [多路复用](/zh/configuration/shared/multiplex)。
参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。
#### transport

View File

@@ -23,7 +23,7 @@
!!! 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

View File

@@ -51,7 +51,7 @@ TUIC 用户密码
#### congestion_control
QUIC 流量控制算法
QUIC 拥塞控制算法
可选值: `cubic`, `new_reno`, `bbr`

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