Compare commits

..

1 Commits

Author SHA1 Message Date
世界
9092139e73 Initialize L3 routing support 2023-03-22 01:36:17 +08:00
378 changed files with 4995 additions and 14153 deletions

View File

@@ -1,77 +1,70 @@
name: Bug report
description: "Report sing-box bug"
name: Bug Report
description: "Create a report to help us improve."
body:
- type: dropdown
- type: checkboxes
id: terms
attributes:
label: Operating system
description: Operating system type
label: Welcome
options:
- iOS
- macOS
- Apple tvOS
- Android
- Windows
- Linux
- Others
validations:
required: true
- type: input
attributes:
label: System version
description: Please provide the operating system version
validations:
required: true
- type: dropdown
attributes:
label: Installation type
description: Please provide the sing-box installation type
options:
- Original sing-box Command Line
- sing-box for iOS Graphical Client
- sing-box for macOS Graphical Client
- sing-box for Apple tvOS Graphical Client
- sing-box for Android Graphical Client
- Third-party graphical clients that advertise themselves as using sing-box (Windows)
- Third-party graphical clients that advertise themselves as using sing-box (Android)
- Others
validations:
required: true
- type: input
attributes:
description: Graphical client version
label: If you are using a graphical client, please provide the version of the client.
- label: Yes, I'm using the latest major release. Only such installations are supported.
required: true
- label: Yes, I'm using the latest Golang release. Only such installations are supported.
required: true
- label: Yes, I've searched similar issues on GitHub and didn't find any.
required: true
- label: Yes, I've included all information below (version, **FULL** config, **FULL** log, etc).
required: true
- type: textarea
id: problem
attributes:
label: Version
description: If you are using the original command line program, please provide the output of the `sing-box version` command.
label: Description of the problem
placeholder: Your problem description
validations:
required: true
- type: textarea
id: version
attributes:
label: Version of sing-box
value: |-
<details>
```console
# Replace this line with the output
$ sing-box version
# Paste output here
```
</details>
- type: textarea
attributes:
label: Description
description: Please provide a detailed description of the error.
validations:
required: true
- type: textarea
id: config
attributes:
label: Reproduction
description: Please provide the steps to reproduce the error, including the configuration files and procedures that can locally (not dependent on the remote server) reproduce the error using the original command line program of sing-box.
validations:
required: true
- type: textarea
attributes:
label: Logs
description: |-
If you encounter a crash with the graphical client, please provide crash logs.
For Apple platform clients, please check `Settings - View Service Log` for crash logs.
For the Android client, please check the `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` file for crash logs.
label: Server and client configuration file
value: |-
<details>
```console
# Replace this line with logs
# paste json here
```
</details>
</details>
validations:
required: true
- type: textarea
id: log
attributes:
label: Server and client log file
value: |-
<details>
```console
# paste log here
```
</details>
validations:
required: true

View File

@@ -1,77 +0,0 @@
name: 错误反馈
description: "提交 sing-box 漏洞"
body:
- type: dropdown
attributes:
label: 操作系统
description: 请提供操作系统类型
options:
- iOS
- macOS
- Apple tvOS
- Android
- Windows
- Linux
- 其他
validations:
required: true
- type: input
attributes:
label: 系统版本
description: 请提供操作系统版本
validations:
required: true
- type: dropdown
attributes:
label: 安装类型
description: 请提供该 sing-box 安装类型
options:
- sing-box 原始命令行程序
- sing-box for iOS 图形客户端程序
- sing-box for macOS 图形客户端程序
- sing-box for Apple tvOS 图形客户端程序
- sing-box for Android 图形客户端程序
- 宣传使用 sing-box 的第三方图形客户端程序 (Windows)
- 宣传使用 sing-box 的第三方图形客户端程序 (Android)
- 其他
validations:
required: true
- type: input
attributes:
description: 图形客户端版本
label: 如果您使用图形客户端程序,请提供该程序版本。
- type: textarea
attributes:
label: 版本
description: 如果您使用原始命令行程序,请提供 `sing-box version` 命令的输出。
value: |-
<details>
```console
# 使用输出内容覆盖此行
```
</details>
- type: textarea
attributes:
label: 描述
description: 请提供错误的详细描述。
validations:
required: true
- type: textarea
attributes:
label: 重现方式
description: 请提供重现错误的步骤,必须包括可以在本地(不依赖与远程服务器)使用 sing-box 原始命令行程序重现错误的配置文件与流程。
validations:
required: true
- type: textarea
attributes:
label: 日志
description: |-
如果您遭遇图形界面应用程序崩溃,请提供崩溃日志。
对于 Apple 平台图形客户端程序,请检查 `Settings - View Service Log` 以导出崩溃日志。
对于 Android 图形客户端程序,请检查 `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` 文件以导出崩溃日志。
value: |-
<details>
```console
# 使用日志内容覆盖此行
```
</details>

View File

@@ -31,6 +31,12 @@ jobs:
uses: actions/setup-go@v4
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: Add cache to Go proxy
run: |
version=`git rev-parse HEAD`
@@ -62,27 +68,7 @@ jobs:
~/go/pkg/mod
key: go118-${{ hashFiles('**/go.sum') }}
- name: Run Test
run: make ci_build_go118
build_go120:
name: Debug build (Go 1.20)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: 1.20.7
- name: Cache go module
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go118-${{ hashFiles('**/go.sum') }}
- name: Run Test
run: make ci_build
run: make
cross:
strategy:
matrix:
@@ -210,6 +196,12 @@ jobs:
uses: actions/setup-go@v4
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: Build
id: build
run: make

View File

@@ -31,6 +31,12 @@ jobs:
uses: actions/setup-go@v4
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:

20
.github/workflows/mkdocs.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: Generate Documents
on:
push:
branches:
- dev
paths:
- docs/**
- .github/workflows/mkdocs.yml
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.x
- run: |
pip install mkdocs-material=="9.*" mkdocs-static-i18n=="0.53"
- run: |
mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history

View File

@@ -8,7 +8,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
- uses: actions/stale@v7
with:
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
days-before-stale: 60

View File

@@ -14,7 +14,6 @@ builds:
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_reality_server
@@ -49,7 +48,6 @@ builds:
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_clash_api
@@ -119,6 +117,8 @@ nfpms:
dst: /etc/systemd/system/sing-box@.service
- src: LICENSE
dst: /usr/share/licenses/sing-box/LICENSE
scripts:
postremove: release/config/postremove.sh
source:
enabled: false
name_template: '{{ .ProjectName }}-{{ .Version }}.source'

View File

@@ -1,4 +1,4 @@
FROM golang:1.21-alpine AS builder
FROM golang:1.20-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
@@ -9,7 +9,7 @@ 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_utls,with_reality_server,with_clash_api,with_acme \
&& go build -v -trimpath -tags with_quic,with_wireguard,with_reality_server,with_acme \
-o /go/bin/sing-box \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
./cmd/sing-box

View File

@@ -11,7 +11,4 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
In addition, no derivative work may use the name or imply association
with this application without prior consent.
along with this program. If not, see <http://www.gnu.org/licenses/>.

129
Makefile
View File

@@ -1,31 +1,20 @@
NAME = sing-box
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
TAGS ?= $(TAGS_GO118),$(TAGS_GO120)
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
GOHOSTOS = $(shell go env GOHOSTOS)
GOHOSTARCH = $(shell go env GOHOSTARCH)
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
MAIN = ./cmd/sing-box
PREFIX ?= $(shell go env GOPATH)
.PHONY: test release
build:
go build $(MAIN_PARAMS) $(MAIN)
ci_build_go118:
go build $(PARAMS) $(MAIN)
go build $(PARAMS) -tags "$(TAGS_GO118)" $(MAIN)
ci_build:
go build $(PARAMS) $(MAIN)
go build $(MAIN_PARAMS) $(MAIN)
install:
go build -o $(PREFIX)/bin/$(NAME) $(PARAMS) $(MAIN)
@@ -33,7 +22,7 @@ install:
fmt:
@gofumpt -l -w .
@gofmt -s -w .
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
@gci write --custom-order -s "standard,prefix(github.com/sagernet/),default" .
fmt_install:
go install -v mvdan.cc/gofumpt@latest
@@ -58,103 +47,24 @@ proto_install:
go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
release:
go run ./cmd/internal/build goreleaser release --clean --skip-publish || exit 1
snapshot:
go run ./cmd/internal/build goreleaser release --rm-dist --snapshot || exit 1
mkdir dist/release
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release
ghr --delete --draft --prerelease -p 1 nightly dist/release
rm -r dist
release:
go run ./cmd/internal/build goreleaser release --rm-dist --skip-publish || exit 1
mkdir dist/release
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
ghr --delete --draft --prerelease -p 3 $(shell git describe --tags) dist/release
rm -r dist
release_install:
go install -v github.com/goreleaser/goreleaser@latest
go install -v github.com/tcnksm/ghr@latest
update_android_version:
go run ./cmd/internal/update_android_version
build_android:
cd ../sing-box-for-android && ./gradlew :app:assembleRelease
upload_android:
mkdir -p dist/release_android
cp ../sing-box-for-android/app/build/outputs/apk/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
build_ios:
cd ../sing-box-for-apple && \
rm -rf build/SFI.xcarchive && \
xcodebuild archive -scheme SFI -configuration Release -archivePath build/SFI.xcarchive
upload_ios_app_store:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist
release_ios: build_ios upload_ios_app_store
build_macos:
cd ../sing-box-for-apple && \
rm -rf build/SFM.xcarchive && \
xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive
upload_macos_app_store:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath build/SFM.xcarchive -exportOptionsPlist SFI/Upload.plist
release_macos: build_macos upload_macos_app_store
build_macos_independent:
cd ../sing-box-for-apple && \
rm -rf build/SFT.System.xcarchive && \
xcodebuild archive -scheme SFM.System -configuration Release -archivePath build/SFM.System.xcarchive
notarize_macos_independent:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath "build/SFM.System.xcarchive" -exportOptionsPlist SFM.System/Upload.plist
wait_notarize_macos_independent:
sleep 60
export_macos_independent:
rm -rf dist/SFM
mkdir -p dist/SFM
cd ../sing-box-for-apple && \
xcodebuild -exportNotarizedApp -archivePath build/SFM.System.xcarchive -exportPath "../sing-box/dist/SFM"
upload_macos_independent:
cd dist/SFM && \
rm -f *.zip && \
zip -ry "SFM-${VERSION}-universal.zip" SFM.app && \
ghr --replace --draft --prerelease "v${VERSION}" *.zip
release_macos_independent: build_macos_independent notarize_macos_independent export_macos_independent wait_notarize_macos_independent upload_macos_independent
build_tvos:
cd ../sing-box-for-apple && \
rm -rf build/SFT.xcarchive && \
export DEVELOPER_DIR=/Applications/Xcode-beta.app/Contents/Developer && \
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive
upload_tvos_app_store:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist
release_tvos: build_tvos upload_tvos_app_store
update_apple_version:
go run ./cmd/internal/update_apple_version
release_apple: 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 ./... && \
cd test && \
@@ -167,20 +77,13 @@ test_stdio:
go mod tidy && \
go test -v -tags "$(TAGS_TEST),force_stdio" .
lib_android:
go run ./cmd/internal/build_libbox -target android
lib_ios:
go run ./cmd/internal/build_libbox -target ios
lib:
go run ./cmd/internal/build_libbox -target android
go run ./cmd/internal/build_libbox -target ios
go run ./cmd/internal/build_libbox
lib_install:
go get -v -d
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20230728014906-3de089147f59
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230728014906-3de089147f59
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20221130124640-349ebaa752ca
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20221130124640-349ebaa752ca
clean:
rm -rf bin dist sing-box

View File

@@ -8,10 +8,6 @@ The universal proxy platform.
https://sing-box.sagernet.org
## Support
https://community.sagernet.org/c/sing-box/
## License
```
@@ -29,7 +25,4 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
In addition, no derivative work may use the name or imply association
with this application without prior consent.
```

View File

@@ -12,9 +12,7 @@ type ClashServer interface {
Service
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)
@@ -22,13 +20,8 @@ type ClashServer interface {
}
type ClashCacheFile interface {
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
}
type Tracker interface {
@@ -36,16 +29,10 @@ type Tracker interface {
}
type OutboundGroup interface {
Outbound
Now() string
All() []string
}
type URLTestGroup interface {
OutboundGroup
URLTest(ctx context.Context, url string) (map[string]uint16, error)
}
func OutboundTag(detour Outbound) string {
if group, isGroup := detour.(OutboundGroup); isGroup {
return group.Now()

View File

@@ -1,32 +0,0 @@
package adapter
import (
"net/netip"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common/logger"
)
type FakeIPStore interface {
Service
Contains(address netip.Addr) bool
Create(domain string, isIPv6 bool) (netip.Addr, error)
Lookup(address netip.Addr) (string, bool)
Reset() error
}
type FakeIPStorage interface {
FakeIPMetadata() *FakeIPMetadata
FakeIPSaveMetadata(metadata *FakeIPMetadata) error
FakeIPSaveMetadataAsync(metadata *FakeIPMetadata)
FakeIPStore(address netip.Addr, domain string) error
FakeIPStoreAsync(address netip.Addr, domain string, logger logger.Logger)
FakeIPLoad(address netip.Addr) (string, bool)
FakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr, bool)
FakeIPReset() error
}
type FakeIPTransport interface {
dns.Transport
Store() FakeIPStore
}

View File

@@ -1,50 +0,0 @@
package adapter
import (
"bytes"
"encoding"
"encoding/binary"
"io"
"net/netip"
"github.com/sagernet/sing/common"
)
type FakeIPMetadata struct {
Inet4Range netip.Prefix
Inet6Range netip.Prefix
Inet4Current netip.Addr
Inet6Current netip.Addr
}
func (m *FakeIPMetadata) MarshalBinary() (data []byte, err error) {
var buffer bytes.Buffer
for _, marshaler := range []encoding.BinaryMarshaler{m.Inet4Range, m.Inet6Range, m.Inet4Current, m.Inet6Current} {
data, err = marshaler.MarshalBinary()
if err != nil {
return
}
common.Must(binary.Write(&buffer, binary.BigEndian, uint16(len(data))))
buffer.Write(data)
}
data = buffer.Bytes()
return
}
func (m *FakeIPMetadata) UnmarshalBinary(data []byte) error {
reader := bytes.NewReader(data)
for _, unmarshaler := range []encoding.BinaryUnmarshaler{&m.Inet4Range, &m.Inet6Range, &m.Inet4Current, &m.Inet6Current} {
var length uint16
common.Must(binary.Read(reader, binary.BigEndian, &length))
element := make([]byte, length)
_, err := io.ReadFull(reader, element)
if err != nil {
return err
}
err = unmarshaler.UnmarshalBinary(element)
if err != nil {
return err
}
}
return nil
}

View File

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

View File

@@ -4,6 +4,7 @@ import (
"context"
"net"
tun "github.com/sagernet/sing-tun"
N "github.com/sagernet/sing/common/network"
)
@@ -13,8 +14,12 @@ type Outbound interface {
Type() string
Tag() string
Network() []string
Dependencies() []string
N.Dialer
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
}
type IPOutbound interface {
Outbound
NewIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) (tun.DirectDestination, error)
}

View File

@@ -4,6 +4,12 @@ type PreStarter interface {
PreStart() error
}
type PostStarter interface {
PostStart() error
func PreStart(starter any) error {
if preService, ok := starter.(PreStarter); ok {
err := preService.PreStart()
if err != nil {
return err
}
}
return nil
}

View File

@@ -21,10 +21,11 @@ type Router interface {
Outbound(tag string) (Outbound, bool)
DefaultOutbound(network string) Outbound
FakeIPStore() FakeIPStore
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
RouteIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) tun.RouteAction
NatRequired(outbound string) bool
GeoIPReader() *geoip.Reader
LoadGeosite(code string) (Rule, error)
@@ -32,10 +33,8 @@ type Router interface {
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)
ClearDNSCache()
InterfaceFinder() control.InterfaceFinder
UpdateInterfaces() error
DefaultInterface() string
AutoDetectInterface() bool
AutoDetectInterfaceFunc() control.Func
@@ -43,7 +42,9 @@ type Router interface {
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager
Rules() []Rule
IPRules() []IPRule
TimeService
@@ -52,8 +53,6 @@ type Router interface {
V2RayServer() V2RayServer
SetV2RayServer(server V2RayServer)
ResetNetwork() error
}
type routerContextKey struct{}
@@ -82,9 +81,13 @@ type Rule interface {
type DNSRule interface {
Rule
DisableCache() bool
RewriteTTL() *uint32
}
type IPRule interface {
Rule
Action() tun.ActionType
}
type InterfaceUpdateListener interface {
InterfaceUpdated()
InterfaceUpdated() error
}

182
box.go
View File

@@ -9,6 +9,7 @@ import (
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/inbound"
@@ -19,7 +20,6 @@ import (
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/service/pause"
)
var _ adapter.Service = (*Box)(nil)
@@ -31,49 +31,80 @@ type Box struct {
outbounds []adapter.Outbound
logFactory log.Factory
logger log.ContextLogger
logFile *os.File
preServices map[string]adapter.Service
postServices map[string]adapter.Service
done chan struct{}
}
type Options struct {
option.Options
Context context.Context
PlatformInterface platform.Interface
}
func New(options Options) (*Box, error) {
ctx := options.Context
if ctx == nil {
ctx = context.Background()
}
ctx = pause.ContextWithDefaultManager(ctx)
func New(ctx context.Context, options option.Options, platformInterface platform.Interface) (*Box, error) {
createdAt := time.Now()
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
var needClashAPI bool
var needV2RayAPI bool
if experimentalOptions.ClashAPI != nil || options.PlatformInterface != nil {
if experimentalOptions.ClashAPI != nil && experimentalOptions.ClashAPI.ExternalController != "" {
needClashAPI = true
}
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
needV2RayAPI = true
}
var defaultLogWriter io.Writer
if options.PlatformInterface != nil {
defaultLogWriter = io.Discard
}
logFactory, err := log.New(log.Options{
Context: ctx,
Options: common.PtrValueOrDefault(options.Log),
Observable: needClashAPI,
DefaultWriter: defaultLogWriter,
BaseTime: createdAt,
PlatformWriter: options.PlatformInterface,
})
if err != nil {
return nil, E.Cause(err, "create log factory")
logOptions := common.PtrValueOrDefault(options.Log)
var logFactory log.Factory
var observableLogFactory log.ObservableFactory
var logFile *os.File
var logWriter io.Writer
if logOptions.Disabled {
observableLogFactory = log.NewNOPFactory()
logFactory = observableLogFactory
} else {
switch logOptions.Output {
case "":
if platformInterface != nil {
logWriter = io.Discard
} else {
logWriter = os.Stdout
}
case "stderr":
logWriter = os.Stderr
case "stdout":
logWriter = os.Stdout
default:
var err error
logFile, err = os.OpenFile(C.BasePath(logOptions.Output), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return nil, err
}
logWriter = logFile
}
logFormatter := log.Formatter{
BaseTime: createdAt,
DisableColors: logOptions.DisableColor || logFile != nil,
DisableTimestamp: !logOptions.Timestamp && logFile != nil,
FullTimestamp: logOptions.Timestamp,
TimestampFormat: "-0700 2006-01-02 15:04:05",
}
if needClashAPI {
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter, platformInterface)
logFactory = observableLogFactory
} else {
logFactory = log.NewFactory(logFormatter, logWriter, platformInterface)
}
if logOptions.Level != "" {
logLevel, err := log.ParseLevel(logOptions.Level)
if err != nil {
return nil, E.Cause(err, "parse log level")
}
logFactory.SetLevel(logLevel)
} else {
logFactory.SetLevel(log.LevelTrace)
}
}
router, err := route.NewRouter(
ctx,
logFactory,
@@ -81,7 +112,7 @@ func New(options Options) (*Box, error) {
common.PtrValueOrDefault(options.DNS),
common.PtrValueOrDefault(options.NTP),
options.Inbounds,
options.PlatformInterface,
platformInterface,
)
if err != nil {
return nil, E.Cause(err, "parse route options")
@@ -101,7 +132,7 @@ func New(options Options) (*Box, error) {
router,
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
inboundOptions,
options.PlatformInterface,
platformInterface,
)
if err != nil {
return nil, E.Cause(err, "parse inbound[", i, "]")
@@ -120,7 +151,6 @@ func New(options Options) (*Box, error) {
ctx,
router,
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
tag,
outboundOptions)
if err != nil {
return nil, E.Cause(err, "parse outbound[", i, "]")
@@ -128,7 +158,7 @@ func New(options Options) (*Box, error) {
outbounds = append(outbounds, out)
}
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"})
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), option.Outbound{Type: "direct", Tag: "default"})
common.Must(oErr)
outbounds = append(outbounds, out)
return out
@@ -136,18 +166,10 @@ func New(options Options) (*Box, error) {
if err != nil {
return nil, err
}
if options.PlatformInterface != nil {
err = options.PlatformInterface.Initialize(ctx, router)
if err != nil {
return nil, E.Cause(err, "initialize platform interface")
}
}
preServices := make(map[string]adapter.Service)
postServices := make(map[string]adapter.Service)
if needClashAPI {
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), clashAPIOptions)
clashServer, err := experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI))
if err != nil {
return nil, E.Cause(err, "create clash api server")
}
@@ -155,7 +177,7 @@ func New(options Options) (*Box, error) {
preServices["clash api"] = clashServer
}
if needV2RayAPI {
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI))
if err != nil {
return nil, E.Cause(err, "create v2ray api server")
}
@@ -169,6 +191,7 @@ func New(options Options) (*Box, error) {
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
logFile: logFile,
preServices: preServices,
postServices: postServices,
done: make(chan struct{}),
@@ -215,17 +238,26 @@ func (s *Box) Start() error {
func (s *Box) preStart() error {
for serviceName, service := range s.preServices {
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)
}
s.logger.Trace("pre-starting ", serviceName)
err := adapter.PreStart(service)
if err != nil {
return E.Cause(err, "pre-start ", serviceName)
}
}
err := s.startOutbounds()
if err != nil {
return err
for i, out := range s.outbounds {
if starter, isStarter := out.(common.Starter); isStarter {
var tag string
if out.Tag() == "" {
tag = F.ToString(i)
} else {
tag = out.Tag()
}
s.logger.Trace("initializing outbound ", tag)
err := starter.Start()
if err != nil {
return E.Cause(err, "initialize outbound/", out.Type(), "[", tag, "]")
}
}
}
return s.router.Start()
}
@@ -249,30 +281,17 @@ func (s *Box) start() error {
} else {
tag = in.Tag()
}
s.logger.Trace("initializing inbound/", in.Type(), "[", tag, "]")
s.logger.Trace("initializing inbound ", tag)
err = in.Start()
if err != nil {
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
}
}
return nil
}
func (s *Box) postStart() error {
for serviceName, service := range s.postServices {
s.logger.Trace("starting ", service)
err := service.Start()
s.logger.Trace("start ", serviceName)
err = service.Start()
if err != nil {
return E.Cause(err, "start ", serviceName)
}
}
for serviceName, service := range s.outbounds {
if lateService, isLateService := service.(adapter.PostStarter); isLateService {
s.logger.Trace("post-starting ", service)
err := lateService.PostStart()
if err != nil {
return E.Cause(err, "post-start ", serviceName)
}
return E.Cause(err, "starting ", serviceName)
}
}
return nil
@@ -287,21 +306,33 @@ func (s *Box) Close() error {
}
var errors error
for serviceName, service := range s.postServices {
s.logger.Trace("closing ", serviceName)
errors = E.Append(errors, service.Close(), func(err error) error {
s.logger.Trace("closing ", serviceName)
return E.Cause(err, "close ", serviceName)
})
}
for i, in := range s.inbounds {
s.logger.Trace("closing inbound/", in.Type(), "[", i, "]")
var tag string
if in.Tag() == "" {
tag = F.ToString(i)
} else {
tag = in.Tag()
}
s.logger.Trace("closing inbound ", tag)
errors = E.Append(errors, in.Close(), func(err error) error {
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
})
}
for i, out := range s.outbounds {
s.logger.Trace("closing outbound/", out.Type(), "[", i, "]")
var tag string
if out.Tag() == "" {
tag = F.ToString(i)
} else {
tag = out.Tag()
}
s.logger.Trace("closing outbound ", tag)
errors = E.Append(errors, common.Close(out), func(err error) error {
return E.Cause(err, "close outbound/", out.Type(), "[", i, "]")
return E.Cause(err, "close inbound/", out.Type(), "[", i, "]")
})
}
s.logger.Trace("closing router")
@@ -316,12 +347,17 @@ func (s *Box) Close() error {
return E.Cause(err, "close ", serviceName)
})
}
s.logger.Trace("closing log factory")
s.logger.Trace("closing logger")
if err := common.Close(s.logFactory); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close log factory")
})
}
if s.logFile != nil {
errors = E.Append(errors, s.logFile.Close(), func(err error) error {
return E.Cause(err, "close log file")
})
}
return errors
}

View File

@@ -1,79 +0,0 @@
package box
import (
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
func (s *Box) startOutbounds() error {
outboundTags := make(map[adapter.Outbound]string)
outbounds := make(map[string]adapter.Outbound)
for i, outboundToStart := range s.outbounds {
var outboundTag string
if outboundToStart.Tag() == "" {
outboundTag = F.ToString(i)
} else {
outboundTag = outboundToStart.Tag()
}
if _, exists := outbounds[outboundTag]; exists {
return E.New("outbound tag ", outboundTag, " duplicated")
}
outboundTags[outboundToStart] = outboundTag
outbounds[outboundTag] = outboundToStart
}
started := make(map[string]bool)
for {
canContinue := false
startOne:
for _, outboundToStart := range s.outbounds {
outboundTag := outboundTags[outboundToStart]
if started[outboundTag] {
continue
}
dependencies := outboundToStart.Dependencies()
for _, dependency := range dependencies {
if !started[dependency] {
continue startOne
}
}
started[outboundTag] = true
canContinue = true
if starter, isStarter := outboundToStart.(common.Starter); isStarter {
s.logger.Trace("initializing outbound/", outboundToStart.Type(), "[", outboundTag, "]")
err := starter.Start()
if err != nil {
return E.Cause(err, "initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
}
}
}
if len(started) == len(s.outbounds) {
break
}
if canContinue {
continue
}
currentOutbound := common.Find(s.outbounds, func(it adapter.Outbound) bool {
return !started[outboundTags[it]]
})
var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error
lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error {
problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool {
return !started[it]
})
if common.Contains(oTree, problemOutboundTag) {
return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag)
}
problemOutbound := outbounds[problemOutboundTag]
if problemOutbound == nil {
return E.New("dependency[", problemOutbound, "] not found for outbound[", outboundTags[oCurrent], "]")
}
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
}
return lintOutbound([]string{outboundTags[currentOutbound]}, currentOutbound)
}
return nil
}

View File

@@ -1,7 +1,6 @@
package main
import (
"go/build"
"os"
"os/exec"
@@ -12,10 +11,6 @@ import (
func main() {
build_shared.FindSDK()
if os.Getenv("build.Default.GOPATH") == "" {
os.Setenv("GOPATH", build.Default.GOPATH)
}
command := exec.Command(os.Args[1], os.Args[2:]...)
command.Stdout = os.Stdout
command.Stderr = os.Stderr

View File

@@ -5,7 +5,6 @@ import (
"os"
"os/exec"
"path/filepath"
"strings"
_ "github.com/sagernet/gomobile/event/key"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
@@ -39,24 +38,18 @@ func main() {
var (
sharedFlags []string
debugFlags []string
sharedTags []string
iosTags []string
debugTags []string
)
func init() {
sharedFlags = append(sharedFlags, "-trimpath")
sharedFlags = append(sharedFlags, "-ldflags")
currentTag, err := build_shared.ReadTag()
if err != nil {
currentTag = "unknown"
}
sharedFlags = append(sharedFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
debugFlags = append(debugFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api")
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
debugTags = append(debugTags, "debug")
}
func buildAndroid() {
@@ -77,9 +70,9 @@ func buildAndroid() {
args = append(args, "-tags")
if !debugEnabled {
args = append(args, strings.Join(sharedTags, ","))
args = append(args, "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api")
} else {
args = append(args, strings.Join(append(sharedTags, debugTags...), ","))
args = append(args, "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api,debug")
}
args = append(args, "./experimental/libbox")
@@ -107,7 +100,7 @@ func buildiOS() {
args := []string{
"bind",
"-v",
"-target", "ios,iossimulator,tvos,tvossimulator,macos",
"-target", "ios,iossimulator,macos",
"-libname=box",
}
if !debugEnabled {
@@ -116,12 +109,11 @@ func buildiOS() {
args = append(args, debugFlags...)
}
tags := append(sharedTags, iosTags...)
args = append(args, "-tags")
if !debugEnabled {
args = append(args, strings.Join(tags, ","))
args = append(args, "with_gvisor,with_quic,with_utls,with_clash_api,with_low_memory,with_conntrack")
} else {
args = append(args, strings.Join(append(tags, debugTags...), ","))
args = append(args, "with_gvisor,with_quic,with_utls,with_clash_api,with_low_memory,with_conntrack,debug")
}
args = append(args, "./experimental/libbox")
@@ -133,7 +125,7 @@ func buildiOS() {
log.Fatal(err)
}
copyPath := filepath.Join("..", "sing-box-for-apple")
copyPath := filepath.Join("..", "sing-box-for-ios")
if rw.FileExists(copyPath) {
targetDir := filepath.Join(copyPath, "Libbox.xcframework")
targetDir, _ = filepath.Abs(targetDir)

View File

@@ -1,10 +1,6 @@
package build_shared
import (
"github.com/sagernet/sing-box/common/badversion"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/shell"
)
import "github.com/sagernet/sing/common/shell"
func ReadTag() (string, error) {
currentTag, err := shell.Exec("git", "describe", "--tags").ReadOutput()
@@ -16,21 +12,5 @@ func ReadTag() (string, error) {
return currentTag[1:], nil
}
shortCommit, _ := shell.Exec("git", "rev-parse", "--short", "HEAD").ReadOutput()
version := badversion.Parse(currentTagRev[1:])
if version.PreReleaseIdentifier == "" {
version.Patch++
}
return version.String() + "-" + shortCommit, nil
}
func ReadTagVersion() (badversion.Version, error) {
currentTag := common.Must1(shell.Exec("git", "describe", "--tags").ReadOutput())
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
version := badversion.Parse(currentTagRev[1:])
if currentTagRev != currentTag {
if version.PreReleaseIdentifier == "" {
version.Patch++
}
}
return version, nil
return currentTagRev[1:] + "-" + shortCommit, nil
}

View File

@@ -1,51 +0,0 @@
package main
import (
"os"
"path/filepath"
"strconv"
"strings"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
)
func main() {
newVersion := common.Must1(build_shared.ReadTagVersion())
androidPath, err := filepath.Abs("../sing-box-for-android")
if err != nil {
log.Fatal(err)
}
common.Must(os.Chdir(androidPath))
localProps := common.Must1(os.ReadFile("local.properties"))
var propsList [][]string
for _, propLine := range strings.Split(string(localProps), "\n") {
propsList = append(propsList, strings.Split(propLine, "="))
}
for _, propPair := range propsList {
if propPair[0] == "VERSION_NAME" {
if propPair[1] == newVersion.String() {
log.Info("version not changed")
return
}
propPair[1] = newVersion.String()
log.Info("updated version to ", newVersion.String())
}
}
for _, propPair := range propsList {
switch propPair[0] {
case "VERSION_CODE":
versionCode := common.Must1(strconv.ParseInt(propPair[1], 10, 64))
propPair[1] = strconv.Itoa(int(versionCode + 1))
log.Info("updated version code to ", propPair[1])
case "RELEASE_NOTES":
propPair[1] = "sing-box " + newVersion.String()
}
}
var newProps []string
for _, propPair := range propsList {
newProps = append(newProps, strings.Join(propPair, "="))
}
common.Must(os.WriteFile("local.properties", []byte(strings.Join(newProps, "\n")), 0o644))
}

View File

@@ -1,80 +0,0 @@
package main
import (
"os"
"path/filepath"
"regexp"
"strings"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
"howett.net/plist"
)
func main() {
newVersion := common.Must1(build_shared.ReadTagVersion())
applePath, err := filepath.Abs("../sing-box-for-apple")
if err != nil {
log.Fatal(err)
}
common.Must(os.Chdir(applePath))
projectFile := common.Must1(os.Open("sing-box.xcodeproj/project.pbxproj"))
var project map[string]any
decoder := plist.NewDecoder(projectFile)
common.Must(decoder.Decode(&project))
objectsMap := project["objects"].(map[string]any)
projectContent := string(common.Must1(os.ReadFile("sing-box.xcodeproj/project.pbxproj")))
newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfa"}, newVersion.VersionString())
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfa.independent", "io.nekohasekai.sfa.system"}, newVersion.String())
if updated0 || updated1 {
log.Info("updated version to ", newVersion.VersionString())
common.Must(os.WriteFile("sing-box.xcodeproj/project.pbxproj.bak", []byte(projectContent), 0o644))
common.Must(os.WriteFile("sing-box.xcodeproj/project.pbxproj", []byte(newContent), 0o644))
} else {
log.Info("version not changed")
}
}
func findAndReplace(objectsMap map[string]any, projectContent string, bundleIDList []string, newVersion string) (string, bool) {
objectKeyList := findObjectKey(objectsMap, bundleIDList)
var updated bool
for _, objectKey := range objectKeyList {
matchRegexp := common.Must1(regexp.Compile(objectKey + ".*= \\{"))
indexes := matchRegexp.FindStringIndex(projectContent)
if len(indexes) < 2 {
println(projectContent)
log.Fatal("failed to find object key ", objectKey, ": ", strings.Index(projectContent, objectKey))
}
indexStart := indexes[1]
indexEnd := indexStart + strings.Index(projectContent[indexStart:], "}")
versionStart := indexStart + strings.Index(projectContent[indexStart:indexEnd], "MARKETING_VERSION = ") + 20
versionEnd := versionStart + strings.Index(projectContent[versionStart:indexEnd], ";")
version := projectContent[versionStart:versionEnd]
if version == newVersion {
continue
}
updated = true
projectContent = projectContent[:versionStart] + newVersion + projectContent[versionEnd:]
}
return projectContent, updated
}
func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
var objectKeyList []string
for objectKey, object := range objectsMap {
buildSettings := object.(map[string]any)["buildSettings"]
if buildSettings == nil {
continue
}
bundleIDObject := buildSettings.(map[string]any)["PRODUCT_BUNDLE_IDENTIFIER"]
if bundleIDObject == nil {
continue
}
if common.Contains(bundleIDList, bundleIDObject.(string)) {
objectKeyList = append(objectKeyList, objectKey)
}
}
return objectKeyList
}

View File

@@ -31,10 +31,7 @@ func check() error {
return err
}
ctx, cancel := context.WithCancel(context.Background())
instance, err := box.New(box.Options{
Context: ctx,
Options: options,
})
instance, err := box.New(ctx, options, nil)
if err == nil {
instance.Close()
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/gofrs/uuid/v5"
"github.com/gofrs/uuid"
"github.com/spf13/cobra"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)

View File

@@ -10,7 +10,6 @@ import (
"sort"
"strings"
"syscall"
"time"
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/common/badjsonmerge"
@@ -128,10 +127,7 @@ func create() (*box.Box, context.CancelFunc, error) {
options.Log.DisableColor = true
}
ctx, cancel := context.WithCancel(context.Background())
instance, err := box.New(box.Options{
Context: ctx,
Options: options,
})
instance, err := box.New(ctx, options, nil)
if err != nil {
cancel()
return nil, nil, E.Cause(err, "create service")
@@ -178,10 +174,7 @@ func run() error {
}
}
cancel()
closeCtx, closed := context.WithCancel(context.Background())
go closeMonitor(closeCtx)
instance.Close()
closed()
if osSignal != syscall.SIGHUP {
return nil
}
@@ -189,13 +182,3 @@ func run() error {
}
}
}
func closeMonitor(ctx context.Context) {
time.Sleep(3 * time.Second)
select {
case <-ctx.Done():
return
default:
}
log.Fatal("sing-box did not close!")
}

View File

@@ -1,6 +1,8 @@
package main
import (
"context"
"github.com/sagernet/sing-box"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
@@ -25,7 +27,7 @@ func createPreStartedClient() (*box.Box, error) {
if err != nil {
return nil, err
}
instance, err := box.New(box.Options{Options: options})
instance, err := box.New(context.Background(), options, nil)
if err != nil {
return nil, E.Cause(err, "create service")
}

44
cmd/sing-box/debug.go Normal file
View File

@@ -0,0 +1,44 @@
//go:build debug
package main
import (
"encoding/json"
"net/http"
_ "net/http/pprof"
"runtime"
"runtime/debug"
"github.com/sagernet/sing-box/common/badjson"
"github.com/sagernet/sing-box/log"
"github.com/dustin/go-humanize"
)
func init() {
http.HandleFunc("/debug/gc", func(writer http.ResponseWriter, request *http.Request) {
writer.WriteHeader(http.StatusNoContent)
go debug.FreeOSMemory()
})
http.HandleFunc("/debug/memory", func(writer http.ResponseWriter, request *http.Request) {
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
var memObject badjson.JSONObject
memObject.Put("heap", humanize.IBytes(memStats.HeapInuse))
memObject.Put("stack", humanize.IBytes(memStats.StackInuse))
memObject.Put("idle", humanize.IBytes(memStats.HeapIdle-memStats.HeapReleased))
memObject.Put("goroutines", runtime.NumGoroutine())
memObject.Put("rss", rusageMaxRSS())
encoder := json.NewEncoder(writer)
encoder.SetIndent("", " ")
encoder.Encode(memObject)
})
go func() {
err := http.ListenAndServe("0.0.0.0:8964", nil)
if err != nil {
log.Debug(err)
}
}()
}

View File

@@ -1,4 +1,6 @@
package box
//go:build debug
package main
import (
"runtime"

View File

@@ -1,6 +1,6 @@
//go:build !linux
//go:build debug && !linux
package box
package main
func rusageMaxRSS() float64 {
return -1

View File

@@ -55,7 +55,7 @@ func WrapQUIC(err error) error {
if err == nil {
return nil
}
if Contains(err, "canceled by local with error code 0") {
if Contains(err, "canceled with error code 0") {
return net.ErrClosed
}
return err

View File

@@ -21,7 +21,7 @@ func TestMergeJSON(t *testing.T) {
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{
Network: []string{N.NetworkTCP},
Network: N.NetworkTCP,
Outbound: "direct",
},
},
@@ -42,7 +42,7 @@ func TestMergeJSON(t *testing.T) {
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{
Network: []string{N.NetworkUDP},
Network: N.NetworkUDP,
Outbound: "direct",
},
},

View File

@@ -1,4 +1,4 @@
//go:build go1.20 && !go1.21
//go:build go1.19 && !go1.20
package badtls
@@ -14,60 +14,39 @@ import (
"sync/atomic"
"unsafe"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
aTLS "github.com/sagernet/sing/common/tls"
)
type Conn struct {
*tls.Conn
writer N.ExtendedWriter
isHandshakeComplete *atomic.Bool
activeCall *atomic.Int32
closeNotifySent *bool
version *uint16
rand io.Reader
halfAccess *sync.Mutex
halfError *error
cipher cipher.AEAD
explicitNonceLen int
halfPtr uintptr
halfSeq []byte
halfScratchBuf []byte
writer N.ExtendedWriter
activeCall *int32
closeNotifySent *bool
version *uint16
rand io.Reader
halfAccess *sync.Mutex
halfError *error
cipher cipher.AEAD
explicitNonceLen int
halfPtr uintptr
halfSeq []byte
halfScratchBuf []byte
}
func TryCreate(conn aTLS.Conn) aTLS.Conn {
tlsConn, ok := conn.(*tls.Conn)
if !ok {
return conn
}
badConn, err := Create(tlsConn)
if err != nil {
log.Warn("initialize badtls: ", err)
return conn
}
return badConn
}
func Create(conn *tls.Conn) (aTLS.Conn, error) {
rawConn := reflect.Indirect(reflect.ValueOf(conn))
rawIsHandshakeComplete := rawConn.FieldByName("isHandshakeComplete")
if !rawIsHandshakeComplete.IsValid() || rawIsHandshakeComplete.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid isHandshakeComplete")
}
isHandshakeComplete := (*atomic.Bool)(unsafe.Pointer(rawIsHandshakeComplete.UnsafeAddr()))
if !isHandshakeComplete.Load() {
func Create(conn *tls.Conn) (TLSConn, error) {
if !handshakeComplete(conn) {
return nil, E.New("handshake not finished")
}
rawConn := reflect.Indirect(reflect.ValueOf(conn))
rawActiveCall := rawConn.FieldByName("activeCall")
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Struct {
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Int32 {
return nil, E.New("badtls: invalid active call")
}
activeCall := (*atomic.Int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
activeCall := (*int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
rawHalfConn := rawConn.FieldByName("out")
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid half conn")
@@ -129,20 +108,19 @@ func Create(conn *tls.Conn) (aTLS.Conn, error) {
}
halfScratchBuf := rawHalfScratchBuf.Bytes()
return &Conn{
Conn: conn,
writer: bufio.NewExtendedWriter(conn.NetConn()),
isHandshakeComplete: isHandshakeComplete,
activeCall: activeCall,
closeNotifySent: closeNotifySent,
version: version,
halfAccess: halfAccess,
halfError: halfError,
cipher: aeadCipher,
explicitNonceLen: explicitNonceLen,
rand: randReader,
halfPtr: rawHalfConn.UnsafeAddr(),
halfSeq: halfSeq,
halfScratchBuf: halfScratchBuf,
Conn: conn,
writer: bufio.NewExtendedWriter(conn.NetConn()),
activeCall: activeCall,
closeNotifySent: closeNotifySent,
version: version,
halfAccess: halfAccess,
halfError: halfError,
cipher: aeadCipher,
explicitNonceLen: explicitNonceLen,
rand: randReader,
halfPtr: rawHalfConn.UnsafeAddr(),
halfSeq: halfSeq,
halfScratchBuf: halfScratchBuf,
}, nil
}
@@ -152,15 +130,15 @@ func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
return common.Error(c.Write(buffer.Bytes()))
}
for {
x := c.activeCall.Load()
x := atomic.LoadInt32(c.activeCall)
if x&1 != 0 {
return net.ErrClosed
}
if c.activeCall.CompareAndSwap(x, x+2) {
if atomic.CompareAndSwapInt32(c.activeCall, x, x+2) {
break
}
}
defer c.activeCall.Add(-2)
defer atomic.AddInt32(c.activeCall, -2)
c.halfAccess.Lock()
defer c.halfAccess.Unlock()
if err := *c.halfError; err != nil {
@@ -208,7 +186,6 @@ func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+c.explicitNonceLen+c.cipher.Overhead()))
}
incSeq(c.halfPtr)
log.Trace("badtls write ", buffer.Len())
return c.writer.WriteBuffer(buffer)
}

View File

@@ -1,14 +1,12 @@
//go:build !go1.19 || go1.21
//go:build !go1.19 || go1.20
package badtls
import (
"crypto/tls"
"os"
aTLS "github.com/sagernet/sing/common/tls"
)
func Create(conn *tls.Conn) (aTLS.Conn, error) {
func Create(conn *tls.Conn) (TLSConn, error) {
return nil, os.ErrInvalid
}

13
common/badtls/conn.go Normal file
View File

@@ -0,0 +1,13 @@
package badtls
import (
"context"
"crypto/tls"
"net"
)
type TLSConn interface {
net.Conn
HandshakeContext(ctx context.Context) error
ConnectionState() tls.ConnectionState
}

View File

@@ -1,8 +1,9 @@
//go:build go1.20 && !go.1.21
//go:build go1.19 && !go.1.20
package badtls
import (
"crypto/tls"
"reflect"
_ "unsafe"
)
@@ -15,6 +16,9 @@ const (
//go:linkname errShutdown crypto/tls.errShutdown
var errShutdown error
//go:linkname handshakeComplete crypto/tls.(*Conn).handshakeComplete
func handshakeComplete(conn *tls.Conn) bool
//go:linkname incSeq crypto/tls.(*halfConn).incSeq
func incSeq(conn uintptr)

View File

@@ -1,124 +0,0 @@
package badversion
import (
"strconv"
"strings"
F "github.com/sagernet/sing/common/format"
)
type Version struct {
Major int
Minor int
Patch int
Commit string
PreReleaseIdentifier string
PreReleaseVersion int
}
func (v Version) After(anotherVersion Version) bool {
if v.Major > anotherVersion.Major {
return true
} else if v.Major < anotherVersion.Major {
return false
}
if v.Minor > anotherVersion.Minor {
return true
} else if v.Minor < anotherVersion.Minor {
return false
}
if v.Patch > anotherVersion.Patch {
return true
} else if v.Patch < anotherVersion.Patch {
return false
}
if v.PreReleaseIdentifier == "" && anotherVersion.PreReleaseIdentifier != "" {
return true
} else if v.PreReleaseIdentifier != "" && anotherVersion.PreReleaseIdentifier == "" {
return false
}
if v.PreReleaseIdentifier != "" && anotherVersion.PreReleaseIdentifier != "" {
if v.PreReleaseIdentifier == anotherVersion.PreReleaseIdentifier {
if v.PreReleaseVersion > anotherVersion.PreReleaseVersion {
return true
} else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion {
return false
}
} else if v.PreReleaseIdentifier == "rc" && anotherVersion.PreReleaseIdentifier == "beta" {
return true
} else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "rc" {
return false
} else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "alpha" {
return true
} else if v.PreReleaseIdentifier == "alpha" && anotherVersion.PreReleaseIdentifier == "beta" {
return false
}
}
return false
}
func (v Version) VersionString() string {
return F.ToString(v.Major, ".", v.Minor, ".", v.Patch)
}
func (v Version) String() string {
version := F.ToString(v.Major, ".", v.Minor, ".", v.Patch)
if v.PreReleaseIdentifier != "" {
version = F.ToString(version, "-", v.PreReleaseIdentifier, ".", v.PreReleaseVersion)
}
return version
}
func (v Version) BadString() string {
version := F.ToString(v.Major, ".", v.Minor)
if v.Patch > 0 {
version = F.ToString(version, ".", v.Patch)
}
if v.PreReleaseIdentifier != "" {
version = F.ToString(version, "-", v.PreReleaseIdentifier)
if v.PreReleaseVersion > 0 {
version = F.ToString(version, v.PreReleaseVersion)
}
}
return version
}
func Parse(versionName string) (version Version) {
if strings.HasPrefix(versionName, "v") {
versionName = versionName[1:]
}
if strings.Contains(versionName, "-") {
parts := strings.Split(versionName, "-")
versionName = parts[0]
identifier := parts[1]
if strings.Contains(identifier, ".") {
identifierParts := strings.Split(identifier, ".")
version.PreReleaseIdentifier = identifierParts[0]
if len(identifierParts) >= 2 {
version.PreReleaseVersion, _ = strconv.Atoi(identifierParts[1])
}
} else {
if strings.HasPrefix(identifier, "alpha") {
version.PreReleaseIdentifier = "alpha"
version.PreReleaseVersion, _ = strconv.Atoi(identifier[5:])
} else if strings.HasPrefix(identifier, "beta") {
version.PreReleaseIdentifier = "beta"
version.PreReleaseVersion, _ = strconv.Atoi(identifier[4:])
} else {
version.Commit = identifier
}
}
}
versionElements := strings.Split(versionName, ".")
versionLen := len(versionElements)
if versionLen >= 1 {
version.Major, _ = strconv.Atoi(versionElements[0])
}
if versionLen >= 2 {
version.Minor, _ = strconv.Atoi(versionElements[1])
}
if versionLen >= 3 {
version.Patch, _ = strconv.Atoi(versionElements[2])
}
return
}

View File

@@ -1,17 +0,0 @@
package badversion
import "github.com/sagernet/sing-box/common/json"
func (v Version) MarshalJSON() ([]byte, error) {
return json.Marshal(v.String())
}
func (v *Version) UnmarshalJSON(data []byte) error {
var version string
err := json.Unmarshal(data, &version)
if err != nil {
return err
}
*v = Parse(version)
return nil
}

View File

@@ -1,18 +0,0 @@
package badversion
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestCompareVersion(t *testing.T) {
t.Parallel()
require.Equal(t, "1.3.0-beta.1", Parse("v1.3.0-beta1").String())
require.Equal(t, "1.3-beta1", Parse("v1.3.0-beta.1").BadString())
require.True(t, Parse("1.3.0").After(Parse("1.3-beta1")))
require.True(t, Parse("1.3.0").After(Parse("1.3.0-beta1")))
require.True(t, Parse("1.3.0-beta1").After(Parse("1.3.0-alpha1")))
require.True(t, Parse("1.3.1").After(Parse("1.3.0")))
require.True(t, Parse("1.4").After(Parse("1.3")))
}

View File

@@ -0,0 +1,48 @@
package canceler
import (
"context"
"time"
)
type Instance struct {
ctx context.Context
cancelFunc context.CancelFunc
timer *time.Timer
timeout time.Duration
}
func New(ctx context.Context, cancelFunc context.CancelFunc, timeout time.Duration) *Instance {
instance := &Instance{
ctx,
cancelFunc,
time.NewTimer(timeout),
timeout,
}
go instance.wait()
return instance
}
func (i *Instance) Update() bool {
if !i.timer.Stop() {
return false
}
if !i.timer.Reset(i.timeout) {
return false
}
return true
}
func (i *Instance) wait() {
select {
case <-i.timer.C:
case <-i.ctx.Done():
}
i.Close()
}
func (i *Instance) Close() error {
i.timer.Stop()
i.cancelFunc()
return nil
}

49
common/canceler/packet.go Normal file
View File

@@ -0,0 +1,49 @@
package canceler
import (
"context"
"time"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type PacketConn struct {
N.PacketConn
instance *Instance
}
func NewPacketConn(ctx context.Context, conn N.PacketConn, timeout time.Duration) (context.Context, N.PacketConn) {
ctx, cancel := context.WithCancel(ctx)
instance := New(ctx, cancel, timeout)
return ctx, &PacketConn{conn, instance}
}
func (c *PacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
destination, err = c.PacketConn.ReadPacket(buffer)
if err == nil {
c.instance.Update()
}
return
}
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
err := c.PacketConn.WritePacket(buffer, destination)
if err == nil {
c.instance.Update()
}
return err
}
func (c *PacketConn) Close() error {
return common.Close(
c.PacketConn,
c.instance,
)
}
func (c *PacketConn) Upstream() any {
return c.PacketConn
}

87
common/debugio/log.go Normal file
View File

@@ -0,0 +1,87 @@
package debugio
import (
"net"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type LogConn struct {
N.ExtendedConn
logger log.Logger
prefix string
}
func NewLogConn(conn net.Conn, logger log.Logger, prefix string) N.ExtendedConn {
return &LogConn{bufio.NewExtendedConn(conn), logger, prefix}
}
func (c *LogConn) Read(p []byte) (n int, err error) {
n, err = c.ExtendedConn.Read(p)
if n > 0 {
c.logger.Debug(c.prefix, " read ", buf.EncodeHexString(p[:n]))
}
return
}
func (c *LogConn) Write(p []byte) (n int, err error) {
c.logger.Debug(c.prefix, " write ", buf.EncodeHexString(p))
return c.ExtendedConn.Write(p)
}
func (c *LogConn) ReadBuffer(buffer *buf.Buffer) error {
err := c.ExtendedConn.ReadBuffer(buffer)
if err == nil {
c.logger.Debug(c.prefix, " read buffer ", buf.EncodeHexString(buffer.Bytes()))
}
return err
}
func (c *LogConn) WriteBuffer(buffer *buf.Buffer) error {
c.logger.Debug(c.prefix, " write buffer ", buf.EncodeHexString(buffer.Bytes()))
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *LogConn) Upstream() any {
return c.ExtendedConn
}
type LogPacketConn struct {
N.NetPacketConn
logger log.Logger
prefix string
}
func NewLogPacketConn(conn net.PacketConn, logger log.Logger, prefix string) N.NetPacketConn {
return &LogPacketConn{bufio.NewPacketConn(conn), logger, prefix}
}
func (c *LogPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
n, addr, err = c.NetPacketConn.ReadFrom(p)
if n > 0 {
c.logger.Debug(c.prefix, " read from ", addr, " ", buf.EncodeHexString(p[:n]))
}
return
}
func (c *LogPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.logger.Debug(c.prefix, " write to ", addr, " ", buf.EncodeHexString(p))
return c.NetPacketConn.WriteTo(p, addr)
}
func (c *LogPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
destination, err = c.NetPacketConn.ReadPacket(buffer)
if err == nil {
c.logger.Debug(c.prefix, " read packet from ", destination, " ", buf.EncodeHexString(buffer.Bytes()))
}
return
}
func (c *LogPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
c.logger.Debug(c.prefix, " write packet to ", destination, " ", buf.EncodeHexString(buffer.Bytes()))
return c.NetPacketConn.WritePacket(buffer, destination)
}

19
common/debugio/print.go Normal file
View File

@@ -0,0 +1,19 @@
package debugio
import (
"fmt"
"reflect"
"github.com/sagernet/sing/common"
)
func PrintUpstream(obj any) {
for obj != nil {
fmt.Println(reflect.TypeOf(obj))
if u, ok := obj.(common.WithUpstream); !ok {
break
} else {
obj = u.Upstream()
}
}
}

48
common/debugio/race.go Normal file
View File

@@ -0,0 +1,48 @@
package debugio
import (
"net"
"sync"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
N "github.com/sagernet/sing/common/network"
)
type RaceConn struct {
N.ExtendedConn
readAccess sync.Mutex
writeAccess sync.Mutex
}
func NewRaceConn(conn net.Conn) N.ExtendedConn {
return &RaceConn{ExtendedConn: bufio.NewExtendedConn(conn)}
}
func (c *RaceConn) Read(p []byte) (n int, err error) {
c.readAccess.Lock()
defer c.readAccess.Unlock()
return c.ExtendedConn.Read(p)
}
func (c *RaceConn) Write(p []byte) (n int, err error) {
c.writeAccess.Lock()
defer c.writeAccess.Unlock()
return c.ExtendedConn.Write(p)
}
func (c *RaceConn) ReadBuffer(buffer *buf.Buffer) error {
c.readAccess.Lock()
defer c.readAccess.Unlock()
return c.ExtendedConn.ReadBuffer(buffer)
}
func (c *RaceConn) WriteBuffer(buffer *buf.Buffer) error {
c.writeAccess.Lock()
defer c.writeAccess.Unlock()
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *RaceConn) Upstream() any {
return c.ExtendedConn
}

View File

@@ -12,7 +12,7 @@ type Conn struct {
element *list.Element[io.Closer]
}
func NewConn(conn net.Conn) (net.Conn, error) {
func NewConn(conn net.Conn) (*Conn, error) {
connAccess.Lock()
element := openConnection.PushBack(conn)
connAccess.Unlock()

View File

@@ -4,7 +4,6 @@ import (
"io"
"net"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/x/list"
)
@@ -13,7 +12,7 @@ type PacketConn struct {
element *list.Element[io.Closer]
}
func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
func NewPacketConn(conn net.PacketConn) (*PacketConn, error) {
connAccess.Lock()
element := openConnection.PushBack(conn)
connAccess.Unlock()
@@ -43,7 +42,7 @@ func (c *PacketConn) Close() error {
}
func (c *PacketConn) Upstream() any {
return bufio.NewPacketConn(c.PacketConn)
return c.PacketConn
}
func (c *PacketConn) ReaderReplaceable() bool {

View File

@@ -14,16 +14,10 @@ var (
)
func Count() int {
if !Enabled {
return 0
}
return openConnection.Len()
}
func List() []io.Closer {
if !Enabled {
return nil
}
connAccess.RLock()
defer connAccess.RUnlock()
connList := make([]io.Closer, 0, openConnection.Len())
@@ -34,9 +28,6 @@ func List() []io.Closer {
}
func Close() {
if !Enabled {
return
}
connAccess.Lock()
defer connAccess.Unlock()
for element := openConnection.Front(); element != nil; element = element.Next() {

View File

@@ -7,17 +7,54 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer/conntrack"
"github.com/sagernet/sing-box/common/warning"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/tfo-go"
)
var warnBindInterfaceOnUnsupportedPlatform = warning.New(
func() bool {
return !(C.IsLinux || C.IsWindows || C.IsDarwin)
},
"outbound option `bind_interface` is only supported on Linux and Windows",
)
var warnRoutingMarkOnUnsupportedPlatform = warning.New(
func() bool {
return !C.IsLinux
},
"outbound option `routing_mark` is only supported on Linux",
)
var warnReuseAdderOnUnsupportedPlatform = warning.New(
func() bool {
return !(C.IsDarwin || C.IsDragonfly || C.IsFreebsd || C.IsLinux || C.IsNetbsd || C.IsOpenbsd || C.IsSolaris || C.IsWindows)
},
"outbound option `reuse_addr` is unsupported on current platform",
)
var warnProtectPathOnNonAndroid = warning.New(
func() bool {
return !C.IsAndroid
},
"outbound option `protect_path` is only supported on Android",
)
var warnTFOOnUnsupportedPlatform = warning.New(
func() bool {
return !(C.IsDarwin || C.IsFreebsd || C.IsLinux || C.IsWindows)
},
"outbound option `tcp_fast_open` is unsupported on current platform",
)
type DefaultDialer struct {
dialer4 tcpDialer
dialer6 tcpDialer
dialer4 tfo.Dialer
dialer6 tfo.Dialer
udpDialer4 net.Dialer
udpDialer6 net.Dialer
udpListener net.ListenConfig
@@ -25,10 +62,11 @@ type DefaultDialer struct {
udpAddr6 string
}
func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDialer, error) {
func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDialer {
var dialer net.Dialer
var listener net.ListenConfig
if options.BindInterface != "" {
warnBindInterfaceOnUnsupportedPlatform.Check()
bindFunc := control.BindToInterface(router.InterfaceFinder(), options.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
@@ -42,6 +80,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
listener.Control = control.Append(listener.Control, bindFunc)
}
if options.RoutingMark != 0 {
warnRoutingMarkOnUnsupportedPlatform.Check()
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
} else if router.DefaultMark() != 0 {
@@ -49,9 +88,11 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
}
if options.ReuseAddr {
warnReuseAdderOnUnsupportedPlatform.Check()
listener.Control = control.Append(listener.Control, control.ReuseAddr())
}
if options.ProtectPath != "" {
warnProtectPathOnNonAndroid.Check()
dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath))
listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath))
}
@@ -60,6 +101,9 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
} else {
dialer.Timeout = C.TCPTimeout
}
if options.TCPFastOpen {
warnTFOOnUnsupportedPlatform.Check()
}
var udpFragment bool
if options.UDPFragment != nil {
udpFragment = *options.UDPFragment
@@ -92,29 +136,15 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
}
if options.TCPMultiPath {
if !go121Available {
return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")
}
setMultiPathTCP(&dialer4)
}
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
if err != nil {
return nil, err
}
tcpDialer6, err := newTCPDialer(dialer6, options.TCPFastOpen)
if err != nil {
return nil, err
}
return &DefaultDialer{
tcpDialer4,
tcpDialer6,
tfo.Dialer{Dialer: dialer4, DisableTFO: !options.TCPFastOpen},
tfo.Dialer{Dialer: dialer6, DisableTFO: !options.TCPFastOpen},
udpDialer4,
udpDialer6,
listener,
udpAddr4,
udpAddr6,
}, nil
}
}
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
@@ -124,9 +154,9 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
switch N.NetworkName(network) {
case N.NetworkUDP:
if !address.IsIPv6() {
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
return d.udpDialer4.DialContext(ctx, network, address.String())
} else {
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
return d.udpDialer6.DialContext(ctx, network, address.String())
}
}
if !address.IsIPv6() {

View File

@@ -1,15 +0,0 @@
//go:build go1.20
package dialer
import (
"net"
"github.com/sagernet/tfo-go"
)
type tcpDialer = tfo.Dialer
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil
}

View File

@@ -1,11 +0,0 @@
//go:build go1.21
package dialer
import "net"
const go121Available = true
func setMultiPathTCP(dialer *net.Dialer) {
dialer.SetMultipathTCP(true)
}

View File

@@ -1,18 +0,0 @@
//go:build !go1.20
package dialer
import (
"net"
E "github.com/sagernet/sing/common/exceptions"
)
type tcpDialer = net.Dialer
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
if tfoEnabled {
return dialer, E.New("TCP Fast Open requires go1.20, please recompile your binary.")
}
return dialer, nil
}

View File

@@ -1,12 +0,0 @@
//go:build !go1.21
package dialer
import (
"net"
)
const go121Available = false
func setMultiPathTCP(dialer *net.Dialer) {
}

View File

@@ -6,24 +6,13 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common"
N "github.com/sagernet/sing/common/network"
)
func MustNew(router adapter.Router, options option.DialerOptions) N.Dialer {
return common.Must1(New(router, options))
}
func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) {
var (
dialer N.Dialer
err error
)
func New(router adapter.Router, options option.DialerOptions) N.Dialer {
var dialer N.Dialer
if options.Detour == "" {
dialer, err = NewDefault(router, options)
if err != nil {
return nil, err
}
dialer = NewDefault(router, options)
} else {
dialer = NewDetour(router, options.Detour)
}
@@ -31,5 +20,5 @@ func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error)
if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" {
dialer = NewResolveDialer(router, dialer, domainStrategy, time.Duration(options.FallbackDelay))
}
return dialer, nil
return dialer
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
@@ -69,11 +68,11 @@ func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
if err != nil {
return nil, err
}
conn, destinationAddress, err := N.ListenSerial(ctx, d.dialer, destination, addresses)
conn, err := N.ListenSerial(ctx, d.dialer, destination, addresses)
if err != nil {
return nil, err
}
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
return NewResolvePacketConn(ctx, d.router, d.strategy, conn), nil
}
func (d *ResolveDialer) Upstream() any {

View File

@@ -0,0 +1,84 @@
package dialer
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func NewResolvePacketConn(ctx context.Context, router adapter.Router, strategy dns.DomainStrategy, conn net.PacketConn) N.NetPacketConn {
if udpConn, ok := conn.(*net.UDPConn); ok {
return &ResolveUDPConn{udpConn, ctx, router, strategy}
} else {
return &ResolvePacketConn{conn, ctx, router, strategy}
}
}
type ResolveUDPConn struct {
*net.UDPConn
ctx context.Context
router adapter.Router
strategy dns.DomainStrategy
}
func (w *ResolveUDPConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {
n, addr, err := w.ReadFromUDPAddrPort(buffer.FreeBytes())
if err != nil {
return M.Socksaddr{}, err
}
buffer.Truncate(n)
return M.SocksaddrFromNetIP(addr), nil
}
func (w *ResolveUDPConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
defer buffer.Release()
if destination.IsFqdn() {
addresses, err := w.router.Lookup(w.ctx, destination.Fqdn, w.strategy)
if err != nil {
return err
}
return common.Error(w.UDPConn.WriteToUDPAddrPort(buffer.Bytes(), M.SocksaddrFrom(addresses[0], destination.Port).AddrPort()))
}
return common.Error(w.UDPConn.WriteToUDPAddrPort(buffer.Bytes(), destination.AddrPort()))
}
func (w *ResolveUDPConn) Upstream() any {
return w.UDPConn
}
type ResolvePacketConn struct {
net.PacketConn
ctx context.Context
router adapter.Router
strategy dns.DomainStrategy
}
func (w *ResolvePacketConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {
_, addr, err := buffer.ReadPacketFrom(w)
if err != nil {
return M.Socksaddr{}, err
}
return M.SocksaddrFromNet(addr), err
}
func (w *ResolvePacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
defer buffer.Release()
if destination.IsFqdn() {
addresses, err := w.router.Lookup(w.ctx, destination.Fqdn, w.strategy)
if err != nil {
return err
}
return common.Error(w.WriteTo(buffer.Bytes(), M.SocksaddrFrom(addresses[0], destination.Port).UDPAddr()))
}
return common.Error(w.WriteTo(buffer.Bytes(), destination.UDPAddr()))
}
func (w *ResolvePacketConn) Upstream() any {
return w.PacketConn
}

View File

@@ -1,5 +1,3 @@
//go:build go1.20
package dialer
import (
@@ -27,7 +25,7 @@ type slowOpenConn struct {
err error
}
func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
switch N.NetworkName(network) {
case N.NetworkTCP, N.NetworkUDP:
@@ -63,7 +61,6 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
if c.conn == nil {
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
if err != nil {
c.conn = nil
c.err = E.Cause(err, "dial tcp fast open")
}
close(c.create)
@@ -127,8 +124,11 @@ func (c *slowOpenConn) LazyHeadroom() bool {
return c.conn == nil
}
func (c *slowOpenConn) NeedHandshake() bool {
return c.conn == nil
func (c *slowOpenConn) ReadFrom(r io.Reader) (n int64, err error) {
if c.conn != nil {
return bufio.Copy(c.conn, r)
}
return bufio.ReadFrom0(c, r)
}
func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {

View File

@@ -1,20 +0,0 @@
//go:build !go1.20
package dialer
import (
"context"
"net"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
switch N.NetworkName(network) {
case N.NetworkTCP, N.NetworkUDP:
return dialer.DialContext(ctx, network, destination.String())
default:
return dialer.DialContext(ctx, network, destination.AddrString())
}
}

View File

@@ -1,21 +1,519 @@
package mux
import (
"context"
"encoding/binary"
"io"
"net"
"sync"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-mux"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list"
)
func NewClientWithOptions(dialer N.Dialer, options option.MultiplexOptions) (*Client, error) {
var _ N.Dialer = (*Client)(nil)
type Client struct {
access sync.Mutex
connections list.List[abstractSession]
ctx context.Context
dialer N.Dialer
protocol Protocol
maxConnections int
minStreams int
maxStreams int
}
func NewClient(ctx context.Context, dialer N.Dialer, protocol Protocol, maxConnections int, minStreams int, maxStreams int) *Client {
return &Client{
ctx: ctx,
dialer: dialer,
protocol: protocol,
maxConnections: maxConnections,
minStreams: minStreams,
maxStreams: maxStreams,
}
}
func NewClientWithOptions(ctx context.Context, dialer N.Dialer, options option.MultiplexOptions) (N.Dialer, error) {
if !options.Enabled {
return nil, nil
}
return mux.NewClient(mux.Options{
Dialer: dialer,
Protocol: options.Protocol,
MaxConnections: options.MaxConnections,
MinStreams: options.MinStreams,
MaxStreams: options.MaxStreams,
Padding: options.Padding,
})
if options.MaxConnections == 0 && options.MaxStreams == 0 {
options.MinStreams = 8
}
protocol, err := ParseProtocol(options.Protocol)
if err != nil {
return nil, err
}
return NewClient(ctx, dialer, protocol, options.MaxConnections, options.MinStreams, options.MaxStreams), nil
}
func (c *Client) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
switch N.NetworkName(network) {
case N.NetworkTCP:
stream, err := c.openStream()
if err != nil {
return nil, err
}
return &ClientConn{Conn: stream, destination: destination}, nil
case N.NetworkUDP:
stream, err := c.openStream()
if err != nil {
return nil, err
}
return bufio.NewUnbindPacketConn(&ClientPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}), nil
default:
return nil, E.Extend(N.ErrUnknownNetwork, network)
}
}
func (c *Client) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
stream, err := c.openStream()
if err != nil {
return nil, err
}
return &ClientPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}, nil
}
func (c *Client) openStream() (net.Conn, error) {
var (
session abstractSession
stream net.Conn
err error
)
for attempts := 0; attempts < 2; attempts++ {
session, err = c.offer()
if err != nil {
continue
}
stream, err = session.Open()
if err != nil {
continue
}
break
}
if err != nil {
return nil, err
}
return &wrapStream{stream}, nil
}
func (c *Client) offer() (abstractSession, error) {
c.access.Lock()
defer c.access.Unlock()
sessions := make([]abstractSession, 0, c.maxConnections)
for element := c.connections.Front(); element != nil; {
if element.Value.IsClosed() {
nextElement := element.Next()
c.connections.Remove(element)
element = nextElement
continue
}
sessions = append(sessions, element.Value)
element = element.Next()
}
sLen := len(sessions)
if sLen == 0 {
return c.offerNew()
}
session := common.MinBy(sessions, abstractSession.NumStreams)
numStreams := session.NumStreams()
if numStreams == 0 {
return session, nil
}
if c.maxConnections > 0 {
if sLen >= c.maxConnections || numStreams < c.minStreams {
return session, nil
}
} else {
if c.maxStreams > 0 && numStreams < c.maxStreams {
return session, nil
}
}
return c.offerNew()
}
func (c *Client) offerNew() (abstractSession, error) {
conn, err := c.dialer.DialContext(c.ctx, N.NetworkTCP, Destination)
if err != nil {
return nil, err
}
if vectorisedWriter, isVectorised := bufio.CreateVectorisedWriter(conn); isVectorised {
conn = &vectorisedProtocolConn{protocolConn{Conn: conn, protocol: c.protocol}, vectorisedWriter}
} else {
conn = &protocolConn{Conn: conn, protocol: c.protocol}
}
session, err := c.protocol.newClient(conn)
if err != nil {
return nil, err
}
c.connections.PushBack(session)
return session, nil
}
func (c *Client) Close() error {
c.access.Lock()
defer c.access.Unlock()
for _, session := range c.connections.Array() {
session.Close()
}
return nil
}
type ClientConn struct {
net.Conn
destination M.Socksaddr
requestWrite bool
responseRead bool
}
func (c *ClientConn) readResponse() error {
response, err := ReadStreamResponse(c.Conn)
if err != nil {
return err
}
if response.Status == statusError {
return E.New("remote error: ", response.Message)
}
return nil
}
func (c *ClientConn) Read(b []byte) (n int, err error) {
if !c.responseRead {
err = c.readResponse()
if err != nil {
return
}
c.responseRead = true
}
return c.Conn.Read(b)
}
func (c *ClientConn) Write(b []byte) (n int, err error) {
if c.requestWrite {
return c.Conn.Write(b)
}
request := StreamRequest{
Network: N.NetworkTCP,
Destination: c.destination,
}
_buffer := buf.StackNewSize(requestLen(request) + len(b))
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
EncodeStreamRequest(request, buffer)
buffer.Write(b)
_, err = c.Conn.Write(buffer.Bytes())
if err != nil {
return
}
c.requestWrite = true
return len(b), nil
}
func (c *ClientConn) ReadFrom(r io.Reader) (n int64, err error) {
if !c.requestWrite {
return bufio.ReadFrom0(c, r)
}
return bufio.Copy(c.Conn, r)
}
func (c *ClientConn) WriteTo(w io.Writer) (n int64, err error) {
if !c.responseRead {
return bufio.WriteTo0(c, w)
}
return bufio.Copy(w, c.Conn)
}
func (c *ClientConn) LocalAddr() net.Addr {
return c.Conn.LocalAddr()
}
func (c *ClientConn) RemoteAddr() net.Addr {
return c.destination.TCPAddr()
}
func (c *ClientConn) ReaderReplaceable() bool {
return c.responseRead
}
func (c *ClientConn) WriterReplaceable() bool {
return c.requestWrite
}
func (c *ClientConn) Upstream() any {
return c.Conn
}
type ClientPacketConn struct {
N.ExtendedConn
destination M.Socksaddr
requestWrite bool
responseRead bool
}
func (c *ClientPacketConn) readResponse() error {
response, err := ReadStreamResponse(c.ExtendedConn)
if err != nil {
return err
}
if response.Status == statusError {
return E.New("remote error: ", response.Message)
}
return nil
}
func (c *ClientPacketConn) Read(b []byte) (n int, err error) {
if !c.responseRead {
err = c.readResponse()
if err != nil {
return
}
c.responseRead = true
}
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
if cap(b) < int(length) {
return 0, io.ErrShortBuffer
}
return io.ReadFull(c.ExtendedConn, b[:length])
}
func (c *ClientPacketConn) writeRequest(payload []byte) (n int, err error) {
request := StreamRequest{
Network: N.NetworkUDP,
Destination: c.destination,
}
rLen := requestLen(request)
if len(payload) > 0 {
rLen += 2 + len(payload)
}
_buffer := buf.StackNewSize(rLen)
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
EncodeStreamRequest(request, buffer)
if len(payload) > 0 {
common.Must(
binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
common.Error(buffer.Write(payload)),
)
}
_, err = c.ExtendedConn.Write(buffer.Bytes())
if err != nil {
return
}
c.requestWrite = true
return len(payload), nil
}
func (c *ClientPacketConn) Write(b []byte) (n int, err error) {
if !c.requestWrite {
return c.writeRequest(b)
}
err = binary.Write(c.ExtendedConn, binary.BigEndian, uint16(len(b)))
if err != nil {
return
}
return c.ExtendedConn.Write(b)
}
func (c *ClientPacketConn) ReadBuffer(buffer *buf.Buffer) (err error) {
if !c.responseRead {
err = c.readResponse()
if err != nil {
return
}
c.responseRead = true
}
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
return
}
func (c *ClientPacketConn) WriteBuffer(buffer *buf.Buffer) error {
if !c.requestWrite {
defer buffer.Release()
return common.Error(c.writeRequest(buffer.Bytes()))
}
bLen := buffer.Len()
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(bLen))
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ClientPacketConn) FrontHeadroom() int {
return 2
}
func (c *ClientPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
err = c.ReadBuffer(buffer)
return
}
func (c *ClientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
return c.WriteBuffer(buffer)
}
func (c *ClientPacketConn) LocalAddr() net.Addr {
return c.ExtendedConn.LocalAddr()
}
func (c *ClientPacketConn) RemoteAddr() net.Addr {
return c.destination.UDPAddr()
}
func (c *ClientPacketConn) Upstream() any {
return c.ExtendedConn
}
var _ N.NetPacketConn = (*ClientPacketAddrConn)(nil)
type ClientPacketAddrConn struct {
N.ExtendedConn
destination M.Socksaddr
requestWrite bool
responseRead bool
}
func (c *ClientPacketAddrConn) readResponse() error {
response, err := ReadStreamResponse(c.ExtendedConn)
if err != nil {
return err
}
if response.Status == statusError {
return E.New("remote error: ", response.Message)
}
return nil
}
func (c *ClientPacketAddrConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if !c.responseRead {
err = c.readResponse()
if err != nil {
return
}
c.responseRead = true
}
destination, err := M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
if err != nil {
return
}
addr = destination.UDPAddr()
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
if cap(p) < int(length) {
return 0, nil, io.ErrShortBuffer
}
n, err = io.ReadFull(c.ExtendedConn, p[:length])
return
}
func (c *ClientPacketAddrConn) writeRequest(payload []byte, destination M.Socksaddr) (n int, err error) {
request := StreamRequest{
Network: N.NetworkUDP,
Destination: c.destination,
PacketAddr: true,
}
rLen := requestLen(request)
if len(payload) > 0 {
rLen += M.SocksaddrSerializer.AddrPortLen(destination) + 2 + len(payload)
}
_buffer := buf.StackNewSize(rLen)
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
EncodeStreamRequest(request, buffer)
if len(payload) > 0 {
common.Must(
M.SocksaddrSerializer.WriteAddrPort(buffer, destination),
binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
common.Error(buffer.Write(payload)),
)
}
_, err = c.ExtendedConn.Write(buffer.Bytes())
if err != nil {
return
}
c.requestWrite = true
return len(payload), nil
}
func (c *ClientPacketAddrConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if !c.requestWrite {
return c.writeRequest(p, M.SocksaddrFromNet(addr))
}
err = M.SocksaddrSerializer.WriteAddrPort(c.ExtendedConn, M.SocksaddrFromNet(addr))
if err != nil {
return
}
err = binary.Write(c.ExtendedConn, binary.BigEndian, uint16(len(p)))
if err != nil {
return
}
return c.ExtendedConn.Write(p)
}
func (c *ClientPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
if !c.responseRead {
err = c.readResponse()
if err != nil {
return
}
c.responseRead = true
}
destination, err = M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
if err != nil {
return
}
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
return
}
func (c *ClientPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
if !c.requestWrite {
defer buffer.Release()
return common.Error(c.writeRequest(buffer.Bytes(), destination))
}
bLen := buffer.Len()
header := buf.With(buffer.ExtendHeader(M.SocksaddrSerializer.AddrPortLen(destination) + 2))
common.Must(
M.SocksaddrSerializer.WriteAddrPort(header, destination),
binary.Write(header, binary.BigEndian, uint16(bLen)),
)
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ClientPacketAddrConn) LocalAddr() net.Addr {
return c.ExtendedConn.LocalAddr()
}
func (c *ClientPacketAddrConn) FrontHeadroom() int {
return 2 + M.MaxSocksaddrLength
}
func (c *ClientPacketAddrConn) Upstream() any {
return c.ExtendedConn
}

View File

@@ -1,14 +1,240 @@
package mux
import (
"github.com/sagernet/sing-mux"
"encoding/binary"
"io"
"net"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/smux"
"github.com/hashicorp/yamux"
)
type (
Client = mux.Client
var Destination = M.Socksaddr{
Fqdn: "sp.mux.sing-box.arpa",
Port: 444,
}
const (
ProtocolSMux Protocol = iota
ProtocolYAMux
)
var (
Destination = mux.Destination
HandleConnection = mux.HandleConnection
type Protocol byte
func ParseProtocol(name string) (Protocol, error) {
switch name {
case "", "smux":
return ProtocolSMux, nil
case "yamux":
return ProtocolYAMux, nil
default:
return ProtocolYAMux, E.New("unknown multiplex protocol: ", name)
}
}
func (p Protocol) newServer(conn net.Conn) (abstractSession, error) {
switch p {
case ProtocolSMux:
session, err := smux.Server(conn, smuxConfig())
if err != nil {
return nil, err
}
return &smuxSession{session}, nil
case ProtocolYAMux:
return yamux.Server(conn, yaMuxConfig())
default:
panic("unknown protocol")
}
}
func (p Protocol) newClient(conn net.Conn) (abstractSession, error) {
switch p {
case ProtocolSMux:
session, err := smux.Client(conn, smuxConfig())
if err != nil {
return nil, err
}
return &smuxSession{session}, nil
case ProtocolYAMux:
return yamux.Client(conn, yaMuxConfig())
default:
panic("unknown protocol")
}
}
func smuxConfig() *smux.Config {
config := smux.DefaultConfig()
config.KeepAliveDisabled = true
return config
}
func yaMuxConfig() *yamux.Config {
config := yamux.DefaultConfig()
config.LogOutput = io.Discard
config.StreamCloseTimeout = C.TCPTimeout
config.StreamOpenTimeout = C.TCPTimeout
return config
}
func (p Protocol) String() string {
switch p {
case ProtocolSMux:
return "smux"
case ProtocolYAMux:
return "yamux"
default:
return "unknown"
}
}
const (
version0 = 0
)
type Request struct {
Protocol Protocol
}
func ReadRequest(reader io.Reader) (*Request, error) {
version, err := rw.ReadByte(reader)
if err != nil {
return nil, err
}
if version != version0 {
return nil, E.New("unsupported version: ", version)
}
protocol, err := rw.ReadByte(reader)
if err != nil {
return nil, err
}
if protocol > byte(ProtocolYAMux) {
return nil, E.New("unsupported protocol: ", protocol)
}
return &Request{Protocol: Protocol(protocol)}, nil
}
func EncodeRequest(buffer *buf.Buffer, request Request) {
buffer.WriteByte(version0)
buffer.WriteByte(byte(request.Protocol))
}
const (
flagUDP = 1
flagAddr = 2
statusSuccess = 0
statusError = 1
)
type StreamRequest struct {
Network string
Destination M.Socksaddr
PacketAddr bool
}
func ReadStreamRequest(reader io.Reader) (*StreamRequest, error) {
var flags uint16
err := binary.Read(reader, binary.BigEndian, &flags)
if err != nil {
return nil, err
}
destination, err := M.SocksaddrSerializer.ReadAddrPort(reader)
if err != nil {
return nil, err
}
var network string
var udpAddr bool
if flags&flagUDP == 0 {
network = N.NetworkTCP
} else {
network = N.NetworkUDP
udpAddr = flags&flagAddr != 0
}
return &StreamRequest{network, destination, udpAddr}, nil
}
func requestLen(request StreamRequest) int {
var rLen int
rLen += 1 // version
rLen += 2 // flags
rLen += M.SocksaddrSerializer.AddrPortLen(request.Destination)
return rLen
}
func EncodeStreamRequest(request StreamRequest, buffer *buf.Buffer) {
destination := request.Destination
var flags uint16
if request.Network == N.NetworkUDP {
flags |= flagUDP
}
if request.PacketAddr {
flags |= flagAddr
if !destination.IsValid() {
destination = Destination
}
}
common.Must(
binary.Write(buffer, binary.BigEndian, flags),
M.SocksaddrSerializer.WriteAddrPort(buffer, destination),
)
}
type StreamResponse struct {
Status uint8
Message string
}
func ReadStreamResponse(reader io.Reader) (*StreamResponse, error) {
var response StreamResponse
status, err := rw.ReadByte(reader)
if err != nil {
return nil, err
}
response.Status = status
if status == statusError {
response.Message, err = rw.ReadVString(reader)
if err != nil {
return nil, err
}
}
return &response, nil
}
type wrapStream struct {
net.Conn
}
func (w *wrapStream) Read(p []byte) (n int, err error) {
n, err = w.Conn.Read(p)
err = wrapError(err)
return
}
func (w *wrapStream) Write(p []byte) (n int, err error) {
n, err = w.Conn.Write(p)
err = wrapError(err)
return
}
func (w *wrapStream) WriteIsThreadUnsafe() {
}
func (w *wrapStream) Upstream() any {
return w.Conn
}
func wrapError(err error) error {
switch err {
case yamux.ErrStreamClosed:
return io.EOF
default:
return err
}
}

257
common/mux/service.go Normal file
View File

@@ -0,0 +1,257 @@
package mux
import (
"context"
"encoding/binary"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/task"
)
func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, conn net.Conn, metadata adapter.InboundContext) error {
request, err := ReadRequest(conn)
if err != nil {
return err
}
session, err := request.Protocol.newServer(conn)
if err != nil {
return err
}
var group task.Group
group.Append0(func(ctx context.Context) error {
var stream net.Conn
for {
stream, err = session.Accept()
if err != nil {
return err
}
go newConnection(ctx, router, errorHandler, logger, stream, metadata)
}
})
group.Cleanup(func() {
session.Close()
})
return group.Run(ctx)
}
func newConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, stream net.Conn, metadata adapter.InboundContext) {
stream = &wrapStream{stream}
request, err := ReadStreamRequest(stream)
if err != nil {
logger.ErrorContext(ctx, err)
return
}
metadata.Destination = request.Destination
if request.Network == N.NetworkTCP {
logger.InfoContext(ctx, "inbound multiplex connection to ", metadata.Destination)
hErr := router.RouteConnection(ctx, &ServerConn{ExtendedConn: bufio.NewExtendedConn(stream)}, metadata)
stream.Close()
if hErr != nil {
errorHandler.NewError(ctx, hErr)
}
} else {
var packetConn N.PacketConn
if !request.PacketAddr {
logger.InfoContext(ctx, "inbound multiplex packet connection to ", metadata.Destination)
packetConn = &ServerPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: request.Destination}
} else {
logger.InfoContext(ctx, "inbound multiplex packet connection")
packetConn = &ServerPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream)}
}
hErr := router.RoutePacketConnection(ctx, packetConn, metadata)
stream.Close()
if hErr != nil {
errorHandler.NewError(ctx, hErr)
}
}
}
var _ N.HandshakeConn = (*ServerConn)(nil)
type ServerConn struct {
N.ExtendedConn
responseWrite bool
}
func (c *ServerConn) HandshakeFailure(err error) error {
errMessage := err.Error()
_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
common.Must(
buffer.WriteByte(statusError),
rw.WriteVString(_buffer, errMessage),
)
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ServerConn) Write(b []byte) (n int, err error) {
if c.responseWrite {
return c.ExtendedConn.Write(b)
}
_buffer := buf.StackNewSize(1 + len(b))
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
common.Must(
buffer.WriteByte(statusSuccess),
common.Error(buffer.Write(b)),
)
_, err = c.ExtendedConn.Write(buffer.Bytes())
if err != nil {
return
}
c.responseWrite = true
return len(b), nil
}
func (c *ServerConn) WriteBuffer(buffer *buf.Buffer) error {
if c.responseWrite {
return c.ExtendedConn.WriteBuffer(buffer)
}
buffer.ExtendHeader(1)[0] = statusSuccess
c.responseWrite = true
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ServerConn) FrontHeadroom() int {
if !c.responseWrite {
return 1
}
return 0
}
func (c *ServerConn) Upstream() any {
return c.ExtendedConn
}
var (
_ N.HandshakeConn = (*ServerPacketConn)(nil)
_ N.PacketConn = (*ServerPacketConn)(nil)
)
type ServerPacketConn struct {
N.ExtendedConn
destination M.Socksaddr
responseWrite bool
}
func (c *ServerPacketConn) HandshakeFailure(err error) error {
errMessage := err.Error()
_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
common.Must(
buffer.WriteByte(statusError),
rw.WriteVString(_buffer, errMessage),
)
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ServerPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
if err != nil {
return
}
destination = c.destination
return
}
func (c *ServerPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
pLen := buffer.Len()
common.Must(binary.Write(buf.With(buffer.ExtendHeader(2)), binary.BigEndian, uint16(pLen)))
if !c.responseWrite {
buffer.ExtendHeader(1)[0] = statusSuccess
c.responseWrite = true
}
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ServerPacketConn) Upstream() any {
return c.ExtendedConn
}
func (c *ServerPacketConn) FrontHeadroom() int {
if !c.responseWrite {
return 3
}
return 2
}
var (
_ N.HandshakeConn = (*ServerPacketAddrConn)(nil)
_ N.PacketConn = (*ServerPacketAddrConn)(nil)
)
type ServerPacketAddrConn struct {
N.ExtendedConn
responseWrite bool
}
func (c *ServerPacketAddrConn) HandshakeFailure(err error) error {
errMessage := err.Error()
_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
common.Must(
buffer.WriteByte(statusError),
rw.WriteVString(_buffer, errMessage),
)
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ServerPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
destination, err = M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
if err != nil {
return
}
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
if err != nil {
return
}
return
}
func (c *ServerPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
pLen := buffer.Len()
common.Must(binary.Write(buf.With(buffer.ExtendHeader(2)), binary.BigEndian, uint16(pLen)))
common.Must(M.SocksaddrSerializer.WriteAddrPort(buf.With(buffer.ExtendHeader(M.SocksaddrSerializer.AddrPortLen(destination))), destination))
if !c.responseWrite {
buffer.ExtendHeader(1)[0] = statusSuccess
c.responseWrite = true
}
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ServerPacketAddrConn) Upstream() any {
return c.ExtendedConn
}
func (c *ServerPacketAddrConn) FrontHeadroom() int {
if !c.responseWrite {
return 3 + M.MaxSocksaddrLength
}
return 2 + M.MaxSocksaddrLength
}

91
common/mux/session.go Normal file
View File

@@ -0,0 +1,91 @@
package mux
import (
"io"
"net"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/smux"
)
type abstractSession interface {
Open() (net.Conn, error)
Accept() (net.Conn, error)
NumStreams() int
Close() error
IsClosed() bool
}
var _ abstractSession = (*smuxSession)(nil)
type smuxSession struct {
*smux.Session
}
func (s *smuxSession) Open() (net.Conn, error) {
return s.OpenStream()
}
func (s *smuxSession) Accept() (net.Conn, error) {
return s.AcceptStream()
}
type protocolConn struct {
net.Conn
protocol Protocol
protocolWritten bool
}
func (c *protocolConn) Write(p []byte) (n int, err error) {
if c.protocolWritten {
return c.Conn.Write(p)
}
_buffer := buf.StackNewSize(2 + len(p))
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
EncodeRequest(buffer, Request{
Protocol: c.protocol,
})
common.Must(common.Error(buffer.Write(p)))
n, err = c.Conn.Write(buffer.Bytes())
if err == nil {
n--
}
c.protocolWritten = true
return n, err
}
func (c *protocolConn) ReadFrom(r io.Reader) (n int64, err error) {
if !c.protocolWritten {
return bufio.ReadFrom0(c, r)
}
return bufio.Copy(c.Conn, r)
}
func (c *protocolConn) Upstream() any {
return c.Conn
}
type vectorisedProtocolConn struct {
protocolConn
N.VectorisedWriter
}
func (c *vectorisedProtocolConn) WriteVectorised(buffers []*buf.Buffer) error {
if c.protocolWritten {
return c.VectorisedWriter.WriteVectorised(buffers)
}
c.protocolWritten = true
_buffer := buf.StackNewSize(2)
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
EncodeRequest(buffer, Request{
Protocol: c.protocol,
})
return c.VectorisedWriter.WriteVectorised(append([]*buf.Buffer{buffer}, buffers...))
}

View File

@@ -3,12 +3,10 @@ package process
import (
"context"
"net/netip"
"os/user"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-tun"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
type Searcher interface {
@@ -30,15 +28,5 @@ type Info struct {
}
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
info, err := searcher.FindProcessInfo(ctx, network, source, destination)
if err != nil {
return nil, err
}
if info.UserId != -1 {
osUser, _ := user.LookupId(F.ToString(info.UserId))
if osUser != nil {
info.User = osUser.Username
}
}
return info, nil
return findProcessInfo(searcher, ctx, network, source, destination)
}

View File

@@ -15,6 +15,7 @@ import (
"unicode"
"unsafe"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
@@ -81,7 +82,9 @@ func resolveSocketByNetlink(network string, source netip.AddrPort, destination n
return 0, 0, E.Cause(err, "write netlink request")
}
buffer := buf.New()
_buffer := buf.StackNew()
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
n, err := syscall.Read(socket, buffer.FreeBytes())

View File

@@ -0,0 +1,25 @@
//go:build linux && !android
package process
import (
"context"
"net/netip"
"os/user"
F "github.com/sagernet/sing/common/format"
)
func findProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
info, err := searcher.FindProcessInfo(ctx, network, source, destination)
if err != nil {
return nil, err
}
if info.UserId != -1 {
osUser, _ := user.LookupId(F.ToString(info.UserId))
if osUser != nil {
info.User = osUser.Username
}
}
return info, nil
}

View File

@@ -0,0 +1,12 @@
//go:build !linux || android
package process
import (
"context"
"net/netip"
)
func findProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
return searcher.FindProcessInfo(ctx, network, source, destination)
}

View File

@@ -20,10 +20,10 @@ type systemProxy struct {
isMixed bool
}
func (p *systemProxy) update(event int) {
func (p *systemProxy) update(event int) error {
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
if p.interfaceName == newInterfaceName {
return
return nil
}
if p.interfaceName != "" {
_ = p.unset()
@@ -31,7 +31,7 @@ func (p *systemProxy) update(event int) {
p.interfaceName = newInterfaceName
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
if err != nil {
return
return err
}
if p.isMixed {
err = shell.Exec("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
@@ -40,9 +40,9 @@ func (p *systemProxy) update(event int) {
err = shell.Exec("networksetup", "-setwebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
}
if err == nil {
_ = shell.Exec("networksetup", "-setsecurewebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
err = shell.Exec("networksetup", "-setsecurewebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
}
return
return err
}
func (p *systemProxy) unset() error {
@@ -88,7 +88,10 @@ func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() er
port: port,
isMixed: isMixed,
}
proxy.update(tun.EventInterfaceUpdate)
err := proxy.update(tun.EventInterfaceUpdate)
if err != nil {
return nil, err
}
proxy.element = interfaceMonitor.RegisterCallback(proxy.update)
return func() error {
interfaceMonitor.UnregisterCallback(proxy.element)

View File

@@ -26,7 +26,9 @@ func StreamDomainNameQuery(readCtx context.Context, reader io.Reader) (*adapter.
if length == 0 {
return nil, os.ErrInvalid
}
buffer := buf.NewSize(int(length))
_buffer := buf.StackNewSize(int(length))
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
readCtx, cancel := context.WithTimeout(readCtx, time.Millisecond*100)

View File

@@ -7,7 +7,6 @@ import (
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/protocol/http"
)
@@ -16,5 +15,5 @@ func HTTPHost(ctx context.Context, reader io.Reader) (*adapter.InboundContext, e
if err != nil {
return nil, err
}
return &adapter.InboundContext{Protocol: C.ProtocolHTTP, Domain: M.ParseSocksaddr(request.Host).AddrString()}, nil
return &adapter.InboundContext{Protocol: C.ProtocolHTTP, Domain: request.Host}, nil
}

View File

@@ -1,27 +0,0 @@
package sniff_test
import (
"context"
"strings"
"testing"
"github.com/sagernet/sing-box/common/sniff"
"github.com/stretchr/testify/require"
)
func TestSniffHTTP1(t *testing.T) {
t.Parallel()
pkt := "GET / HTTP/1.1\r\nHost: www.google.com\r\nAccept: */*\r\n\r\n"
metadata, err := sniff.HTTPHost(context.Background(), strings.NewReader(pkt))
require.NoError(t, err)
require.Equal(t, metadata.Domain, "www.google.com")
}
func TestSniffHTTP1WithPort(t *testing.T) {
t.Parallel()
pkt := "GET / HTTP/1.1\r\nHost: www.gov.cn:8080\r\nAccept: */*\r\n\r\n"
metadata, err := sniff.HTTPHost(context.Background(), strings.NewReader(pkt))
require.NoError(t, err)
require.Equal(t, metadata.Domain, "www.gov.cn")
}

View File

@@ -22,29 +22,23 @@ func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout
if timeout == 0 {
timeout = C.ReadPayloadTimeout
}
deadline := time.Now().Add(timeout)
err := conn.SetReadDeadline(time.Now().Add(timeout))
if err != nil {
return nil, err
}
_, err = buffer.ReadOnceFrom(conn)
err = E.Errors(err, conn.SetReadDeadline(time.Time{}))
if err != nil {
return nil, err
}
var metadata *adapter.InboundContext
var errors []error
for i := 0; i < 3; i++ {
err := conn.SetReadDeadline(deadline)
if err != nil {
return nil, E.Cause(err, "set read deadline")
}
_, err = buffer.ReadOnceFrom(conn)
err = E.Errors(err, conn.SetReadDeadline(time.Time{}))
if err != nil {
if i > 0 {
break
}
return nil, E.Cause(err, "read payload")
}
for _, sniffer := range sniffers {
metadata, err := sniffer(ctx, bytes.NewReader(buffer.Bytes()))
if metadata != nil {
return metadata, nil
}
errors = append(errors, err)
for _, sniffer := range sniffers {
metadata, err = sniffer(ctx, bytes.NewReader(buffer.Bytes()))
if metadata != nil {
return metadata, nil
}
errors = append(errors, err)
}
return nil, E.Errors(errors...)
}

View File

@@ -21,7 +21,6 @@ import (
type acmeWrapper struct {
ctx context.Context
cfg *certmagic.Config
cache *certmagic.Cache
domain []string
}
@@ -30,7 +29,7 @@ func (w *acmeWrapper) Start() error {
}
func (w *acmeWrapper) Close() error {
w.cache.Stop()
w.cfg.Unmanage(w.domain)
return nil
}
@@ -78,11 +77,10 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
acmeConfig.ExternalAccount = (*acme.EAB)(options.ExternalAccount)
}
config.Issuers = []certmagic.Issuer{certmagic.NewACMEIssuer(config, acmeConfig)}
cache := certmagic.NewCache(certmagic.CacheOptions{
config = certmagic.New(certmagic.NewCache(certmagic.CacheOptions{
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
return config, nil
},
})
config = certmagic.New(cache, *config)
return config.TLSConfig(), &acmeWrapper{ctx: ctx, cfg: config, cache: cache, domain: options.Domain}, nil
}), *config)
return config.TLSConfig(), &acmeWrapper{ctx, config, options.Domain}, nil
}

View File

@@ -111,16 +111,6 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
if err != nil {
return nil, err
}
if len(uConfig.NextProtos) > 0 {
for _, extension := range uConn.Extensions {
if alpnExtension, isALPN := extension.(*utls.ALPNExtension); isALPN {
alpnExtension.AlpnProtocols = uConfig.NextProtos
break
}
}
}
hello := uConn.HandshakeState.Hello
hello.SessionId = make([]byte, 32)
copy(hello.Raw[39:], hello.SessionId)
@@ -134,9 +124,8 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
binary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))
hello.SessionId[0] = 1
hello.SessionId[1] = 8
hello.SessionId[2] = 1
binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))
hello.SessionId[1] = 7
hello.SessionId[2] = 5
copy(hello.SessionId[8:], e.shortID[:])
if debug.Enabled {

View File

@@ -101,10 +101,7 @@ func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Log
tlsConfig.ShortIds[shortID] = true
}
handshakeDialer, err := dialer.New(router, options.Reality.Handshake.DialerOptions)
if err != nil {
return nil, err
}
handshakeDialer := dialer.New(router, options.Reality.Handshake.DialerOptions)
tlsConfig.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
}

View File

@@ -164,8 +164,8 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
var acmeService adapter.Service
var err error
if options.ACME != nil && len(options.ACME.Domain) > 0 {
//nolint:staticcheck
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
//nolint:staticcheck
if err != nil {
return nil, err
}
@@ -180,7 +180,7 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
tlsConfig.ServerName = options.ServerName
}
if len(options.ALPN) > 0 {
tlsConfig.NextProtos = append(options.ALPN, tlsConfig.NextProtos...)
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
}
if options.MinVersion != "" {
minVersion, err := ParseTLSVersion(options.MinVersion)

View File

@@ -3,7 +3,6 @@
package tls
import (
"context"
"crypto/tls"
"crypto/x509"
"math/rand"
@@ -48,7 +47,7 @@ func (e *UTLSClientConfig) Config() (*STDConfig, error) {
}
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, e.config.NextProtos}, nil
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, nil
}
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
@@ -88,31 +87,6 @@ func (c *utlsConnWrapper) Upstream() any {
return c.UConn
}
type utlsALPNWrapper struct {
utlsConnWrapper
nextProtocols []string
}
func (c *utlsALPNWrapper) HandshakeContext(ctx context.Context) error {
if len(c.nextProtocols) > 0 {
err := c.BuildHandshakeState()
if err != nil {
return err
}
for _, extension := range c.Extensions {
if alpnExtension, isALPN := extension.(*utls.ALPNExtension); isALPN {
alpnExtension.AlpnProtocols = c.nextProtocols
err = c.BuildHandshakeState()
if err != nil {
return err
}
break
}
}
}
return c.UConn.HandshakeContext(ctx)
}
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
var serverName string
if options.ServerName != "" {

View File

@@ -20,7 +20,6 @@ type History struct {
type HistoryStorage struct {
access sync.RWMutex
delayHistory map[string]*History
updateHook chan<- struct{}
}
func NewHistoryStorage() *HistoryStorage {
@@ -29,10 +28,6 @@ func NewHistoryStorage() *HistoryStorage {
}
}
func (s *HistoryStorage) SetHook(hook chan<- struct{}) {
s.updateHook = hook
}
func (s *HistoryStorage) LoadURLTestHistory(tag string) *History {
if s == nil {
return nil
@@ -44,37 +39,17 @@ func (s *HistoryStorage) LoadURLTestHistory(tag string) *History {
func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
s.access.Lock()
defer s.access.Unlock()
delete(s.delayHistory, tag)
s.access.Unlock()
s.notifyUpdated()
}
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) {
s.access.Lock()
defer s.access.Unlock()
s.delayHistory[tag] = history
s.access.Unlock()
s.notifyUpdated()
}
func (s *HistoryStorage) notifyUpdated() {
updateHook := s.updateHook
if updateHook != nil {
select {
case updateHook <- struct{}{}:
default:
}
}
}
func (s *HistoryStorage) Close() error {
s.updateHook = nil
return nil
}
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
if link == "" {
link = "https://www.gstatic.com/generate_204"
}
linkURL, err := url.Parse(link)
if err != nil {
return

31
common/warning/warning.go Normal file
View File

@@ -0,0 +1,31 @@
package warning
import (
"sync"
"github.com/sagernet/sing-box/log"
)
type Warning struct {
logger log.Logger
check CheckFunc
message string
checkOnce sync.Once
}
type CheckFunc = func() bool
func New(checkFunc CheckFunc, message string) Warning {
return Warning{
check: checkFunc,
message: message,
}
}
func (w *Warning) Check() {
w.checkOnce.Do(func() {
if w.check() {
log.Warn(w.message)
}
})
}

View File

@@ -3,13 +3,28 @@ package constant
import (
"os"
"path/filepath"
"strings"
"github.com/sagernet/sing/common/rw"
)
const dirName = "sing-box"
var resourcePaths []string
var (
basePath string
resourcePaths []string
)
func BasePath(name string) string {
if basePath == "" || strings.HasPrefix(name, "/") {
return name
}
return filepath.Join(basePath, name)
}
func SetBasePath(path string) {
basePath = path
}
func FindPath(name string) (string, bool) {
name = os.ExpandEnv(name)

View File

@@ -7,7 +7,7 @@ const (
TypeDirect = "direct"
TypeBlock = "block"
TypeDNS = "dns"
TypeSOCKS = "socks"
TypeSocks = "socks"
TypeHTTP = "http"
TypeMixed = "mixed"
TypeShadowsocks = "shadowsocks"
@@ -21,55 +21,9 @@ const (
TypeShadowTLS = "shadowtls"
TypeShadowsocksR = "shadowsocksr"
TypeVLESS = "vless"
TypeTUIC = "tuic"
)
const (
TypeSelector = "selector"
TypeURLTest = "urltest"
)
func ProxyDisplayName(proxyType string) string {
switch proxyType {
case TypeDirect:
return "Direct"
case TypeBlock:
return "Block"
case TypeDNS:
return "DNS"
case TypeSOCKS:
return "SOCKS"
case TypeHTTP:
return "HTTP"
case TypeShadowsocks:
return "Shadowsocks"
case TypeVMess:
return "VMess"
case TypeTrojan:
return "Trojan"
case TypeNaive:
return "Naive"
case TypeWireGuard:
return "WireGuard"
case TypeHysteria:
return "Hysteria"
case TypeTor:
return "Tor"
case TypeSSH:
return "SSH"
case TypeShadowTLS:
return "ShadowTLS"
case TypeShadowsocksR:
return "ShadowsocksR"
case TypeVLESS:
return "VLESS"
case TypeTUIC:
return "TUIC"
case TypeSelector:
return "Selector"
case TypeURLTest:
return "URLTest"
default:
return "Unknown"
}
}

View File

@@ -10,7 +10,6 @@ import (
)
func applyDebugOptions(options option.DebugOptions) {
applyDebugListenOption(options)
if options.GCPercent != nil {
debug.SetGCPercent(*options.GCPercent)
}

View File

@@ -10,7 +10,6 @@ import (
)
func applyDebugOptions(options option.DebugOptions) {
applyDebugListenOption(options)
if options.GCPercent != nil {
debug.SetGCPercent(*options.GCPercent)
}

View File

@@ -1,67 +0,0 @@
package box
import (
"net/http"
"net/http/pprof"
"runtime"
"runtime/debug"
"github.com/sagernet/sing-box/common/badjson"
"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/dustin/go-humanize"
"github.com/go-chi/chi/v5"
)
var debugHTTPServer *http.Server
func applyDebugListenOption(options option.DebugOptions) {
if debugHTTPServer != nil {
debugHTTPServer.Close()
debugHTTPServer = nil
}
if options.Listen == "" {
return
}
r := chi.NewMux()
r.Route("/debug", func(r chi.Router) {
r.Get("/gc", func(writer http.ResponseWriter, request *http.Request) {
writer.WriteHeader(http.StatusNoContent)
go debug.FreeOSMemory()
})
r.Get("/memory", func(writer http.ResponseWriter, request *http.Request) {
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
var memObject badjson.JSONObject
memObject.Put("heap", humanize.IBytes(memStats.HeapInuse))
memObject.Put("stack", humanize.IBytes(memStats.StackInuse))
memObject.Put("idle", humanize.IBytes(memStats.HeapIdle-memStats.HeapReleased))
memObject.Put("goroutines", runtime.NumGoroutine())
memObject.Put("rss", rusageMaxRSS())
encoder := json.NewEncoder(writer)
encoder.SetIndent("", " ")
encoder.Encode(memObject)
})
r.HandleFunc("/pprof", pprof.Index)
r.HandleFunc("/pprof/*", pprof.Index)
r.HandleFunc("/pprof/cmdline", pprof.Cmdline)
r.HandleFunc("/pprof/profile", pprof.Profile)
r.HandleFunc("/pprof/symbol", pprof.Symbol)
r.HandleFunc("/pprof/trace", pprof.Trace)
})
debugHTTPServer = &http.Server{
Addr: options.Listen,
Handler: r,
}
go func() {
err := debugHTTPServer.ListenAndServe()
if err != nil && !E.IsClosed(err) {
log.Error(E.Cause(err, "serve debug HTTP server"))
}
}()
}

View File

@@ -1,365 +1,3 @@
#### 1.4.1
* Fixes and improvements
#### 1.4.0
* Fix bugs and update dependencies
Important changes since 1.3:
* Add TUIC support **1**
* Add `udp_over_stream` option for TUIC client **2**
* Add MultiPath TCP support **3**
* Add `include_interface` and `exclude_interface` options for tun inbound
* Pause recurring tasks when no network or device idle
* Improve Android and Apple platform clients
*1*:
See [TUIC inbound](/configuration/inbound/tuic)
and [TUIC outbound](/configuration/outbound/tuic)
**2**:
This is the TUIC port of the [UDP over TCP protocol](/configuration/shared/udp-over-tcp), designed to provide a QUIC
stream based UDP relay mode that TUIC does not provide. Since it is an add-on protocol, you will need to use sing-box or
another program compatible with the protocol as a server.
This mode has no positive effect in a proper UDP proxy scenario and should only be applied to relay streaming UDP
traffic (basically QUIC streams).
*3*:
Requires sing-box to be compiled with Go 1.21.
#### 1.4.0-rc.3
* Fixes and improvements
#### 1.4.0-rc.2
* Fixes and improvements
#### 1.4.0-rc.1
* Fix TUIC UDP
#### 1.4.0-beta.6
* Add `udp_over_stream` option for TUIC client **1**
* Add `include_interface` and `exclude_interface` options for tun inbound
* Fixes and improvements
**1**:
This is the TUIC port of the [UDP over TCP protocol](/configuration/shared/udp-over-tcp), designed to provide a QUIC
stream based UDP relay mode that TUIC does not provide. Since it is an add-on protocol, you will need to use sing-box or
another program compatible with the protocol as a server.
This mode has no positive effect in a proper UDP proxy scenario and should only be applied to relay streaming UDP
traffic (basically QUIC streams).
#### 1.4.0-beta.5
* Fixes and improvements
#### 1.4.0-beta.4
* Graphical clients: Persistence group expansion state
* Fixes and improvements
#### 1.4.0-beta.3
* Fixes and improvements
#### 1.4.0-beta.2
* Add MultiPath TCP support **1**
* Drop QUIC support for Go 1.18 and 1.19 due to upstream changes
* Fixes and improvements
*1*:
Requires sing-box to be compiled with Go 1.21.
#### 1.4.0-beta.1
* Add TUIC support **1**
* Pause recurring tasks when no network or device idle
* Fixes and improvements
*1*:
See [TUIC inbound](/configuration/inbound/tuic)
and [TUIC outbound](/configuration/outbound/tuic)
#### 1.3.6
* Fixes and improvements
#### 1.3.5
* Fixes and improvements
* Introducing our [Apple tvOS](/installation/clients/sft) client applications **1**
* Add per app proxy and app installed/updated trigger support for Android client
* Add profile sharing support for Android/iOS/macOS clients
**1**:
Due to the requirement of tvOS 17, the app cannot be submitted to the App Store for the time being, and can only be
downloaded through TestFlight.
#### 1.3.4
* Fixes and improvements
* We're now on the [App Store](https://apps.apple.com/us/app/sing-box/id6451272673), always free! It should be noted
that due to stricter and slower review, the release of Store versions will be delayed.
* We've made a standalone version of the macOS client (the original Application Extension relies on App Store
distribution), which you can download as SFM-version-universal.zip in the release artifacts.
#### 1.3.3
* Fixes and improvements
#### 1.3.1-rc.1
* Fix bugs and update dependencies
#### 1.3.1-beta.3
* Introducing our [new iOS](/installation/clients/sfi) and [macOS](/installation/clients/sfm) client applications **1**
* Fixes and improvements
**1**:
The old testflight link and app are no longer valid.
#### 1.3.1-beta.2
* Fix bugs and update dependencies
#### 1.3.1-beta.1
* Fixes and improvements
#### 1.3.0
* Fix bugs and update dependencies
Important changes since 1.2:
* Add [FakeIP](/configuration/dns/fakeip) support **1**
* Improve multiplex **2**
* Add [DNS reverse mapping](/configuration/dns#reverse_mapping) support
* Add `rewrite_ttl` DNS rule action
* Add `store_fakeip` Clash API option
* Add multi-peer support for [WireGuard](/configuration/outbound/wireguard#peers) outbound
* Add loopback detect
* Add Clash.Meta API compatibility for Clash API
* Download Yacd-meta by default if the specified Clash `external_ui` directory is empty
* Add path and headers option for HTTP outbound
* Perform URLTest recheck after network changes
* Fix `system` tun stack for ios
* Fix network monitor for android/ios
* Update VLESS and XUDP protocol
* Make splice work with traffic statistics systems like Clash API
* Significantly reduces memory usage of idle connections
* Improve DNS caching
* Add `independent_cache` [option](/configuration/dns#independent_cache) for DNS
* Reimplemented shadowsocks client
* Add multiplex support for VLESS outbound
* Automatically add Windows firewall rules in order for the system tun stack to work
* Fix TLS 1.2 support for shadow-tls client
* Add `cache_id` [option](/configuration/experimental#cache_id) for Clash cache file
* Fix `local` DNS transport for Android
*1*:
See [FAQ](/faq/fakeip) for more information.
*2*:
Added new `h2mux` multiplex protocol and `padding` multiplex option, see [Multiplex](/configuration/shared/multiplex).
#### 1.3-rc2
* Fix `local` DNS transport for Android
* Fix bugs and update dependencies
#### 1.3-rc1
* Fix bugs and update dependencies
#### 1.3-beta14
* Fixes and improvements
#### 1.3-beta13
* Fix resolving fakeip domains **1**
* Deprecate L3 routing
* Fix bugs and update dependencies
**1**:
If the destination address of the connection is obtained from fakeip, dns rules with server type fakeip will be skipped.
#### 1.3-beta12
* Automatically add Windows firewall rules in order for the system tun stack to work
* Fix TLS 1.2 support for shadow-tls client
* Add `cache_id` [option](/configuration/experimental#cache_id) for Clash cache file
* Fixes and improvements
#### 1.3-beta11
* Fix bugs and update dependencies
#### 1.3-beta10
* Improve direct copy **1**
* Improve DNS caching
* Add `independent_cache` [option](/configuration/dns#independent_cache) for DNS
* Reimplemented shadowsocks client **2**
* Add multiplex support for VLESS outbound
* Set TCP keepalive for WireGuard gVisor TCP connections
* Fixes and improvements
**1**:
* Make splice work with traffic statistics systems like Clash API
* Significantly reduces memory usage of idle connections
**2**:
Improved performance and reduced memory usage.
#### 1.3-beta9
* Improve multiplex **1**
* Fixes and improvements
*1*:
Added new `h2mux` multiplex protocol and `padding` multiplex option, see [Multiplex](/configuration/shared/multiplex).
#### 1.2.6
* Fix bugs and update dependencies
#### 1.3-beta8
* Fix `system` tun stack for ios
* Fix network monitor for android/ios
* Update VLESS and XUDP protocol **1**
* Fixes and improvements
*1:
This is an incompatible update for XUDP in VLESS if vision flow is enabled.
#### 1.3-beta7
* Add `path` and `headers` options for HTTP outbound
* Add multi-user support for Shadowsocks legacy AEAD inbound
* Fixes and improvements
#### 1.2.4
* Fixes and improvements
#### 1.3-beta6
* Fix WireGuard reconnect
* Perform URLTest recheck after network changes
* Fix bugs and update dependencies
#### 1.3-beta5
* Add Clash.Meta API compatibility for Clash API
* Download Yacd-meta by default if the specified Clash `external_ui` directory is empty
* Add path and headers option for HTTP outbound
* Fixes and improvements
#### 1.3-beta4
* Fix bugs
#### 1.3-beta2
* Download clash-dashboard if the specified Clash `external_ui` directory is empty
* Fix bugs and update dependencies
#### 1.3-beta1
* Add [DNS reverse mapping](/configuration/dns#reverse_mapping) support
* Add [L3 routing](/configuration/route/ip-rule) support **1**
* Add `rewrite_ttl` DNS rule action
* Add [FakeIP](/configuration/dns/fakeip) support **2**
* Add `store_fakeip` Clash API option
* Add multi-peer support for [WireGuard](/configuration/outbound/wireguard#peers) outbound
* Add loopback detect
*1*:
It can currently be used to [route connections directly to WireGuard](/examples/wireguard-direct) or block connections
at the IP layer.
*2*:
See [FAQ](/faq/fakeip) for more information.
#### 1.2.3
* Introducing our [new Android client application](/installation/clients/sfa)
* Improve UDP domain destination NAT
* Update reality protocol
* Fix TTL calculation for DNS response
* Fix v2ray HTTP transport compatibility
* Fix bugs and update dependencies
#### 1.2.2
* Accept `any` outbound in dns rule **1**
* Fix bugs and update dependencies
*1*:
Now you can use the `any` outbound rule to match server address queries instead of filling in all server domains
to `domain` rule.
#### 1.2.1
* Fix missing default host in v2ray http transport`s request
* Flush DNS cache for macOS when tun start/close
* Fix tun's DNS hijacking compatibility with systemd-resolved
#### 1.2.0
* Fix bugs and update dependencies
Important changes since 1.1:
* Introducing our [new iOS client application](/installation/clients/sfi)
* Introducing [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp)
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
* Add [VLESS server](/configuration/inbound/vless) and [vision](/configuration/outbound/vless#flow) support
* Add [reality TLS](/configuration/shared/tls) support
* Add [NTP service](/configuration/ntp)
* Add [DHCP DNS server](/configuration/dns/server) support
* Add SSH [host key validation](/configuration/outbound/ssh) support
* Add [query_type](/configuration/dns/rule) DNS rule item
* Add fallback support for v2ray transport
* Add custom TLS server support for http based v2ray transports
* Add health check support for http-based v2ray transports
* Add multiple configuration support
#### 1.2-rc1
* Fix bugs and update dependencies
#### 1.2-beta10
* Add multiple configuration support **1**
@@ -367,11 +5,9 @@ Important changes since 1.1:
*1*:
Now you can pass the parameter `--config` or `-c` multiple times, or use the new parameter `--config-directory` or `-C`
to load all configuration files in a directory.
Now you can pass the parameter `--config` or `-c` multiple times, or use the new parameter `--config-directory` or `-C` to load all configuration files in a directory.
Loaded configuration files are sorted by name. If you want to control the merge order, add a numeric prefix to the file
name.
Loaded configuration files are sorted by name. If you want to control the merge order, add a numeric prefix to the file name.
#### 1.1.7

View File

@@ -1,25 +0,0 @@
# FakeIP
### Structure
```json
{
"enabled": true,
"inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18"
}
```
### Fields
#### enabled
Enable FakeIP service.
#### inet4_range
IPv4 address range for FakeIP.
#### inet6_address
IPv6 address range for FakeIP.

View File

@@ -1,25 +0,0 @@
# FakeIP
### 结构
```json
{
"enabled": true,
"inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18"
}
```
### 字段
#### enabled
启用 FakeIP 服务。
#### inet4_range
用于 FakeIP 的 IPv4 地址范围。
#### inet6_range
用于 FakeIP 的 IPv6 地址范围。

View File

@@ -10,10 +10,7 @@
"final": "",
"strategy": "",
"disable_cache": false,
"disable_expire": false,
"independent_cache": false,
"reverse_mapping": false,
"fakeip": {}
"disable_expire": false
}
}
@@ -25,7 +22,6 @@
|----------|--------------------------------|
| `server` | List of [DNS Server](./server) |
| `rules` | List of [DNS Rule](./rule) |
| `fakeip` | [FakeIP](./fakeip) |
#### final
@@ -47,19 +43,4 @@ Disable dns cache.
#### disable_expire
Disable dns cache expire.
#### independent_cache
Make each DNS server's cache independent for special purposes. If enabled, will slightly degrade performance.
#### reverse_mapping
Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing.
Since this process relies on the act of resolving domain names by an application before making a request, it can be
problematic in environments such as macOS, where DNS is proxied and cached by the system.
#### fakeip
[FakeIP](./fakeip) settings.
Disable dns cache expire.

View File

@@ -10,10 +10,7 @@
"final": "",
"strategy": "",
"disable_cache": false,
"disable_expire": false,
"independent_cache": false,
"reverse_mapping": false,
"fakeip": {}
"disable_expire": false
}
}
@@ -46,18 +43,4 @@
#### disable_expire
禁用 DNS 缓存过期。
#### independent_cache
使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。
#### reverse_mapping
在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
由于此过程依赖于应用程序在发出请求之前解析域名的行为,因此在 macOS 等 DNS 由系统代理和缓存的环境中可能会出现问题。
#### fakeip
[FakeIP](./fakeip) 设置。
禁用 DNS 缓存过期。

View File

@@ -84,16 +84,14 @@
"direct"
],
"server": "local",
"disable_cache": false,
"rewrite_ttl": 100
"disable_cache": false
},
{
"type": "logical",
"mode": "and",
"rules": [],
"server": "local",
"disable_cache": false,
"rewrite_ttl": 100
"disable_cache": false
}
]
}
@@ -234,8 +232,6 @@ Invert match result.
Match outbound.
`any` can be used as a value to match any outbound.
#### server
==Required==
@@ -246,10 +242,6 @@ Tag of the target dns server.
Disable cache and save cache in this query.
#### rewrite_ttl
Rewrite TTL in DNS responses.
### Logical Fields
#### type
@@ -262,4 +254,18 @@ Rewrite TTL in DNS responses.
#### rules
Included default rules.
Included default rules.
#### invert
Invert match result.
#### server
==Required==
Tag of the target dns server.
#### disable_cache
Disable cache and save cache in this query.

View File

@@ -231,8 +231,6 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
匹配出站。
`any` 可作为值用于匹配任意出站。
#### server
==必填==
@@ -243,10 +241,6 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
在此查询中禁用缓存。
#### rewrite_ttl
重写 DNS 回应中的 TTL。
### 逻辑字段
#### type
@@ -259,4 +253,18 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
#### rules
包括的默认规则。
包括的默认规则。
#### invert
反选匹配结果。
#### server
==必填==
目标 DNS 服务器的标签。
#### disable_cache
在此查询中禁用缓存。

View File

@@ -30,18 +30,17 @@ The tag of the dns server.
The address of the dns server.
| Protocol | Format |
|-------------------------------------|-------------------------------|
| `System` | `local` |
| `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
| `TLS` | `tls://dns.google` |
| `HTTPS` | `https://1.1.1.1/dns-query` |
| `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
| [FakeIP](/configuration/dns/fakeip) | `fakeip` |
| Protocol | Format |
|----------|-------------------------------|
| `System` | `local` |
| `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
| `TLS` | `tls://dns.google` |
| `HTTPS` | `https://1.1.1.1/dns-query` |
| `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
!!! warning ""
@@ -94,4 +93,4 @@ Take no effect if override by other settings.
Tag of an outbound for connecting to the dns server.
Default outbound will be used if empty.
Default outbound will be used if empty.

View File

@@ -30,18 +30,17 @@ DNS 服务器的标签。
DNS 服务器的地址。
| 协议 | 格式 |
|-------------------------------------|------------------------------|
| `System` | `local` |
| `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
| `TLS` | `tls://dns.google` |
| `HTTPS` | `https://1.1.1.1/dns-query` |
| `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto``dhcp://en0` |
| [FakeIP](/configuration/dns/fakeip) | `fakeip` |
| 协议 | 格式 |
|----------|------------------------------|
| `System` | `local` |
| `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
| `TLS` | `tls://dns.google` |
| `HTTPS` | `https://1.1.1.1/dns-query` |
| `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto``dhcp://en0` |
!!! warning ""

View File

@@ -7,16 +7,11 @@
"experimental": {
"clash_api": {
"external_controller": "127.0.0.1:9090",
"external_ui": "",
"external_ui_download_url": "",
"external_ui_download_detour": "",
"external_ui": "folder",
"secret": "",
"default_mode": "",
"store_mode": false,
"default_mode": "rule",
"store_selected": false,
"store_fakeip": false,
"cache_file": "",
"cache_id": ""
"cache_file": "cache.db"
},
"v2ray_api": {
"listen": "127.0.0.1:8080",
@@ -58,18 +53,6 @@ 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)
@@ -82,10 +65,6 @@ 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 ""
@@ -94,20 +73,10 @@ Store Clash mode in cache file.
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 ""

View File

@@ -7,16 +7,11 @@
"experimental": {
"clash_api": {
"external_controller": "127.0.0.1:9090",
"external_ui": "",
"external_ui_download_url": "",
"external_ui_download_detour": "",
"external_ui": "folder",
"secret": "",
"default_mode": "",
"store_mode": false,
"default_mode": "rule",
"store_selected": false,
"store_fakeip": false,
"cache_file": "",
"cache_id": ""
"cache_file": "cache.db"
},
"v2ray_api": {
"listen": "127.0.0.1:8080",
@@ -56,18 +51,6 @@ RESTful web API 监听地址。如果为空,则禁用 Clash API。
到静态网页资源目录的相对路径或绝对路径。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 的密钥(可选)
@@ -80,10 +63,6 @@ Clash 中的默认模式,默认使用 `rule`。
此设置没有直接影响,但可以通过 `clash_mode` 规则项在路由和 DNS 规则中使用。
#### store_mode
将 Clash 模式存储在缓存文件中。
#### store_selected
!!! note ""
@@ -92,20 +71,10 @@ Clash 中的默认模式,默认使用 `rule`。
`Selector` 中出站的选定的目标出站存储在缓存文件中。
#### store_fakeip
将 fakeip 存储在缓存文件中。
#### cache_file
缓存文件路径,默认使用`cache.db`
#### cache_id
缓存 ID。
如果不为空,`store_selected` 将会使用以此为键的独立存储。
### V2Ray API 字段
!!! error ""

View File

@@ -40,8 +40,4 @@ No authentication required if empty.
Only supported on Linux, Android, Windows, and macOS.
!!! warning ""
To work on Android and iOS without privileges, use tun.platform.http_proxy instead.
Automatically set system proxy configuration when start and clean up when stop.

View File

@@ -40,8 +40,4 @@ HTTP 用户
仅支持 Linux、Android、Windows 和 macOS。
!!! warning ""
要在无特权的 Android 和 iOS 上工作,请改用 tun.platform.http_proxy。
启动时自动设置系统代理,停止时自动清理。

View File

@@ -37,8 +37,4 @@ No authentication required if empty.
Only supported on Linux, Android, Windows, and macOS.
!!! warning ""
To work on Android and iOS without privileges, use tun.platform.http_proxy instead.
Automatically set system proxy configuration when start and clean up when stop.
Automatically set system proxy configuration when start and clean up when stop.

View File

@@ -37,8 +37,4 @@ SOCKS 和 HTTP 用户
仅支持 Linux、Android、Windows 和 macOS。
!!! warning ""
要在无特权的 Android 和 iOS 上工作,请改用 tun.platform.http_proxy。
启动时自动设置系统代理,停止时自动清理。

View File

@@ -1,82 +0,0 @@
### Structure
```json
{
"type": "tuic",
"tag": "tuic-in",
... // Listen Fields
"users": [
{
"name": "sekai",
"uuid": "059032A9-7D40-4A96-9BB1-36823D848068",
"password": "hello"
}
],
"congestion_control": "cubic",
"auth_timeout": "3s",
"zero_rtt_handshake": false,
"heartbeat": "10s",
"tls": {}
}
```
!!! warning ""
QUIC, which is required by TUIC is not included by default, see [Installation](/#installation).
### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details.
### Fields
#### users
TUIC users
#### users.uuid
==Required==
TUIC user uuid
#### users.password
TUIC user password
#### congestion_control
QUIC congestion control algorithm
One of: `cubic`, `new_reno`, `bbr`
`cubic` is used by default.
#### auth_timeout
How long the server should wait for the client to send the authentication command
`3s` is used by default.
#### zero_rtt_handshake
Enable 0-RTT QUIC connection handshake on the client side
This is not impacting much on the performance, as the protocol is fully multiplexed
!!! warning ""
Disabling this is highly recommended, as it is vulnerable to replay attacks.
See [Attack of the clones](https://blog.cloudflare.com/even-faster-connection-establishment-with-quic-0-rtt-resumption/#attack-of-the-clones)
#### heartbeat
Interval for sending heartbeat packets for keeping the connection alive
`10s` is used by default.
#### tls
==Required==
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).

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