diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index c04aaf50b8..6133e7657e 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -1,70 +1,77 @@
-name: Bug Report
-description: "Create a report to help us improve."
+name: Bug report
+description: "Report sing-box bug"
body:
- - type: checkboxes
- id: terms
+ - type: dropdown
attributes:
- label: Welcome
+ label: Operating system
+ description: Operating system type
options:
- - 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
+ - iOS
+ - macOS
+ - Apple tvOS
+ - Android
+ - Windows
+ - Linux
+ - Others
+ validations:
+ required: true
+ - type: input
attributes:
- label: Description of the problem
- placeholder: Your problem description
+ 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.
- type: textarea
- id: version
attributes:
- label: Version of sing-box
+ label: Version
+ description: If you are using the original command line program, please provide the output of the `sing-box version` command.
value: |-
-
```console
- $ sing-box version
- # Paste output here
+ # Replace this line with the output
```
-
+ - type: textarea
+ attributes:
+ label: Description
+ description: Please provide a detailed description of the error.
validations:
required: true
-
- type: textarea
- id: config
attributes:
- label: Server and client configuration file
- value: |-
-
-
- ```console
- # paste json here
- ```
-
-
+ 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
- id: log
attributes:
- label: Server and client log file
+ 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.
value: |-
-
```console
- # paste log here
+ # Replace this line with logs
```
-
-
- validations:
- required: true
+
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug_report_zh.yml b/.github/ISSUE_TEMPLATE/bug_report_zh.yml
new file mode 100644
index 0000000000..7c6c7c22bc
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report_zh.yml
@@ -0,0 +1,77 @@
+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: |-
+
+ ```console
+ # 使用输出内容覆盖此行
+ ```
+
+ - 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: |-
+
+ ```console
+ # 使用日志内容覆盖此行
+ ```
+
\ No newline at end of file
diff --git a/.github/workflows/debug.yml b/.github/workflows/debug.yml
index e513544339..812c40e611 100644
--- a/.github/workflows/debug.yml
+++ b/.github/workflows/debug.yml
@@ -62,7 +62,27 @@ jobs:
~/go/pkg/mod
key: go118-${{ hashFiles('**/go.sum') }}
- name: Run Test
- run: make
+ 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
cross:
strategy:
matrix:
diff --git a/.github/workflows/mkdocs.yml b/.github/workflows/mkdocs.yml
deleted file mode 100644
index 7c9addac41..0000000000
--- a/.github/workflows/mkdocs.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-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
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 709554db3d..06f2174393 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM golang:1.20-alpine AS builder
+FROM golang:1.21-alpine AS builder
LABEL maintainer="nekohasekai "
COPY . /go/src/github.com/sagernet/sing-box
WORKDIR /go/src/github.com/sagernet/sing-box
diff --git a/Makefile b/Makefile
index d69247e062..8e76673891 100644
--- a/Makefile
+++ b/Makefile
@@ -1,20 +1,31 @@
NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD)
-TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_clash_api
+TAGS_GO118 = with_gvisor,with_dhcp,with_wireguard,with_utls,with_reality_server,with_clash_api
+TAGS_GO120 = with_quic
+TAGS ?= $(TAGS_GO118),$(TAGS_GO120)
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 -tags "$(TAGS)" -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
+PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
+MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
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)
diff --git a/adapter/experimental.go b/adapter/experimental.go
index f25d70a435..9afdcb7b9e 100644
--- a/adapter/experimental.go
+++ b/adapter/experimental.go
@@ -23,6 +23,8 @@ type ClashServer interface {
type ClashCacheFile interface {
LoadSelected(group string) string
StoreSelected(group string, selected string) error
+ LoadGroupExpand(group string) (isExpand bool, loaded bool)
+ StoreGroupExpand(group string, expand bool) error
FakeIPStorage
}
diff --git a/adapter/fakeip.go b/adapter/fakeip.go
index 6a14224557..51247c32fe 100644
--- a/adapter/fakeip.go
+++ b/adapter/fakeip.go
@@ -18,6 +18,7 @@ type FakeIPStore interface {
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)
diff --git a/box.go b/box.go
index c82605a548..baebc7617d 100644
--- a/box.go
+++ b/box.go
@@ -53,7 +53,7 @@ func New(options Options) (*Box, error) {
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
var needClashAPI bool
var needV2RayAPI bool
- if experimentalOptions.ClashAPI != nil && experimentalOptions.ClashAPI.ExternalController != "" {
+ if experimentalOptions.ClashAPI != nil || options.PlatformInterface != nil {
needClashAPI = true
}
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
@@ -145,7 +145,7 @@ func New(options Options) (*Box, error) {
preServices := make(map[string]adapter.Service)
postServices := make(map[string]adapter.Service)
if needClashAPI {
- clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), common.PtrValueOrDefault(options.Experimental.ClashAPI))
+ clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), common.PtrValueOrDefault(experimentalOptions.ClashAPI))
if err != nil {
return nil, E.Cause(err, "create clash api server")
}
@@ -153,7 +153,7 @@ func New(options Options) (*Box, error) {
preServices["clash api"] = clashServer
}
if needV2RayAPI {
- v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI))
+ v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
if err != nil {
return nil, E.Cause(err, "create v2ray api server")
}
diff --git a/cmd/internal/build_shared/tag.go b/cmd/internal/build_shared/tag.go
index 9a37638516..b1fecab438 100644
--- a/cmd/internal/build_shared/tag.go
+++ b/cmd/internal/build_shared/tag.go
@@ -1,6 +1,9 @@
package build_shared
-import "github.com/sagernet/sing/common/shell"
+import (
+ "github.com/sagernet/sing-box/common/badversion"
+ "github.com/sagernet/sing/common/shell"
+)
func ReadTag() (string, error) {
currentTag, err := shell.Exec("git", "describe", "--tags").ReadOutput()
@@ -12,5 +15,9 @@ func ReadTag() (string, error) {
return currentTag[1:], nil
}
shortCommit, _ := shell.Exec("git", "rev-parse", "--short", "HEAD").ReadOutput()
- return currentTagRev[1:] + "-" + shortCommit, nil
+ version := badversion.Parse(currentTagRev[1:])
+ if version.PreReleaseIdentifier == "" {
+ version.Patch++
+ }
+ return version.String() + "-" + shortCommit, nil
}
diff --git a/common/badtls/badtls_stub.go b/common/badtls/badtls_stub.go
index 7810bb1cc1..2f0028f6e9 100644
--- a/common/badtls/badtls_stub.go
+++ b/common/badtls/badtls_stub.go
@@ -5,8 +5,10 @@ package badtls
import (
"crypto/tls"
"os"
+
+ aTLS "github.com/sagernet/sing/common/tls"
)
-func Create(conn *tls.Conn) (TLSConn, error) {
+func Create(conn *tls.Conn) (aTLS.Conn, error) {
return nil, os.ErrInvalid
}
diff --git a/common/badversion/version.go b/common/badversion/version.go
index faf292fe9c..23168eca0e 100644
--- a/common/badversion/version.go
+++ b/common/badversion/version.go
@@ -11,6 +11,7 @@ type Version struct {
Major int
Minor int
Patch int
+ Commit string
PreReleaseIdentifier string
PreReleaseVersion int
}
@@ -37,14 +38,19 @@ func (v Version) After(anotherVersion Version) bool {
return false
}
if v.PreReleaseIdentifier != "" && anotherVersion.PreReleaseIdentifier != "" {
- if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "alpha" {
+ 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 == "alpha" && anotherVersion.PreReleaseIdentifier == "beta" {
+ } else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "rc" {
return false
- }
- if v.PreReleaseVersion > anotherVersion.PreReleaseVersion {
+ } else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "alpha" {
return true
- } else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion {
+ } else if v.PreReleaseIdentifier == "alpha" && anotherVersion.PreReleaseIdentifier == "beta" {
return false
}
}
@@ -95,7 +101,7 @@ func Parse(versionName string) (version Version) {
version.PreReleaseIdentifier = "beta"
version.PreReleaseVersion, _ = strconv.Atoi(identifier[4:])
} else {
- version.PreReleaseIdentifier = identifier
+ version.Commit = identifier
}
}
}
diff --git a/common/dialer/default.go b/common/dialer/default.go
index f45b7809be..4bf519978f 100644
--- a/common/dialer/default.go
+++ b/common/dialer/default.go
@@ -13,12 +13,11 @@ import (
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"
)
type DefaultDialer struct {
- dialer4 tfo.Dialer
- dialer6 tfo.Dialer
+ dialer4 tcpDialer
+ dialer6 tcpDialer
udpDialer4 net.Dialer
udpDialer6 net.Dialer
udpListener net.ListenConfig
@@ -26,7 +25,7 @@ type DefaultDialer struct {
udpAddr6 string
}
-func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDialer {
+func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDialer, error) {
var dialer net.Dialer
var listener net.ListenConfig
if options.BindInterface != "" {
@@ -93,15 +92,29 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
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{
- tfo.Dialer{Dialer: dialer4, DisableTFO: !options.TCPFastOpen},
- tfo.Dialer{Dialer: dialer6, DisableTFO: !options.TCPFastOpen},
+ tcpDialer4,
+ tcpDialer6,
udpDialer4,
udpDialer6,
listener,
udpAddr4,
udpAddr6,
- }
+ }, nil
}
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
diff --git a/common/dialer/default_go1.20.go b/common/dialer/default_go1.20.go
new file mode 100644
index 0000000000..8c3507c213
--- /dev/null
+++ b/common/dialer/default_go1.20.go
@@ -0,0 +1,15 @@
+//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
+}
diff --git a/common/dialer/default_go1.21.go b/common/dialer/default_go1.21.go
new file mode 100644
index 0000000000..6ecb5b2572
--- /dev/null
+++ b/common/dialer/default_go1.21.go
@@ -0,0 +1,11 @@
+//go:build go1.21
+
+package dialer
+
+import "net"
+
+const go121Available = true
+
+func setMultiPathTCP(dialer *net.Dialer) {
+ dialer.SetMultipathTCP(true)
+}
diff --git a/common/dialer/default_nongo1.20.go b/common/dialer/default_nongo1.20.go
new file mode 100644
index 0000000000..215024245a
--- /dev/null
+++ b/common/dialer/default_nongo1.20.go
@@ -0,0 +1,18 @@
+//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
+}
diff --git a/common/dialer/default_nongo1.21.go b/common/dialer/default_nongo1.21.go
new file mode 100644
index 0000000000..386d50dd02
--- /dev/null
+++ b/common/dialer/default_nongo1.21.go
@@ -0,0 +1,12 @@
+//go:build !go1.21
+
+package dialer
+
+import (
+ "net"
+)
+
+const go121Available = false
+
+func setMultiPathTCP(dialer *net.Dialer) {
+}
diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go
index 5b1750cc9e..0f5c913abb 100644
--- a/common/dialer/dialer.go
+++ b/common/dialer/dialer.go
@@ -6,13 +6,24 @@ 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 New(router adapter.Router, options option.DialerOptions) N.Dialer {
- var dialer N.Dialer
+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
+ )
if options.Detour == "" {
- dialer = NewDefault(router, options)
+ dialer, err = NewDefault(router, options)
+ if err != nil {
+ return nil, err
+ }
} else {
dialer = NewDetour(router, options.Detour)
}
@@ -20,5 +31,5 @@ func New(router adapter.Router, options option.DialerOptions) N.Dialer {
if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" {
dialer = NewResolveDialer(router, dialer, domainStrategy, time.Duration(options.FallbackDelay))
}
- return dialer
+ return dialer, nil
}
diff --git a/common/dialer/tfo.go b/common/dialer/tfo.go
index 0d4646cf2e..2e3eb9b3f4 100644
--- a/common/dialer/tfo.go
+++ b/common/dialer/tfo.go
@@ -1,3 +1,5 @@
+//go:build go1.20
+
package dialer
import (
@@ -25,7 +27,7 @@ type slowOpenConn struct {
err error
}
-func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
+func DialSlowContext(dialer *tcpDialer, 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:
@@ -61,6 +63,7 @@ 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)
diff --git a/common/dialer/tfo_stub.go b/common/dialer/tfo_stub.go
new file mode 100644
index 0000000000..144902e5be
--- /dev/null
+++ b/common/dialer/tfo_stub.go
@@ -0,0 +1,20 @@
+//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())
+ }
+}
diff --git a/common/tls/reality_server.go b/common/tls/reality_server.go
index 0cd339c9aa..fd1a6815be 100644
--- a/common/tls/reality_server.go
+++ b/common/tls/reality_server.go
@@ -101,7 +101,10 @@ func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Log
tlsConfig.ShortIds[shortID] = true
}
- handshakeDialer := dialer.New(router, options.Reality.Handshake.DialerOptions)
+ handshakeDialer, err := dialer.New(router, options.Reality.Handshake.DialerOptions)
+ if err != nil {
+ return nil, err
+ }
tlsConfig.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
}
diff --git a/docs/changelog.md b/docs/changelog.md
index 5df4e2c346..2b1031a905 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -1,7 +1,50 @@
+#### 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
+* Pause recurring tasks when no network or device idle
+* Fixes and improvements
*1*:
@@ -21,13 +64,16 @@ and [TUIC outbound](/configuration/outbound/tuic)
**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.
+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.
+* 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
diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md
index 8edef4c24a..4c9670a27d 100644
--- a/docs/configuration/inbound/tun.md
+++ b/docs/configuration/inbound/tun.md
@@ -24,6 +24,12 @@
],
"endpoint_independent_nat": false,
"stack": "system",
+ "include_interface": [
+ "lan0"
+ ],
+ "exclude_interface": [
+ "lan1"
+ ],
"include_uid": [
0
],
@@ -153,6 +159,22 @@ TCP/IP stack.
gVisor and LWIP stacks is not included by default, see [Installation](/#installation).
+#### include_interface
+
+!!! error ""
+
+ Interface rules are only supported on Linux and require auto_route.
+
+Limit interfaces in route. Not limited by default.
+
+Conflict with `exclude_interface`.
+
+#### exclude_interface
+
+Exclude interfaces in route.
+
+Conflict with `include_interface`.
+
#### include_uid
!!! error ""
diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md
index 350c8d9ab1..fbd10abf7e 100644
--- a/docs/configuration/inbound/tun.zh.md
+++ b/docs/configuration/inbound/tun.zh.md
@@ -24,6 +24,12 @@
],
"endpoint_independent_nat": false,
"stack": "system",
+ "include_interface": [
+ "lan0"
+ ],
+ "exclude_interface": [
+ "lan1"
+ ],
"include_uid": [
0
],
@@ -149,6 +155,22 @@ TCP/IP 栈。
默认安装不包含 gVisor 和 LWIP 栈,请参阅 [安装](/zh/#_2)。
+#### include_interface
+
+!!! error ""
+
+ 接口规则仅在 Linux 下被支持,并且需要 `auto_route`。
+
+限制被路由的接口。默认不限制。
+
+与 `exclude_interface` 冲突。
+
+#### exclude_interface
+
+排除路由的接口。
+
+与 `include_interface` 冲突。
+
#### include_uid
!!! error ""
diff --git a/docs/configuration/outbound/tuic.md b/docs/configuration/outbound/tuic.md
index d46d4cb4c6..bbcc199ff2 100644
--- a/docs/configuration/outbound/tuic.md
+++ b/docs/configuration/outbound/tuic.md
@@ -11,6 +11,7 @@
"password": "hello",
"congestion_control": "cubic",
"udp_relay_mode": "native",
+ "udp_over_stream": false,
"zero_rtt_handshake": false,
"heartbeat": "10s",
"network": "tcp",
@@ -67,6 +68,19 @@ UDP packet relay mode
`native` is used by default.
+Conflict with `udp_over_stream`.
+
+#### udp_over_stream
+
+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).
+
+Conflict with `udp_relay_mode`.
+
#### network
Enabled network
diff --git a/docs/configuration/outbound/tuic.zh.md b/docs/configuration/outbound/tuic.zh.md
index 9d19078a9d..ce5a18999b 100644
--- a/docs/configuration/outbound/tuic.zh.md
+++ b/docs/configuration/outbound/tuic.zh.md
@@ -11,6 +11,7 @@
"password": "hello",
"congestion_control": "cubic",
"udp_relay_mode": "native",
+ "udp_over_stream": false,
"zero_rtt_handshake": false,
"heartbeat": "10s",
"network": "tcp",
@@ -65,6 +66,15 @@ UDP 包中继模式
| native | 原生 UDP |
| quic | 使用 QUIC 流的无损 UDP 中继,引入了额外的开销 |
+与 `udp_over_stream` 冲突。
+
+#### udp_over_stream
+
+这是 TUIC 的 [UDP over TCP 协议](/configuration/shared/udp-over-tcp) 移植, 旨在提供 TUIC 不提供的 基于 QUIC 流的 UDP 中继模式。 由于它是一个附加协议,因此您需要使用 sing-box 或其他兼容的程序作为服务器。
+
+此模式在正确的 UDP 代理场景中没有任何积极作用,仅适用于中继流式 UDP 流量(基本上是 QUIC 流)。
+
+与 `udp_relay_mode` 冲突。
#### zero_rtt_handshake
diff --git a/docs/configuration/route/index.zh.md b/docs/configuration/route/index.zh.md
index 8525f7b080..c05bb2e14a 100644
--- a/docs/configuration/route/index.zh.md
+++ b/docs/configuration/route/index.zh.md
@@ -24,7 +24,6 @@
|------------|-------------------------|
| `geoip` | [GeoIP](./geoip) |
| `geosite` | [GeoSite](./geosite) |
-| `ip_rules` | 一组 [IP 路由规则](./ip-rule) |
| `rules` | 一组 [路由规则](./rule) |
#### final
diff --git a/docs/configuration/route/ip-rule.md b/docs/configuration/route/ip-rule.md
deleted file mode 100644
index 352c39f82b..0000000000
--- a/docs/configuration/route/ip-rule.md
+++ /dev/null
@@ -1,205 +0,0 @@
-### Structure
-
-```json
-{
- "route": {
- "ip_rules": [
- {
- "inbound": [
- "mixed-in"
- ],
- "ip_version": 6,
- "network": [
- "tcp"
- ],
- "domain": [
- "test.com"
- ],
- "domain_suffix": [
- ".cn"
- ],
- "domain_keyword": [
- "test"
- ],
- "domain_regex": [
- "^stun\\..+"
- ],
- "geosite": [
- "cn"
- ],
- "source_geoip": [
- "private"
- ],
- "geoip": [
- "cn"
- ],
- "source_ip_cidr": [
- "10.0.0.0/24",
- "192.168.0.1"
- ],
- "ip_cidr": [
- "10.0.0.0/24",
- "192.168.0.1"
- ],
- "source_port": [
- 12345
- ],
- "source_port_range": [
- "1000:2000",
- ":3000",
- "4000:"
- ],
- "port": [
- 80,
- 443
- ],
- "port_range": [
- "1000:2000",
- ":3000",
- "4000:"
- ],
- "invert": false,
- "action": "direct",
- "outbound": "wireguard"
- },
- {
- "type": "logical",
- "mode": "and",
- "rules": [],
- "invert": false,
- "action": "direct",
- "outbound": "wireguard"
- }
- ]
- }
-}
-
-```
-
-!!! note ""
-
- You can ignore the JSON Array [] tag when the content is only one item
-
-### Default Fields
-
-!!! note ""
-
- The default rule uses the following matching logic:
- (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr`) &&
- (`port` || `port_range`) &&
- (`source_geoip` || `source_ip_cidr`) &&
- (`source_port` || `source_port_range`) &&
- `other fields`
-
-#### inbound
-
-Tags of [Inbound](/configuration/inbound).
-
-#### ip_version
-
-4 or 6.
-
-Not limited if empty.
-
-#### network
-
-Match network protocol.
-
-Available values:
-
-* `tcp`
-* `udp`
-* `icmpv4`
-* `icmpv6`
-
-#### domain
-
-Match full domain.
-
-#### domain_suffix
-
-Match domain suffix.
-
-#### domain_keyword
-
-Match domain using keyword.
-
-#### domain_regex
-
-Match domain using regular expression.
-
-#### geosite
-
-Match geosite.
-
-#### source_geoip
-
-Match source geoip.
-
-#### geoip
-
-Match geoip.
-
-#### source_ip_cidr
-
-Match source ip cidr.
-
-#### ip_cidr
-
-Match ip cidr.
-
-#### source_port
-
-Match source port.
-
-#### source_port_range
-
-Match source port range.
-
-#### port
-
-Match port.
-
-#### port_range
-
-Match port range.
-
-#### invert
-
-Invert match result.
-
-#### action
-
-==Required==
-
-| Action | Description |
-|--------|--------------------------------------------------------------------|
-| return | Stop IP routing and assemble the connection to the transport layer |
-| block | Block the connection |
-| direct | Directly forward the connection |
-
-#### outbound
-
-==Required if action is direct==
-
-Tag of the target outbound.
-
-Only outbound which supports IP connection can be used, see [Outbounds that support IP connection](/configuration/outbound/#outbounds-that-support-ip-connection).
-
-### Logical Fields
-
-#### type
-
-`logical`
-
-#### mode
-
-==Required==
-
-`and` or `or`
-
-#### rules
-
-==Required==
-
-Included default rules.
\ No newline at end of file
diff --git a/docs/configuration/route/ip-rule.zh.md b/docs/configuration/route/ip-rule.zh.md
deleted file mode 100644
index d580086ce6..0000000000
--- a/docs/configuration/route/ip-rule.zh.md
+++ /dev/null
@@ -1,204 +0,0 @@
-### 结构
-
-```json
-{
- "route": {
- "ip_rules": [
- {
- "inbound": [
- "mixed-in"
- ],
- "ip_version": 6,
- "network": [
- "tcp"
- ],
- "domain": [
- "test.com"
- ],
- "domain_suffix": [
- ".cn"
- ],
- "domain_keyword": [
- "test"
- ],
- "domain_regex": [
- "^stun\\..+"
- ],
- "geosite": [
- "cn"
- ],
- "source_geoip": [
- "private"
- ],
- "geoip": [
- "cn"
- ],
- "source_ip_cidr": [
- "10.0.0.0/24",
- "192.168.0.1"
- ],
- "ip_cidr": [
- "10.0.0.0/24",
- "192.168.0.1"
- ],
- "source_port": [
- 12345
- ],
- "source_port_range": [
- "1000:2000",
- ":3000",
- "4000:"
- ],
- "port": [
- 80,
- 443
- ],
- "port_range": [
- "1000:2000",
- ":3000",
- "4000:"
- ],
- "invert": false,
- "action": "direct",
- "outbound": "wireguard"
- },
- {
- "type": "logical",
- "mode": "and",
- "rules": [],
- "invert": false,
- "action": "direct",
- "outbound": "wireguard"
- }
- ]
- }
-}
-
-```
-
-!!! note ""
-
- 当内容只有一项时,可以忽略 JSON 数组 [] 标签。
-
-### Default Fields
-
-!!! note ""
-
- 默认规则使用以下匹配逻辑:
- (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr`) &&
- (`port` || `port_range`) &&
- (`source_geoip` || `source_ip_cidr`) &&
- (`source_port` || `source_port_range`) &&
- `other fields`
-
-#### inbound
-
-[入站](/zh/configuration/inbound) 标签。
-
-#### ip_version
-
-4 或 6。
-
-默认不限制。
-
-#### network
-
-匹配网络协议。
-
-可用值:
-
-* `tcp`
-* `udp`
-* `icmpv4`
-* `icmpv6`
-
-#### domain
-
-匹配完整域名。
-
-#### domain_suffix
-
-匹配域名后缀。
-
-#### domain_keyword
-
-匹配域名关键字。
-
-#### domain_regex
-
-匹配域名正则表达式。
-
-#### geosite
-
-匹配 GeoSite。
-
-#### source_geoip
-
-匹配源 GeoIP。
-
-#### geoip
-
-匹配 GeoIP。
-
-#### source_ip_cidr
-
-匹配源 IP CIDR。
-
-#### ip_cidr
-
-匹配 IP CIDR。
-
-#### source_port
-
-匹配源端口。
-
-#### source_port_range
-
-匹配源端口范围。
-
-#### port
-
-匹配端口。
-
-#### port_range
-
-匹配端口范围。
-
-#### invert
-
-反选匹配结果。
-
-#### action
-
-==必填==
-
-| Action | 描述 |
-|--------|---------------------|
-| return | 停止 IP 路由并将该连接组装到传输层 |
-| block | 屏蔽该连接 |
-| direct | 直接转发该连接 |
-
-
-#### outbound
-
-==action 为 direct 则必填==
-
-目标出站的标签。
-
-### 逻辑字段
-
-#### type
-
-`logical`
-
-#### mode
-
-==必填==
-
-`and` 或 `or`
-
-#### rules
-
-==必填==
-
-包括的默认规则。
\ No newline at end of file
diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md
index f4c5b666dd..1f524f0800 100644
--- a/docs/configuration/shared/dial.md
+++ b/docs/configuration/shared/dial.md
@@ -10,6 +10,7 @@
"reuse_addr": false,
"connect_timeout": "5s",
"tcp_fast_open": false,
+ "tcp_multi_path": false,
"udp_fragment": false,
"domain_strategy": "prefer_ipv6",
"fallback_delay": "300ms"
@@ -18,9 +19,9 @@
### Fields
-| Field | Available Context |
-|----------------------------------------------------------------------------------------------------------------------|-------------------|
-| `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open`/ `udp_fragment` /`connect_timeout` | `detour` not set |
+| Field | Available Context |
+|------------------------------------------------------------------------------------------------------------------------------------------|-------------------|
+| `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open` / `tcp_multi_path` / `udp_fragment` /`connect_timeout` | `detour` not set |
#### detour
@@ -54,6 +55,14 @@ Reuse listener address.
Enable TCP Fast Open.
+#### tcp_multi_path
+
+!!! warning ""
+
+ Go 1.21 required.
+
+Enable TCP Multi Path.
+
#### udp_fragment
Enable UDP fragmentation.
diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md
index e084f15e46..62b094f3fb 100644
--- a/docs/configuration/shared/dial.zh.md
+++ b/docs/configuration/shared/dial.zh.md
@@ -10,6 +10,7 @@
"reuse_addr": false,
"connect_timeout": "5s",
"tcp_fast_open": false,
+ "tcp_multi_path": false,
"udp_fragment": false,
"domain_strategy": "prefer_ipv6",
"fallback_delay": "300ms"
@@ -18,9 +19,9 @@
### 字段
-| 字段 | 可用上下文 |
-|----------------------------------------------------------------------------------------------------------------------|--------------|
-| `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open`/ `udp_fragment` /`connect_timeout` | `detour` 未设置 |
+| 字段 | 可用上下文 |
+|------------------------------------------------------------------------------------------------------------------------------------------|--------------|
+| `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open` / `tcp_mutli_path` / `udp_fragment` /`connect_timeout` | `detour` 未设置 |
#### detour
@@ -57,6 +58,14 @@
启用 TCP Fast Open。
+#### tcp_multi_path
+
+!!! warning ""
+
+ 需要 Go 1.21。
+
+启用 TCP Multi Path。
+
#### udp_fragment
启用 UDP 分段。
diff --git a/docs/configuration/shared/listen.md b/docs/configuration/shared/listen.md
index f20d42a3a9..d4a0e58ee3 100644
--- a/docs/configuration/shared/listen.md
+++ b/docs/configuration/shared/listen.md
@@ -5,6 +5,7 @@
"listen": "::",
"listen_port": 5353,
"tcp_fast_open": false,
+ "tcp_multi_path": false,
"udp_fragment": false,
"sniff": false,
"sniff_override_destination": false,
@@ -24,6 +25,7 @@
| `listen` | Needs to listen on TCP or UDP. |
| `listen_port` | Needs to listen on TCP or UDP. |
| `tcp_fast_open` | Needs to listen on TCP. |
+| `tcp_multi_path` | Needs to listen on TCP. |
| `udp_timeout` | Needs to assemble UDP connections, currently Tun and Shadowsocks. |
| `proxy_protocol` | Needs to listen on TCP. |
| `proxy_protocol_accept_no_header` | When `proxy_protocol` enabled |
@@ -42,6 +44,14 @@ Listen port.
Enable TCP Fast Open.
+#### tcp_multi_path
+
+!!! warning ""
+
+ Go 1.21 required.
+
+Enable TCP Multi Path.
+
#### udp_fragment
Enable UDP fragmentation.
diff --git a/docs/configuration/shared/listen.zh.md b/docs/configuration/shared/listen.zh.md
index f9f469d2a3..b25ce295c0 100644
--- a/docs/configuration/shared/listen.zh.md
+++ b/docs/configuration/shared/listen.zh.md
@@ -5,6 +5,7 @@
"listen": "::",
"listen_port": 5353,
"tcp_fast_open": false,
+ "tcp_multi_path": false,
"udp_fragment": false,
"sniff": false,
"sniff_override_destination": false,
@@ -23,6 +24,7 @@
| `listen` | 需要监听 TCP 或 UDP。 |
| `listen_port` | 需要监听 TCP 或 UDP。 |
| `tcp_fast_open` | 需要监听 TCP。 |
+| `tcp_multi_path` | 需要监听 TCP。 |
| `udp_timeout` | 需要组装 UDP 连接, 当前为 Tun 和 Shadowsocks。 |
| `proxy_protocol` | 需要监听 TCP。 |
| `proxy_protocol_accept_no_header` | `proxy_protocol` 启用时 |
@@ -43,6 +45,14 @@
启用 TCP Fast Open。
+#### tcp_multi_path
+
+!!! warning ""
+
+ 需要 Go 1.21。
+
+启用 TCP Multi Path。
+
#### udp_fragment
启用 UDP 分段。
diff --git a/docs/examples/index.md b/docs/examples/index.md
index c39a7f329d..6bbab74145 100644
--- a/docs/examples/index.md
+++ b/docs/examples/index.md
@@ -8,5 +8,4 @@ Configuration examples for sing-box.
* [Shadowsocks](./shadowsocks)
* [ShadowTLS](./shadowtls)
* [Clash API](./clash-api)
-* [WireGuard Direct](./wireguard-direct)
* [FakeIP](./fakeip)
diff --git a/docs/examples/index.zh.md b/docs/examples/index.zh.md
index 2dd801ecc3..7528e5d438 100644
--- a/docs/examples/index.zh.md
+++ b/docs/examples/index.zh.md
@@ -8,5 +8,4 @@ sing-box 的配置示例。
* [Shadowsocks](./shadowsocks)
* [ShadowTLS](./shadowtls)
* [Clash API](./clash-api)
-* [WireGuard Direct](./wireguard-direct)
* [FakeIP](./fakeip)
diff --git a/docs/examples/wireguard-direct.md b/docs/examples/wireguard-direct.md
deleted file mode 100644
index 98e5d57568..0000000000
--- a/docs/examples/wireguard-direct.md
+++ /dev/null
@@ -1,90 +0,0 @@
-# WireGuard Direct
-
-```json
-{
- "dns": {
- "servers": [
- {
- "tag": "google",
- "address": "tls://8.8.8.8"
- },
- {
- "tag": "local",
- "address": "223.5.5.5",
- "detour": "direct"
- }
- ],
- "rules": [
- {
- "geoip": "cn",
- "server": "direct"
- }
- ],
- "reverse_mapping": true
- },
- "inbounds": [
- {
- "type": "tun",
- "tag": "tun",
- "inet4_address": "172.19.0.1/30",
- "auto_route": true,
- "sniff": true,
- "stack": "system"
- }
- ],
- "outbounds": [
- {
- "type": "wireguard",
- "tag": "wg",
- "server": "127.0.0.1",
- "server_port": 2345,
- "local_address": [
- "172.19.0.1/128"
- ],
- "private_key": "KLTnpPY03pig/WC3zR8U7VWmpANHPFh2/4pwICGJ5Fk=",
- "peer_public_key": "uvNabcamf6Rs0vzmcw99jsjTJbxo6eWGOykSY66zsUk="
- },
- {
- "type": "dns",
- "tag": "dns"
- },
- {
- "type": "direct",
- "tag": "direct"
- },
- {
- "type": "block",
- "tag": "block"
- }
- ],
- "route": {
- "ip_rules": [
- {
- "port": 53,
- "action": "return"
- },
- {
- "geoip": "cn",
- "geosite": "cn",
- "action": "return"
- },
- {
- "action": "direct",
- "outbound": "wg"
- }
- ],
- "rules": [
- {
- "protocol": "dns",
- "outbound": "dns"
- },
- {
- "geoip": "cn",
- "geosite": "cn",
- "outbound": "direct"
- }
- ],
- "auto_detect_interface": true
- }
-}
-```
\ No newline at end of file
diff --git a/docs/installation/clients/sfa.md b/docs/installation/clients/sfa.md
index 9ad88b00ee..2ec79ec1c6 100644
--- a/docs/installation/clients/sfa.md
+++ b/docs/installation/clients/sfa.md
@@ -9,7 +9,7 @@ Experimental Android client for sing-box.
#### Download
* [AppCenter](https://install.appcenter.ms/users/nekohasekai/apps/sfa/distribution_groups/publictest)
-* [Github Releases](https://SagerNet/sing-box/releases)
+* [Github Releases](https://github.com/SagerNet/sing-box/releases)
#### Note
diff --git a/docs/installation/clients/sfa.zh.md b/docs/installation/clients/sfa.zh.md
index 1383576ed9..fa005f11cf 100644
--- a/docs/installation/clients/sfa.zh.md
+++ b/docs/installation/clients/sfa.zh.md
@@ -9,7 +9,7 @@
#### 下载
* [AppCenter](https://install.appcenter.ms/users/nekohasekai/apps/sfa/distribution_groups/publictest)
-* [Github Releases](https://SagerNet/sing-box/releases)
+* [Github Releases](https://github.com/SagerNet/sing-box/releases)
#### 注意事项
diff --git a/docs/installation/from-source.md b/docs/installation/from-source.md
index 5ab46bbb56..da0ffc02d8 100644
--- a/docs/installation/from-source.md
+++ b/docs/installation/from-source.md
@@ -1,6 +1,17 @@
# Install from source
-sing-box requires Golang **1.18.5** or a higher version.
+## Requirements
+
+Before sing-box 1.4.0:
+
+* Go 1.18.5 - 1.20.x
+
+Since sing-box 1.4.0:
+
+* Go 1.18.5 - ~
+* Go 1.20.0 - ~ if `with_quic` tag enabled
+
+## Installation
```bash
go install -v github.com/sagernet/sing-box/cmd/sing-box@latest
@@ -9,7 +20,7 @@ go install -v github.com/sagernet/sing-box/cmd/sing-box@latest
Install with options:
```bash
-go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@latest
+go install -v -tags with_quic,with_wireguard github.com/sagernet/sing-box/cmd/sing-box@latest
```
| Build Tag | Description |
diff --git a/experimental/clashapi/cachefile/cache.go b/experimental/clashapi/cachefile/cache.go
index c282d715bb..e135a74283 100644
--- a/experimental/clashapi/cachefile/cache.go
+++ b/experimental/clashapi/cachefile/cache.go
@@ -12,17 +12,21 @@ import (
"go.etcd.io/bbolt"
)
-var bucketSelected = []byte("selected")
+var (
+ bucketSelected = []byte("selected")
+ bucketExpand = []byte("group_expand")
+)
var _ adapter.ClashCacheFile = (*CacheFile)(nil)
type CacheFile struct {
- DB *bbolt.DB
- cacheID []byte
- saveAccess sync.RWMutex
- saveDomain map[netip.Addr]string
- saveAddress4 map[string]netip.Addr
- saveAddress6 map[string]netip.Addr
+ DB *bbolt.DB
+ cacheID []byte
+ saveAccess sync.RWMutex
+ saveDomain map[netip.Addr]string
+ saveAddress4 map[string]netip.Addr
+ saveAddress6 map[string]netip.Addr
+ saveMetadataTimer *time.Timer
}
func Open(path string, cacheID string) (*CacheFile, error) {
@@ -48,21 +52,15 @@ func Open(path string, cacheID string) (*CacheFile, error) {
if name[0] == 0 {
return b.ForEachBucket(func(k []byte) error {
bucketName := string(k)
- if !(bucketName == string(bucketSelected)) {
- delErr := b.DeleteBucket(name)
- if delErr != nil {
- return delErr
- }
+ if !(bucketName == string(bucketSelected) || bucketName == string(bucketExpand)) {
+ _ = b.DeleteBucket(name)
}
return nil
})
} else {
bucketName := string(name)
- if !(bucketName == string(bucketSelected) || strings.HasPrefix(bucketName, fakeipBucketPrefix)) {
- delErr := tx.DeleteBucket(name)
- if delErr != nil {
- return delErr
- }
+ if !(bucketName == string(bucketSelected) || bucketName == string(bucketExpand) || strings.HasPrefix(bucketName, fakeipBucketPrefix)) {
+ _ = tx.DeleteBucket(name)
}
}
return nil
@@ -128,6 +126,36 @@ func (c *CacheFile) StoreSelected(group, selected string) error {
})
}
+func (c *CacheFile) LoadGroupExpand(group string) (isExpand bool, loaded bool) {
+ c.DB.View(func(t *bbolt.Tx) error {
+ bucket := c.bucket(t, bucketExpand)
+ if bucket == nil {
+ return nil
+ }
+ expandBytes := bucket.Get([]byte(group))
+ if len(expandBytes) == 1 {
+ isExpand = expandBytes[0] == 1
+ loaded = true
+ }
+ return nil
+ })
+ return
+}
+
+func (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error {
+ return c.DB.Batch(func(t *bbolt.Tx) error {
+ bucket, err := c.createBucket(t, bucketExpand)
+ if err != nil {
+ return err
+ }
+ if isExpand {
+ return bucket.Put([]byte(group), []byte{1})
+ } else {
+ return bucket.Put([]byte(group), []byte{0})
+ }
+ })
+}
+
func (c *CacheFile) Close() error {
return c.DB.Close()
}
diff --git a/experimental/clashapi/cachefile/fakeip.go b/experimental/clashapi/cachefile/fakeip.go
index cdc279f54c..a2396b591b 100644
--- a/experimental/clashapi/cachefile/fakeip.go
+++ b/experimental/clashapi/cachefile/fakeip.go
@@ -3,6 +3,7 @@ package cachefile
import (
"net/netip"
"os"
+ "time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common/logger"
@@ -57,6 +58,15 @@ func (c *CacheFile) FakeIPSaveMetadata(metadata *adapter.FakeIPMetadata) error {
})
}
+func (c *CacheFile) FakeIPSaveMetadataAsync(metadata *adapter.FakeIPMetadata) {
+ if timer := c.saveMetadataTimer; timer != nil {
+ timer.Stop()
+ }
+ c.saveMetadataTimer = time.AfterFunc(10*time.Second, func() {
+ _ = c.FakeIPSaveMetadata(metadata)
+ })
+}
+
func (c *CacheFile) FakeIPStore(address netip.Addr, domain string) error {
return c.DB.Batch(func(tx *bbolt.Tx) error {
bucket, err := tx.CreateBucketIfNotExists(bucketFakeIP)
diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go
index 2c422b9db8..c8a55c584b 100644
--- a/experimental/clashapi/server.go
+++ b/experimental/clashapi/server.go
@@ -52,6 +52,7 @@ type Server struct {
cacheID string
cacheFile adapter.ClashCacheFile
+ externalController bool
externalUI string
externalUIDownloadURL string
externalUIDownloadDetour string
@@ -71,6 +72,7 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
trafficManager: trafficManager,
mode: strings.ToLower(options.DefaultMode),
storeSelected: options.StoreSelected,
+ externalController: options.ExternalController != "",
storeFakeIP: options.StoreFakeIP,
externalUIDownloadURL: options.ExternalUIDownloadURL,
externalUIDownloadDetour: options.ExternalUIDownloadDetour,
@@ -82,7 +84,7 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
if server.mode == "" {
server.mode = "rule"
}
- if options.StoreSelected || options.StoreFakeIP {
+ if options.StoreSelected || options.StoreFakeIP || options.ExternalController == "" {
cachePath := os.ExpandEnv(options.CacheFile)
if cachePath == "" {
cachePath = "cache.db"
@@ -146,18 +148,20 @@ func (s *Server) PreStart() error {
}
func (s *Server) Start() error {
- s.checkAndDownloadExternalUI()
- listener, err := net.Listen("tcp", s.httpServer.Addr)
- if err != nil {
- return E.Cause(err, "external controller listen error")
- }
- s.logger.Info("restful api listening at ", listener.Addr())
- go func() {
- err = s.httpServer.Serve(listener)
- if err != nil && !errors.Is(err, http.ErrServerClosed) {
- s.logger.Error("external controller serve error: ", err)
+ if s.externalController {
+ s.checkAndDownloadExternalUI()
+ listener, err := net.Listen("tcp", s.httpServer.Addr)
+ if err != nil {
+ return E.Cause(err, "external controller listen error")
}
- }()
+ s.logger.Info("restful api listening at ", listener.Addr())
+ go func() {
+ err = s.httpServer.Serve(listener)
+ if err != nil && !errors.Is(err, http.ErrServerClosed) {
+ s.logger.Error("external controller serve error: ", err)
+ }
+ }()
+ }
return nil
}
diff --git a/experimental/libbox/command.go b/experimental/libbox/command.go
index dff012a2db..7824a87d68 100644
--- a/experimental/libbox/command.go
+++ b/experimental/libbox/command.go
@@ -8,4 +8,5 @@ const (
CommandGroup
CommandSelectOutbound
CommandURLTest
+ CommandGroupExpand
)
diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go
index 11dccdd3ed..720ffe62d7 100644
--- a/experimental/libbox/command_client.go
+++ b/experimental/libbox/command_client.go
@@ -32,7 +32,7 @@ func NewStandaloneCommandClient() *CommandClient {
return new(CommandClient)
}
-func NewCommandClient(sharedDirectory string, handler CommandClientHandler, options *CommandClientOptions) *CommandClient {
+func NewCommandClient(handler CommandClientHandler, options *CommandClientOptions) *CommandClient {
return &CommandClient{
handler: handler,
options: common.PtrValueOrDefault(options),
diff --git a/experimental/libbox/command_group.go b/experimental/libbox/command_group.go
index fef043cb5c..a65fa1d3b5 100644
--- a/experimental/libbox/command_group.go
+++ b/experimental/libbox/command_group.go
@@ -9,6 +9,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/urltest"
"github.com/sagernet/sing-box/outbound"
+ E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/service"
)
@@ -18,6 +19,7 @@ type OutboundGroup struct {
Type string
Selectable bool
Selected string
+ IsExpand bool
items []*OutboundGroupItem
}
@@ -114,6 +116,11 @@ func readGroups(reader io.Reader) (OutboundGroupIterator, error) {
return nil, err
}
+ err = binary.Read(reader, binary.BigEndian, &group.IsExpand)
+ if err != nil {
+ return nil, err
+ }
+
var itemLength uint16
err = binary.Read(reader, binary.BigEndian, &itemLength)
if err != nil {
@@ -152,6 +159,10 @@ func readGroups(reader io.Reader) (OutboundGroupIterator, error) {
func writeGroups(writer io.Writer, boxService *BoxService) error {
historyStorage := service.PtrFromContext[urltest.HistoryStorage](boxService.ctx)
+ var cacheFile adapter.ClashCacheFile
+ if clashServer := boxService.instance.Router().ClashServer(); clashServer != nil {
+ cacheFile = clashServer.CacheFile()
+ }
outbounds := boxService.instance.Router().Outbounds()
var iGroups []adapter.OutboundGroup
@@ -167,6 +178,11 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
group.Type = iGroup.Type()
_, group.Selectable = iGroup.(*outbound.Selector)
group.Selected = iGroup.Now()
+ if cacheFile != nil {
+ if isExpand, loaded := cacheFile.LoadGroupExpand(group.Tag); loaded {
+ group.IsExpand = isExpand
+ }
+ }
for _, itemTag := range iGroup.All() {
itemOutbound, isLoaded := boxService.instance.Router().Outbound(itemTag)
@@ -207,6 +223,10 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
if err != nil {
return err
}
+ err = binary.Write(writer, binary.BigEndian, group.IsExpand)
+ if err != nil {
+ return err
+ }
err = binary.Write(writer, binary.BigEndian, uint16(len(group.items)))
if err != nil {
return err
@@ -232,3 +252,50 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
}
return nil
}
+
+func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error {
+ conn, err := c.directConnect()
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+ err = binary.Write(conn, binary.BigEndian, uint8(CommandGroupExpand))
+ if err != nil {
+ return err
+ }
+ err = rw.WriteVString(conn, groupTag)
+ if err != nil {
+ return err
+ }
+ err = binary.Write(conn, binary.BigEndian, isExpand)
+ if err != nil {
+ return err
+ }
+ return readError(conn)
+}
+
+func (s *CommandServer) handleSetGroupExpand(conn net.Conn) error {
+ defer conn.Close()
+ groupTag, err := rw.ReadVString(conn)
+ if err != nil {
+ return err
+ }
+ var isExpand bool
+ err = binary.Read(conn, binary.BigEndian, &isExpand)
+ if err != nil {
+ return err
+ }
+ service := s.service
+ if service == nil {
+ return writeError(conn, E.New("service not ready"))
+ }
+ if clashServer := service.instance.Router().ClashServer(); clashServer != nil {
+ if cacheFile := clashServer.CacheFile(); cacheFile != nil {
+ err = cacheFile.StoreGroupExpand(groupTag, isExpand)
+ if err != nil {
+ return writeError(conn, err)
+ }
+ }
+ }
+ return writeError(conn, nil)
+}
diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go
index 1aeb12afe1..ba2d573e42 100644
--- a/experimental/libbox/command_server.go
+++ b/experimental/libbox/command_server.go
@@ -154,6 +154,8 @@ func (s *CommandServer) handleConnection(conn net.Conn) error {
return s.handleSelectOutbound(conn)
case CommandURLTest:
return s.handleURLTest(conn)
+ case CommandGroupExpand:
+ return s.handleSetGroupExpand(conn)
default:
return E.New("unknown command: ", command)
}
diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go
index 14f0479c5c..a24a0e8779 100644
--- a/experimental/libbox/service.go
+++ b/experimental/libbox/service.go
@@ -3,6 +3,7 @@ package libbox
import (
"context"
"net/netip"
+ runtimeDebug "runtime/debug"
"syscall"
"github.com/sagernet/sing-box"
@@ -35,6 +36,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
if err != nil {
return nil, err
}
+ runtimeDebug.FreeOSMemory()
ctx, cancel := context.WithCancel(context.Background())
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
ctx = service.ContextWithPtr(ctx, urltest.NewHistoryStorage())
@@ -49,6 +51,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
cancel()
return nil, E.Cause(err, "create service")
}
+ runtimeDebug.FreeOSMemory()
return &BoxService{
ctx: ctx,
cancel: cancel,
diff --git a/go.mod b/go.mod
index 32765d4937..b985e8c104 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/sagernet/sing-box
-go 1.18
+go 1.20
require (
berty.tech/go-libtor v1.0.385
@@ -23,21 +23,21 @@ require (
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0
github.com/sagernet/gomobile v0.0.0-20230728014906-3de089147f59
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2
- github.com/sagernet/quic-go v0.0.0-20230731154841-cdc97aca6239
+ github.com/sagernet/quic-go v0.0.0-20230731012313-1327e4015111
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
- github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a
+ github.com/sagernet/sing v0.2.10-0.20230802105922-c6a69b4912ee
github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659
- github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21
+ github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c
github.com/sagernet/sing-shadowsocks v0.2.4
github.com/sagernet/sing-shadowsocks2 v0.1.3
github.com/sagernet/sing-shadowtls v0.1.4
- github.com/sagernet/sing-tun v0.1.12-0.20230807113616-ffdbe07853b3
+ github.com/sagernet/sing-tun v0.1.11
github.com/sagernet/sing-vmess v0.1.7
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
- github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
+ github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e
- github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77
+ github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.4
go.etcd.io/bbolt v1.3.7
@@ -60,8 +60,9 @@ require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/cloudflare/circl v1.2.1-0.20221019164342-6ab4dfed8f3c // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
- github.com/go-ole/go-ole v1.2.6 // indirect
+ github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
+ github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
@@ -76,9 +77,7 @@ require (
github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
- github.com/quic-go/qtls-go1-18 v0.2.0 // indirect
- github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
- github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
+ github.com/quic-go/qtls-go1-20 v0.3.1 // indirect
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
diff --git a/go.sum b/go.sum
index d5b74d068c..caae816728 100644
--- a/go.sum
+++ b/go.sum
@@ -34,13 +34,14 @@ github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vz
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
-github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
-github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
+github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
@@ -93,12 +94,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
-github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U=
-github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc=
-github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
-github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
-github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
-github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
+github.com/quic-go/qtls-go1-20 v0.3.1 h1:O4BLOM3hwfVF3AcktIylQXyl7Yi2iBNVy5QsV+ySxbg=
+github.com/quic-go/qtls-go1-20 v0.3.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 h1:KyhtFFt1Jtp5vW2ohNvstvQffTOQ/s5vENuGXzdA+TM=
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0/go.mod h1:D4SFEOkJK+4W1v86ZhX0jPM0rAL498fyQAChqMtes/I=
@@ -110,38 +107,38 @@ github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 h1:dnkKrzapqtAwjTS
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2/go.mod h1:1JUiV7nGuf++YFm9eWZ8q2lrwHmhcUGzptMl/vL1+LA=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
-github.com/sagernet/quic-go v0.0.0-20230731154841-cdc97aca6239 h1:UwKDwYDzGc9FGBYm3pr7SpaEQcjYdnfjtRDvePHtcBA=
-github.com/sagernet/quic-go v0.0.0-20230731154841-cdc97aca6239/go.mod h1:5rilP6WxqIl/4ypZbMjr+MK+STxuCEvO5yVtEyYNZ6g=
+github.com/sagernet/quic-go v0.0.0-20230821100820-d38697ecdbb0 h1:NCda9SkbANuTWSxf6GVKNXHfYgOIV3byai5T5J9R0UU=
+github.com/sagernet/quic-go v0.0.0-20230821100820-d38697ecdbb0/go.mod h1:w+nln6f/ZtyPpGbFxmgd5iYFVMmgS+gpD5hu5GAqC1I=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
-github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a h1:b89t6Mjgk4rJ5lrNMnCzy1/J116XkhgdB3YNd9FHyF4=
-github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA=
+github.com/sagernet/sing v0.2.10-0.20230821073500-620f3a3b882d h1:4kgoOCE48CuQcBUcoRnE0QTPXkl8yM8i7Nipmzp/978=
+github.com/sagernet/sing v0.2.10-0.20230821073500-620f3a3b882d/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA=
github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 h1:1DAKccGNqTYJ8nsBR765FS0LVBVXfuFlFAHqKsGN3EI=
github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659/go.mod h1:W7GHTZFS8RkoLI3bA2LFY27/0E+uoQESWtMFLepO/JA=
-github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21 h1:IQ7oBBKz+lwIqwI9IMStlQ9YSUu3eKJmNTip0aLbvOI=
-github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY=
+github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c h1:35/FowAvt3Z62mck0TXzVc4jS5R5CWq62qcV2P1cp0I=
+github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY=
github.com/sagernet/sing-shadowsocks v0.2.4 h1:s/CqXlvFAZhlIoHWUwPw5CoNnQ9Ibki9pckjuugtVfY=
github.com/sagernet/sing-shadowsocks v0.2.4/go.mod h1:80fNKP0wnqlu85GZXV1H1vDPC/2t+dQbFggOw4XuFUM=
github.com/sagernet/sing-shadowsocks2 v0.1.3 h1:WXoLvCFi5JTFBRYorf1YePGYIQyJ/zbsBM6Fwbl5kGA=
github.com/sagernet/sing-shadowsocks2 v0.1.3/go.mod h1:DOhJc/cLeqRv0wuePrQso+iUmDxOnWF4eT/oMcRzYFw=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
-github.com/sagernet/sing-tun v0.1.12-0.20230807113616-ffdbe07853b3 h1:vXVA/3D3hq0f1WzfhhwSQLIF/BNTfLUS0/1FkFZnTFA=
-github.com/sagernet/sing-tun v0.1.12-0.20230807113616-ffdbe07853b3/go.mod h1:8SV1M7BATTt5P4ZA9Q5m5UCvp5wWoq9c002gipjlRIs=
+github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641 h1:a8lktNrCWZJisB+nPraW+qB73ZofgPtGmlfqNYcO79g=
+github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641/go.mod h1:+YImslQMLgMQcVgZZ9IK4ue1o/605VSU90amHUcp4hA=
github.com/sagernet/sing-vmess v0.1.7 h1:TM8FFLsXmlXH9XT8/oDgc6PC5BOzrg6OzyEe01is2r4=
github.com/sagernet/sing-vmess v0.1.7/go.mod h1:1qkC1L1T2sxnS/NuO6HU72S8TkltV+EXoKGR29m/Yss=
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
-github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 h1:2ItpW1nMNkPzmBTxV0/eClCklHrFSQMnUGcpUmJxVeE=
-github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9/go.mod h1:FUyTEc5ye5NjKnDTDMuiLF2M6T4BE6y6KZuax//UCEg=
+github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 h1:Px+hN4Vzgx+iCGVnWH5A8eR7JhNnIV3rGQmBxA7cw6Q=
+github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6/go.mod h1:zovq6vTvEM6ECiqE3Eeb9rpIylPpamPcmrJ9tv0Bt0M=
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4=
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
-github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 h1:g6QtRWQ2dKX7EQP++1JLNtw4C2TNxd4/ov8YUpOPOSo=
-github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77/go.mod h1:pJDdXzZIwJ+2vmnT0TKzmf8meeum+e2mTDSehw79eE0=
+github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho=
+github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
@@ -158,6 +155,7 @@ github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gV
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
@@ -173,33 +171,46 @@ go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk=
go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28 h1:zLxFnORHDFTSkJPawMU7LzsuGQJ4MUFS653jJHpORow=
go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
@@ -207,9 +218,14 @@ golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM=
diff --git a/inbound/default_tcp.go b/inbound/default_tcp.go
index 238762dc7d..ef01bfac02 100644
--- a/inbound/default_tcp.go
+++ b/inbound/default_tcp.go
@@ -10,17 +10,26 @@ import (
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"
)
func (a *myInboundAdapter) ListenTCP() (net.Listener, error) {
var err error
bindAddr := M.SocksaddrFrom(a.listenOptions.Listen.Build(), a.listenOptions.ListenPort)
var tcpListener net.Listener
- if !a.listenOptions.TCPFastOpen {
- tcpListener, err = net.ListenTCP(M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.TCPAddr())
+ var listenConfig net.ListenConfig
+ if a.listenOptions.TCPMultiPath {
+ if !go121Available {
+ return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")
+ }
+ setMultiPathTCP(&listenConfig)
+ }
+ if a.listenOptions.TCPFastOpen {
+ if !go120Available {
+ return nil, E.New("TCP Fast Open requires go1.20, please recompile your binary.")
+ }
+ tcpListener, err = listenTFO(listenConfig, a.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
} else {
- tcpListener, err = tfo.ListenTCP(M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.TCPAddr())
+ tcpListener, err = listenConfig.Listen(a.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
}
if err == nil {
a.logger.Info("tcp server started at ", tcpListener.Addr())
diff --git a/inbound/default_tcp_go1.20.go b/inbound/default_tcp_go1.20.go
new file mode 100644
index 0000000000..ee3731ecda
--- /dev/null
+++ b/inbound/default_tcp_go1.20.go
@@ -0,0 +1,18 @@
+//go:build go1.20
+
+package inbound
+
+import (
+ "context"
+ "net"
+
+ "github.com/sagernet/tfo-go"
+)
+
+const go120Available = true
+
+func listenTFO(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.Listener, error) {
+ var tfoConfig tfo.ListenConfig
+ tfoConfig.ListenConfig = listenConfig
+ return tfoConfig.Listen(ctx, network, address)
+}
diff --git a/inbound/default_tcp_go1.21.go b/inbound/default_tcp_go1.21.go
new file mode 100644
index 0000000000..906818cbe2
--- /dev/null
+++ b/inbound/default_tcp_go1.21.go
@@ -0,0 +1,11 @@
+//go:build go1.21
+
+package inbound
+
+import "net"
+
+const go121Available = true
+
+func setMultiPathTCP(listenConfig *net.ListenConfig) {
+ listenConfig.SetMultipathTCP(true)
+}
diff --git a/inbound/default_tcp_nongo1.20.go b/inbound/default_tcp_nongo1.20.go
new file mode 100644
index 0000000000..e7a026bc85
--- /dev/null
+++ b/inbound/default_tcp_nongo1.20.go
@@ -0,0 +1,15 @@
+//go:build !go1.20
+
+package inbound
+
+import (
+ "context"
+ "net"
+ "os"
+)
+
+const go120Available = false
+
+func listenTFO(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.Listener, error) {
+ return nil, os.ErrInvalid
+}
diff --git a/inbound/default_tcp_nongo1.21.go b/inbound/default_tcp_nongo1.21.go
new file mode 100644
index 0000000000..d19adb1957
--- /dev/null
+++ b/inbound/default_tcp_nongo1.21.go
@@ -0,0 +1,10 @@
+//go:build !go1.21
+
+package inbound
+
+import "net"
+
+const go121Available = false
+
+func setMultiPathTCP(listenConfig *net.ListenConfig) {
+}
diff --git a/inbound/hysteria.go b/inbound/hysteria.go
index 9830276cb5..0e13bdcdcd 100644
--- a/inbound/hysteria.go
+++ b/inbound/hysteria.go
@@ -244,7 +244,7 @@ func (h *Hysteria) accept(ctx context.Context, conn quic.Connection) error {
func (h *Hysteria) udpRecvLoop(conn quic.Connection) {
for {
- packet, err := conn.ReceiveMessage()
+ packet, err := conn.ReceiveMessage(h.ctx)
if err != nil {
return
}
diff --git a/inbound/shadowtls.go b/inbound/shadowtls.go
index cd7a4db8cc..59b7c10770 100644
--- a/inbound/shadowtls.go
+++ b/inbound/shadowtls.go
@@ -40,12 +40,20 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context
if options.Version > 1 {
handshakeForServerName = make(map[string]shadowtls.HandshakeConfig)
for serverName, serverOptions := range options.HandshakeForServerName {
+ handshakeDialer, err := dialer.New(router, serverOptions.DialerOptions)
+ if err != nil {
+ return nil, err
+ }
handshakeForServerName[serverName] = shadowtls.HandshakeConfig{
Server: serverOptions.ServerOptions.Build(),
- Dialer: dialer.New(router, serverOptions.DialerOptions),
+ Dialer: handshakeDialer,
}
}
}
+ handshakeDialer, err := dialer.New(router, options.Handshake.DialerOptions)
+ if err != nil {
+ return nil, err
+ }
service, err := shadowtls.NewService(shadowtls.ServiceConfig{
Version: options.Version,
Password: options.Password,
@@ -54,7 +62,7 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context
}),
Handshake: shadowtls.HandshakeConfig{
Server: options.Handshake.ServerOptions.Build(),
- Dialer: dialer.New(router, options.Handshake.DialerOptions),
+ Dialer: handshakeDialer,
},
HandshakeForServerName: handshakeForServerName,
StrictMode: options.StrictMode,
diff --git a/inbound/tuic.go b/inbound/tuic.go
index f8ce24a0a7..9b5c4ef377 100644
--- a/inbound/tuic.go
+++ b/inbound/tuic.go
@@ -99,6 +99,12 @@ func (h *TUIC) newPacketConnection(ctx context.Context, conn N.PacketConn, metad
}
func (h *TUIC) Start() error {
+ if h.tlsConfig != nil {
+ err := h.tlsConfig.Start()
+ if err != nil {
+ return err
+ }
+ }
packetConn, err := h.myInboundAdapter.ListenUDP()
if err != nil {
return err
diff --git a/inbound/tun.go b/inbound/tun.go
index cb94dc2340..9156d20361 100644
--- a/inbound/tun.go
+++ b/inbound/tun.go
@@ -77,6 +77,8 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
Inet6Address: common.Map(options.Inet6Address, option.ListenPrefix.Build),
AutoRoute: options.AutoRoute,
StrictRoute: options.StrictRoute,
+ IncludeInterface: options.IncludeInterface,
+ ExcludeInterface: options.ExcludeInterface,
Inet4RouteAddress: common.Map(options.Inet4RouteAddress, option.ListenPrefix.Build),
Inet6RouteAddress: common.Map(options.Inet6RouteAddress, option.ListenPrefix.Build),
IncludeUID: includeUID,
diff --git a/mkdocs.yml b/mkdocs.yml
index f9fc7179b9..93dfc97296 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -86,6 +86,7 @@ nav:
- Hysteria: configuration/inbound/hysteria.md
- ShadowTLS: configuration/inbound/shadowtls.md
- VLESS: configuration/inbound/vless.md
+ - TUIC: configuration/inbound/tuic.md
- Tun: configuration/inbound/tun.md
- Redirect: configuration/inbound/redirect.md
- TProxy: configuration/inbound/tproxy.md
@@ -103,6 +104,7 @@ nav:
- ShadowTLS: configuration/outbound/shadowtls.md
- ShadowsocksR: configuration/outbound/shadowsocksr.md
- VLESS: configuration/outbound/vless.md
+ - TUIC: configuration/outbound/tuic.md
- Tor: configuration/outbound/tor.md
- SSH: configuration/outbound/ssh.md
- DNS: configuration/outbound/dns.md
@@ -120,7 +122,6 @@ nav:
- Shadowsocks: examples/shadowsocks.md
- ShadowTLS: examples/shadowtls.md
- Clash API: examples/clash-api.md
- - WireGuard Direct: examples/wireguard-direct.md
- FakeIP: examples/fakeip.md
- Contributing:
- contributing/index.md
diff --git a/ntp/service.go b/ntp/service.go
index a8dc6ea2be..3ca1de3c35 100644
--- a/ntp/service.go
+++ b/ntp/service.go
@@ -31,7 +31,7 @@ type Service struct {
clockOffset time.Duration
}
-func NewService(ctx context.Context, router adapter.Router, logger logger.Logger, options option.NTPOptions) *Service {
+func NewService(ctx context.Context, router adapter.Router, logger logger.Logger, options option.NTPOptions) (*Service, error) {
ctx, cancel := common.ContextWithCancelCause(ctx)
server := options.ServerOptions.Build()
if server.Port == 0 {
@@ -43,15 +43,19 @@ func NewService(ctx context.Context, router adapter.Router, logger logger.Logger
} else {
interval = 30 * time.Minute
}
+ outboundDialer, err := dialer.New(router, options.DialerOptions)
+ if err != nil {
+ return nil, err
+ }
return &Service{
ctx: ctx,
cancel: cancel,
server: server,
writeToSystem: options.WriteToSystem,
- dialer: dialer.New(router, options.DialerOptions),
+ dialer: outboundDialer,
logger: logger,
ticker: time.NewTicker(interval),
- }
+ }, nil
}
func (s *Service) Start() error {
diff --git a/option/inbound.go b/option/inbound.go
index b09b3a65a4..64b45e6c1c 100644
--- a/option/inbound.go
+++ b/option/inbound.go
@@ -125,6 +125,7 @@ type ListenOptions struct {
Listen *ListenAddress `json:"listen,omitempty"`
ListenPort uint16 `json:"listen_port,omitempty"`
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
+ TCPMultiPath bool `json:"tcp_multi_path,omitempty"`
UDPFragment *bool `json:"udp_fragment,omitempty"`
UDPFragmentDefault bool `json:"-"`
UDPTimeout int64 `json:"udp_timeout,omitempty"`
diff --git a/option/outbound.go b/option/outbound.go
index ab7aa0eb5d..5e837741a1 100644
--- a/option/outbound.go
+++ b/option/outbound.go
@@ -134,6 +134,7 @@ type DialerOptions struct {
ReuseAddr bool `json:"reuse_addr,omitempty"`
ConnectTimeout Duration `json:"connect_timeout,omitempty"`
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
+ TCPMultiPath bool `json:"tcp_multi_path,omitempty"`
UDPFragment *bool `json:"udp_fragment,omitempty"`
UDPFragmentDefault bool `json:"-"`
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
diff --git a/option/tuic.go b/option/tuic.go
index 98d48be2cf..2720509d65 100644
--- a/option/tuic.go
+++ b/option/tuic.go
@@ -23,6 +23,7 @@ type TUICOutboundOptions struct {
Password string `json:"password,omitempty"`
CongestionControl string `json:"congestion_control,omitempty"`
UDPRelayMode string `json:"udp_relay_mode,omitempty"`
+ UDPOverStream bool `json:"udp_over_stream,omitempty"`
ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"`
Heartbeat Duration `json:"heartbeat,omitempty"`
Network NetworkList `json:"network,omitempty"`
diff --git a/option/tun.go b/option/tun.go
index 731b6eed44..f566f0983f 100644
--- a/option/tun.go
+++ b/option/tun.go
@@ -9,6 +9,8 @@ type TunInboundOptions struct {
StrictRoute bool `json:"strict_route,omitempty"`
Inet4RouteAddress Listable[ListenPrefix] `json:"inet4_route_address,omitempty"`
Inet6RouteAddress Listable[ListenPrefix] `json:"inet6_route_address,omitempty"`
+ IncludeInterface Listable[string] `json:"include_interface,omitempty"`
+ ExcludeInterface Listable[string] `json:"exclude_interface,omitempty"`
IncludeUID Listable[uint32] `json:"include_uid,omitempty"`
IncludeUIDRange Listable[string] `json:"include_uid_range,omitempty"`
ExcludeUID Listable[uint32] `json:"exclude_uid,omitempty"`
diff --git a/outbound/direct.go b/outbound/direct.go
index 5a0cd34dd8..ed1268305a 100644
--- a/outbound/direct.go
+++ b/outbound/direct.go
@@ -38,6 +38,10 @@ type Direct struct {
func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (*Direct, error) {
options.UDPFragmentDefault = true
+ outboundDialer, err := dialer.New(router, options.DialerOptions)
+ if err != nil {
+ return nil, err
+ }
outbound := &Direct{
myOutboundAdapter: myOutboundAdapter{
protocol: C.TypeDirect,
@@ -49,7 +53,7 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti
},
domainStrategy: dns.DomainStrategy(options.DomainStrategy),
fallbackDelay: time.Duration(options.FallbackDelay),
- dialer: dialer.New(router, options.DialerOptions),
+ dialer: outboundDialer,
proxyProto: options.ProxyProtocol,
}
if options.ProxyProtocol > 2 {
diff --git a/outbound/http.go b/outbound/http.go
index e07f2f012e..2e265da188 100644
--- a/outbound/http.go
+++ b/outbound/http.go
@@ -26,7 +26,11 @@ type HTTP struct {
}
func NewHTTP(router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (*HTTP, error) {
- detour, err := tls.NewDialerFromOptions(router, dialer.New(router, options.DialerOptions), options.Server, common.PtrValueOrDefault(options.TLS))
+ outboundDialer, err := dialer.New(router, options.DialerOptions)
+ if err != nil {
+ return nil, err
+ }
+ detour, err := tls.NewDialerFromOptions(router, outboundDialer, options.Server, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}
diff --git a/outbound/hysteria.go b/outbound/hysteria.go
index 2c13ac6cfb..9ed5b6d876 100644
--- a/outbound/hysteria.go
+++ b/outbound/hysteria.go
@@ -117,6 +117,10 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
if down < hysteria.MinSpeedBPS {
return nil, E.New("invalid down speed")
}
+ outboundDialer, err := dialer.New(router, options.DialerOptions)
+ if err != nil {
+ return nil, err
+ }
return &Hysteria{
myOutboundAdapter: myOutboundAdapter{
protocol: C.TypeHysteria,
@@ -127,7 +131,7 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
dependencies: withDialerDependency(options.DialerOptions),
},
ctx: ctx,
- dialer: dialer.New(router, options.DialerOptions),
+ dialer: outboundDialer,
serverAddr: options.ServerOptions.Build(),
tlsConfig: tlsConfig,
quicConfig: quicConfig,
@@ -214,7 +218,7 @@ func (h *Hysteria) offerNew(ctx context.Context) (quic.Connection, error) {
func (h *Hysteria) udpRecvLoop(conn quic.Connection) {
for {
- packet, err := conn.ReceiveMessage()
+ packet, err := conn.ReceiveMessage(h.ctx)
if err != nil {
return
}
diff --git a/outbound/shadowsocks.go b/outbound/shadowsocks.go
index 2f7d92cbf0..c8a9b0a8da 100644
--- a/outbound/shadowsocks.go
+++ b/outbound/shadowsocks.go
@@ -39,6 +39,10 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
if err != nil {
return nil, err
}
+ outboundDialer, err := dialer.New(router, options.DialerOptions)
+ if err != nil {
+ return nil, err
+ }
outbound := &Shadowsocks{
myOutboundAdapter: myOutboundAdapter{
protocol: C.TypeShadowsocks,
@@ -48,7 +52,7 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
- dialer: dialer.New(router, options.DialerOptions),
+ dialer: outboundDialer,
method: method,
serverAddr: options.ServerOptions.Build(),
}
diff --git a/outbound/shadowsocksr.go b/outbound/shadowsocksr.go
index 6b30595df7..f8e4e4b346 100644
--- a/outbound/shadowsocksr.go
+++ b/outbound/shadowsocksr.go
@@ -37,6 +37,10 @@ type ShadowsocksR struct {
}
func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (*ShadowsocksR, error) {
+ outboundDialer, err := dialer.New(router, options.DialerOptions)
+ if err != nil {
+ return nil, err
+ }
outbound := &ShadowsocksR{
myOutboundAdapter: myOutboundAdapter{
protocol: C.TypeShadowsocksR,
@@ -46,11 +50,10 @@ func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.Cont
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
- dialer: dialer.New(router, options.DialerOptions),
+ dialer: outboundDialer,
serverAddr: options.ServerOptions.Build(),
}
var cipher string
- var err error
switch options.Method {
case "none":
cipher = "dummy"
diff --git a/outbound/shadowtls.go b/outbound/shadowtls.go
index 7eeb1baeab..9f02124d1f 100644
--- a/outbound/shadowtls.go
+++ b/outbound/shadowtls.go
@@ -72,11 +72,15 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context
tlsHandshakeFunc = shadowtls.DefaultTLSHandshakeFunc(options.Password, stdTLSConfig)
}
}
+ outboundDialer, err := dialer.New(router, options.DialerOptions)
+ if err != nil {
+ return nil, err
+ }
client, err := shadowtls.NewClient(shadowtls.ClientConfig{
Version: options.Version,
Password: options.Password,
Server: options.ServerOptions.Build(),
- Dialer: dialer.New(router, options.DialerOptions),
+ Dialer: outboundDialer,
TLSHandshake: tlsHandshakeFunc,
Logger: logger,
})
diff --git a/outbound/socks.go b/outbound/socks.go
index 97579fd3a0..48107480e5 100644
--- a/outbound/socks.go
+++ b/outbound/socks.go
@@ -37,6 +37,10 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio
if err != nil {
return nil, err
}
+ outboundDialer, err := dialer.New(router, options.DialerOptions)
+ if err != nil {
+ return nil, err
+ }
outbound := &Socks{
myOutboundAdapter: myOutboundAdapter{
protocol: C.TypeSOCKS,
@@ -46,7 +50,7 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
- client: socks.NewClient(dialer.New(router, options.DialerOptions), options.ServerOptions.Build(), version, options.Username, options.Password),
+ client: socks.NewClient(outboundDialer, options.ServerOptions.Build(), version, options.Username, options.Password),
resolve: version == socks.Version4,
}
uotOptions := common.PtrValueOrDefault(options.UDPOverTCPOptions)
diff --git a/outbound/ssh.go b/outbound/ssh.go
index 93e5a7bc18..0c6a9894dc 100644
--- a/outbound/ssh.go
+++ b/outbound/ssh.go
@@ -44,6 +44,10 @@ type SSH struct {
}
func NewSSH(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (*SSH, error) {
+ outboundDialer, err := dialer.New(router, options.DialerOptions)
+ if err != nil {
+ return nil, err
+ }
outbound := &SSH{
myOutboundAdapter: myOutboundAdapter{
protocol: C.TypeSSH,
@@ -54,7 +58,7 @@ func NewSSH(ctx context.Context, router adapter.Router, logger log.ContextLogger
dependencies: withDialerDependency(options.DialerOptions),
},
ctx: ctx,
- dialer: dialer.New(router, options.DialerOptions),
+ dialer: outboundDialer,
serverAddr: options.ServerOptions.Build(),
user: options.User,
hostKeyAlgorithms: options.HostKeyAlgorithms,
diff --git a/outbound/tor.go b/outbound/tor.go
index 0e81066e87..76c7955da7 100644
--- a/outbound/tor.go
+++ b/outbound/tor.go
@@ -66,6 +66,10 @@ func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger
}
startConf.TorrcFile = torrcFile
}
+ outboundDialer, err := dialer.New(router, options.DialerOptions)
+ if err != nil {
+ return nil, err
+ }
return &Tor{
myOutboundAdapter: myOutboundAdapter{
protocol: C.TypeTor,
@@ -76,7 +80,7 @@ func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger
dependencies: withDialerDependency(options.DialerOptions),
},
ctx: ctx,
- proxy: NewProxyListener(ctx, logger, dialer.New(router, options.DialerOptions)),
+ proxy: NewProxyListener(ctx, logger, outboundDialer),
startConf: &startConf,
options: options.Options,
}, nil
diff --git a/outbound/trojan.go b/outbound/trojan.go
index a12041e47d..db11d1057a 100644
--- a/outbound/trojan.go
+++ b/outbound/trojan.go
@@ -33,6 +33,10 @@ type Trojan struct {
}
func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (*Trojan, error) {
+ outboundDialer, err := dialer.New(router, options.DialerOptions)
+ if err != nil {
+ return nil, err
+ }
outbound := &Trojan{
myOutboundAdapter: myOutboundAdapter{
protocol: C.TypeTrojan,
@@ -42,11 +46,10 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
- dialer: dialer.New(router, options.DialerOptions),
+ dialer: outboundDialer,
serverAddr: options.ServerOptions.Build(),
key: trojan.Key(options.Password),
}
- var err error
if options.TLS != nil {
outbound.tlsConfig, err = tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS))
if err != nil {
diff --git a/outbound/tuic.go b/outbound/tuic.go
index a3c19ad0b1..71148aca74 100644
--- a/outbound/tuic.go
+++ b/outbound/tuic.go
@@ -20,6 +20,7 @@ import (
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/uot"
"github.com/gofrs/uuid/v5"
)
@@ -31,7 +32,8 @@ var (
type TUIC struct {
myOutboundAdapter
- client *tuic.Client
+ client *tuic.Client
+ udpStream bool
}
func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (*TUIC, error) {
@@ -51,21 +53,28 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge
if err != nil {
return nil, E.Cause(err, "invalid uuid")
}
- var udpStream bool
+ var tuicUDPStream bool
+ if options.UDPOverStream && options.UDPRelayMode != "" {
+ return nil, E.New("udp_over_stream is conflict with udp_relay_mode")
+ }
switch options.UDPRelayMode {
case "native":
case "quic":
- udpStream = true
+ tuicUDPStream = true
+ }
+ outboundDialer, err := dialer.New(router, options.DialerOptions)
+ if err != nil {
+ return nil, err
}
client, err := tuic.NewClient(tuic.ClientOptions{
Context: ctx,
- Dialer: dialer.New(router, options.DialerOptions),
+ Dialer: outboundDialer,
ServerAddress: options.ServerOptions.Build(),
TLSConfig: tlsConfig,
UUID: userUUID,
Password: options.Password,
CongestionControl: options.CongestionControl,
- UDPStream: udpStream,
+ UDPStream: tuicUDPStream,
ZeroRTTHandshake: options.ZeroRTTHandshake,
Heartbeat: time.Duration(options.Heartbeat),
})
@@ -81,7 +90,8 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
- client: client,
+ client: client,
+ udpStream: options.UDPOverStream,
}, nil
}
@@ -91,19 +101,43 @@ func (h *TUIC) DialContext(ctx context.Context, network string, destination M.So
h.logger.InfoContext(ctx, "outbound connection to ", destination)
return h.client.DialConn(ctx, destination)
case N.NetworkUDP:
- conn, err := h.ListenPacket(ctx, destination)
- if err != nil {
- return nil, err
+ if h.udpStream {
+ h.logger.InfoContext(ctx, "outbound stream packet connection to ", destination)
+ streamConn, err := h.client.DialConn(ctx, uot.RequestDestination(uot.Version))
+ if err != nil {
+ return nil, err
+ }
+ return uot.NewLazyConn(streamConn, uot.Request{
+ IsConnect: true,
+ Destination: destination,
+ }), nil
+ } else {
+ conn, err := h.ListenPacket(ctx, destination)
+ if err != nil {
+ return nil, err
+ }
+ return bufio.NewBindPacketConn(conn, destination), nil
}
- return bufio.NewBindPacketConn(conn, destination), nil
default:
return nil, E.New("unsupported network: ", network)
}
}
func (h *TUIC) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
- h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
- return h.client.ListenPacket(ctx)
+ if h.udpStream {
+ h.logger.InfoContext(ctx, "outbound stream packet connection to ", destination)
+ streamConn, err := h.client.DialConn(ctx, uot.RequestDestination(uot.Version))
+ if err != nil {
+ return nil, err
+ }
+ return uot.NewLazyConn(streamConn, uot.Request{
+ IsConnect: false,
+ Destination: destination,
+ }), nil
+ } else {
+ h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
+ return h.client.ListenPacket(ctx)
+ }
}
func (h *TUIC) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
@@ -115,7 +149,7 @@ func (h *TUIC) NewPacketConnection(ctx context.Context, conn N.PacketConn, metad
}
func (h *TUIC) InterfaceUpdated() {
- return
+ _ = h.client.CloseWithError(E.New("network changed"))
}
func (h *TUIC) Close() error {
diff --git a/outbound/vless.go b/outbound/vless.go
index 1257446719..4a13040307 100644
--- a/outbound/vless.go
+++ b/outbound/vless.go
@@ -36,6 +36,10 @@ type VLESS struct {
}
func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (*VLESS, error) {
+ outboundDialer, err := dialer.New(router, options.DialerOptions)
+ if err != nil {
+ return nil, err
+ }
outbound := &VLESS{
myOutboundAdapter: myOutboundAdapter{
protocol: C.TypeVLESS,
@@ -45,10 +49,9 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
- dialer: dialer.New(router, options.DialerOptions),
+ dialer: outboundDialer,
serverAddr: options.ServerOptions.Build(),
}
- var err error
if options.TLS != nil {
outbound.tlsConfig, err = tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS))
if err != nil {
diff --git a/outbound/vmess.go b/outbound/vmess.go
index 6f7735fc5c..b07f13d721 100644
--- a/outbound/vmess.go
+++ b/outbound/vmess.go
@@ -35,6 +35,10 @@ type VMess struct {
}
func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (*VMess, error) {
+ outboundDialer, err := dialer.New(router, options.DialerOptions)
+ if err != nil {
+ return nil, err
+ }
outbound := &VMess{
myOutboundAdapter: myOutboundAdapter{
protocol: C.TypeVMess,
@@ -44,10 +48,9 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
- dialer: dialer.New(router, options.DialerOptions),
+ dialer: outboundDialer,
serverAddr: options.ServerOptions.Build(),
}
- var err error
if options.TLS != nil {
outbound.tlsConfig, err = tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS))
if err != nil {
diff --git a/outbound/wireguard.go b/outbound/wireguard.go
index eb8f65db76..bc24e2e786 100644
--- a/outbound/wireguard.go
+++ b/outbound/wireguard.go
@@ -65,7 +65,11 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
connectAddr = options.ServerOptions.Build()
}
}
- outbound.bind = wireguard.NewClientBind(ctx, outbound, dialer.New(router, options.DialerOptions), isConnect, connectAddr, reserved)
+ outboundDialer, err := dialer.New(router, options.DialerOptions)
+ if err != nil {
+ return nil, err
+ }
+ outbound.bind = wireguard.NewClientBind(ctx, outbound, outboundDialer, isConnect, connectAddr, reserved)
localPrefixes := common.Map(options.LocalAddress, option.ListenPrefix.Build)
if len(localPrefixes) == 0 {
return nil, E.New("missing local address")
@@ -157,7 +161,6 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
mtu = 1408
}
var wireTunDevice wireguard.Device
- var err error
if !options.SystemInterface && tun.WithGVisor {
wireTunDevice, err = wireguard.NewStackDevice(localPrefixes, mtu)
} else {
@@ -166,7 +169,7 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
if err != nil {
return nil, E.Cause(err, "create WireGuard device")
}
- wgDevice := device.NewDevice(wireTunDevice, outbound.bind, &device.Logger{
+ wgDevice := device.NewDevice(ctx, wireTunDevice, outbound.bind, &device.Logger{
Verbosef: func(format string, args ...interface{}) {
logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
},
diff --git a/route/router.go b/route/router.go
index 909ff229bc..f2926f8749 100644
--- a/route/router.go
+++ b/route/router.go
@@ -38,7 +38,9 @@ import (
F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
+ serviceNTP "github.com/sagernet/sing/common/ntp"
"github.com/sagernet/sing/common/uot"
+ "github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/pause"
)
@@ -319,7 +321,12 @@ func NewRouter(
}
}
if ntpOptions.Enabled {
- router.timeService = ntp.NewService(ctx, router, logFactory.NewLogger("ntp"), ntpOptions)
+ timeService, err := ntp.NewService(ctx, router, logFactory.NewLogger("ntp"), ntpOptions)
+ if err != nil {
+ return nil, err
+ }
+ service.ContextWith[serviceNTP.TimeService](ctx, timeService)
+ router.timeService = timeService
}
return router, nil
}
@@ -629,6 +636,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
if !loaded {
return E.New("missing fakeip context")
}
+ metadata.OriginDestination = metadata.Destination
metadata.Destination = M.Socksaddr{
Fqdn: domain,
Port: metadata.Destination.Port,
@@ -732,13 +740,12 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
}
metadata.Network = N.NetworkUDP
- var originAddress M.Socksaddr
if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) {
domain, loaded := r.fakeIPStore.Lookup(metadata.Destination.Addr)
if !loaded {
return E.New("missing fakeip context")
}
- originAddress = metadata.Destination
+ metadata.OriginDestination = metadata.Destination
metadata.Destination = M.Socksaddr{
Fqdn: domain,
Port: metadata.Destination.Port,
@@ -752,7 +759,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
conn = deadline.NewPacketConn(bufio.NewNetPacketConn(conn))
}*/
- if metadata.InboundOptions.SniffEnabled {
+ if metadata.InboundOptions.SniffEnabled || metadata.Destination.Addr.IsUnspecified() {
buffer := buf.NewPacket()
buffer.FullReset()
destination, err := conn.ReadPacket(buffer)
@@ -760,20 +767,25 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
buffer.Release()
return err
}
- sniffMetadata, _ := sniff.PeekPacket(ctx, buffer.Bytes(), sniff.DomainNameQuery, sniff.QUICClientHello, sniff.STUNMessage)
- if sniffMetadata != nil {
- metadata.Protocol = sniffMetadata.Protocol
- metadata.Domain = sniffMetadata.Domain
- if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) {
- metadata.Destination = M.Socksaddr{
- Fqdn: metadata.Domain,
- Port: metadata.Destination.Port,
+ if metadata.Destination.Addr.IsUnspecified() {
+ metadata.Destination = destination
+ }
+ if metadata.InboundOptions.SniffEnabled {
+ sniffMetadata, _ := sniff.PeekPacket(ctx, buffer.Bytes(), sniff.DomainNameQuery, sniff.QUICClientHello, sniff.STUNMessage)
+ if sniffMetadata != nil {
+ metadata.Protocol = sniffMetadata.Protocol
+ metadata.Domain = sniffMetadata.Domain
+ if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) {
+ metadata.Destination = M.Socksaddr{
+ Fqdn: metadata.Domain,
+ Port: metadata.Destination.Port,
+ }
+ }
+ if metadata.Domain != "" {
+ r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain)
+ } else {
+ r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol)
}
- }
- if metadata.Domain != "" {
- r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain)
- } else {
- r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol)
}
}
conn = bufio.NewCachedPacketConn(conn, buffer, destination)
@@ -810,8 +822,8 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
conn = statsService.RoutedPacketConnection(metadata.Inbound, detour.Tag(), metadata.User, conn)
}
}
- if originAddress.IsValid() {
- conn = fakeip.NewNATPacketConn(conn, originAddress, metadata.Destination)
+ if metadata.FakeIP {
+ conn = fakeip.NewNATPacketConn(conn, metadata.OriginDestination, metadata.Destination)
}
return detour.NewPacketConnection(ctx, conn, metadata)
}
@@ -976,10 +988,8 @@ func (r *Router) NewError(ctx context.Context, err error) {
func (r *Router) notifyNetworkUpdate(event int) {
if event == tun.EventNoRoute {
- r.logger.Info("paused network")
r.pauseManager.NetworkPause()
- r.logger.Info("isPaused=", r.pauseManager.IsPaused())
- return
+ r.logger.Error("missing default interface")
} else {
r.pauseManager.NetworkWake()
if C.IsAndroid && r.platformInterface == nil {
diff --git a/test/box_test.go b/test/box_test.go
index 0cb533c4a1..4ce61d468e 100644
--- a/test/box_test.go
+++ b/test/box_test.go
@@ -74,6 +74,21 @@ func testSuit(t *testing.T, clientPort uint16, testPort uint16) {
// require.NoError(t, testPacketConnTimeout(t, dialUDP))
}
+func testSuitLargeUDP(t *testing.T, clientPort uint16, testPort uint16) {
+ dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", clientPort), socks.Version5, "", "")
+ dialTCP := func() (net.Conn, error) {
+ return dialer.DialContext(context.Background(), "tcp", M.ParseSocksaddrHostPort("127.0.0.1", testPort))
+ }
+ dialUDP := func() (net.PacketConn, error) {
+ return dialer.ListenPacket(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", testPort))
+ }
+ require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
+ require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))
+ require.NoError(t, testLargeDataWithConn(t, testPort, dialTCP))
+ require.NoError(t, testLargeDataWithPacketConn(t, testPort, dialUDP))
+ require.NoError(t, testLargeDataWithPacketConnSize(t, testPort, 1250, dialUDP))
+}
+
func testTCP(t *testing.T, clientPort uint16, testPort uint16) {
dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", clientPort), socks.Version5, "", "")
dialTCP := func() (net.Conn, error) {
diff --git a/test/clash_test.go b/test/clash_test.go
index 7ea03ebcbf..af9e80b409 100644
--- a/test/clash_test.go
+++ b/test/clash_test.go
@@ -38,8 +38,8 @@ const (
ImageShadowsocksR = "teddysun/shadowsocks-r:latest"
ImageXRayCore = "teddysun/xray:latest"
ImageShadowsocksLegacy = "mritd/shadowsocks:latest"
- ImageTUICServer = ""
- ImageTUICClient = ""
+ ImageTUICServer = "kilvn/tuic-server:latest"
+ ImageTUICClient = "kilvn/tuic-client:latest"
)
var allImages = []string{
@@ -55,8 +55,8 @@ var allImages = []string{
ImageShadowsocksR,
ImageXRayCore,
ImageShadowsocksLegacy,
- // ImageTUICServer,
- // ImageTUICClient,
+ ImageTUICServer,
+ ImageTUICClient,
}
var localIP = netip.MustParseAddr("127.0.0.1")
@@ -364,6 +364,10 @@ func testLargeDataWithConn(t *testing.T, port uint16, cc func() (net.Conn, error
}
func testLargeDataWithPacketConn(t *testing.T, port uint16, pcc func() (net.PacketConn, error)) error {
+ return testLargeDataWithPacketConnSize(t, port, 1024, pcc)
+}
+
+func testLargeDataWithPacketConnSize(t *testing.T, port uint16, chunkSize int, pcc func() (net.PacketConn, error)) error {
l, err := listenPacket("udp", ":"+F.ToString(port))
if err != nil {
return err
@@ -373,7 +377,6 @@ func testLargeDataWithPacketConn(t *testing.T, port uint16, pcc func() (net.Pack
rAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: int(port)}
times := 50
- chunkSize := int64(1024)
pingCh, pongCh, test := newLargeDataPair()
writeRandData := func(pc net.PacketConn, addr net.Addr) (map[int][]byte, error) {
diff --git a/test/config/tuic-client.json b/test/config/tuic-client.json
index c1042b5362..254fba8068 100644
--- a/test/config/tuic-client.json
+++ b/test/config/tuic-client.json
@@ -1,6 +1,7 @@
{
"relay": {
- "server": "127.0.0.1:10000",
+ "server": "example.org:10000",
+ "ip": "127.0.0.1",
"uuid": "FE35D05B-8803-45C4-BAE6-723AD2CD5D3D",
"password": "tuic",
"certificates": [
diff --git a/test/go.mod b/test/go.mod
index f70da1e1d9..e50af86662 100644
--- a/test/go.mod
+++ b/test/go.mod
@@ -1,6 +1,6 @@
module test
-go 1.18
+go 1.20
require github.com/sagernet/sing-box v0.0.0
@@ -10,7 +10,7 @@ require (
github.com/docker/docker v24.0.5+incompatible
github.com/docker/go-connections v0.4.0
github.com/gofrs/uuid/v5 v5.0.0
- github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a
+ github.com/sagernet/sing v0.2.10-0.20230821073500-620f3a3b882d
github.com/sagernet/sing-shadowsocks v0.2.4
github.com/sagernet/sing-shadowsocks2 v0.1.3
github.com/spyzhov/ajson v0.9.0
@@ -37,9 +37,10 @@ require (
github.com/go-chi/chi/v5 v5.0.10 // indirect
github.com/go-chi/cors v1.2.1 // indirect
github.com/go-chi/render v1.0.3 // indirect
- github.com/go-ole/go-ole v1.2.6 // indirect
+ github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
@@ -64,25 +65,23 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
- github.com/quic-go/qtls-go1-18 v0.2.0 // indirect
- github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
- github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
+ github.com/quic-go/qtls-go1-20 v0.3.1 // indirect
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 // indirect
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 // indirect
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
- github.com/sagernet/quic-go v0.0.0-20230731154841-cdc97aca6239 // indirect
+ github.com/sagernet/quic-go v0.0.0-20230821100820-d38697ecdbb0 // indirect
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 // indirect
- github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21 // indirect
+ github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c // indirect
github.com/sagernet/sing-shadowtls v0.1.4 // indirect
- github.com/sagernet/sing-tun v0.1.12-0.20230807113616-ffdbe07853b3 // indirect
+ github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641 // indirect
github.com/sagernet/sing-vmess v0.1.7 // indirect
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
- github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 // indirect
+ github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 // indirect
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 // indirect
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e // indirect
- github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 // indirect
+ github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f // indirect
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
diff --git a/test/go.sum b/test/go.sum
index 742b0068b0..38446a9722 100644
--- a/test/go.sum
+++ b/test/go.sum
@@ -44,8 +44,8 @@ github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vz
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
-github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
-github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
+github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
@@ -53,6 +53,7 @@ github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
@@ -112,12 +113,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
-github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U=
-github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc=
-github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
-github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
-github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
-github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
+github.com/quic-go/qtls-go1-20 v0.3.1 h1:O4BLOM3hwfVF3AcktIylQXyl7Yi2iBNVy5QsV+ySxbg=
+github.com/quic-go/qtls-go1-20 v0.3.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 h1:KyhtFFt1Jtp5vW2ohNvstvQffTOQ/s5vENuGXzdA+TM=
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0/go.mod h1:D4SFEOkJK+4W1v86ZhX0jPM0rAL498fyQAChqMtes/I=
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
@@ -126,43 +123,38 @@ github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 h1:dnkKrzapqtAwjTS
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2/go.mod h1:1JUiV7nGuf++YFm9eWZ8q2lrwHmhcUGzptMl/vL1+LA=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
-github.com/sagernet/quic-go v0.0.0-20230731154841-cdc97aca6239 h1:UwKDwYDzGc9FGBYm3pr7SpaEQcjYdnfjtRDvePHtcBA=
-github.com/sagernet/quic-go v0.0.0-20230731154841-cdc97aca6239/go.mod h1:5rilP6WxqIl/4ypZbMjr+MK+STxuCEvO5yVtEyYNZ6g=
+github.com/sagernet/quic-go v0.0.0-20230821100820-d38697ecdbb0 h1:NCda9SkbANuTWSxf6GVKNXHfYgOIV3byai5T5J9R0UU=
+github.com/sagernet/quic-go v0.0.0-20230821100820-d38697ecdbb0/go.mod h1:w+nln6f/ZtyPpGbFxmgd5iYFVMmgS+gpD5hu5GAqC1I=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
-github.com/sagernet/sing v0.2.10-0.20230802114159-a755de3bbd49 h1:/8bN8GrPvGvhV0oQNWM68aSHa4gKHkfvnjmiu1HdXcQ=
-github.com/sagernet/sing v0.2.10-0.20230802114159-a755de3bbd49/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA=
-github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA=
+github.com/sagernet/sing v0.2.10-0.20230821073500-620f3a3b882d h1:4kgoOCE48CuQcBUcoRnE0QTPXkl8yM8i7Nipmzp/978=
+github.com/sagernet/sing v0.2.10-0.20230821073500-620f3a3b882d/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA=
github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 h1:1DAKccGNqTYJ8nsBR765FS0LVBVXfuFlFAHqKsGN3EI=
github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659/go.mod h1:W7GHTZFS8RkoLI3bA2LFY27/0E+uoQESWtMFLepO/JA=
-github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21 h1:IQ7oBBKz+lwIqwI9IMStlQ9YSUu3eKJmNTip0aLbvOI=
-github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY=
+github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c h1:35/FowAvt3Z62mck0TXzVc4jS5R5CWq62qcV2P1cp0I=
+github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY=
github.com/sagernet/sing-shadowsocks v0.2.4 h1:s/CqXlvFAZhlIoHWUwPw5CoNnQ9Ibki9pckjuugtVfY=
github.com/sagernet/sing-shadowsocks v0.2.4/go.mod h1:80fNKP0wnqlu85GZXV1H1vDPC/2t+dQbFggOw4XuFUM=
github.com/sagernet/sing-shadowsocks2 v0.1.3 h1:WXoLvCFi5JTFBRYorf1YePGYIQyJ/zbsBM6Fwbl5kGA=
github.com/sagernet/sing-shadowsocks2 v0.1.3/go.mod h1:DOhJc/cLeqRv0wuePrQso+iUmDxOnWF4eT/oMcRzYFw=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
-github.com/sagernet/sing-tun v0.1.11 h1:wUfRQZ4eHk8suHkGKEFxjV5uXl3tfZhPm/v14/4lHvk=
-github.com/sagernet/sing-tun v0.1.11/go.mod h1:XsyIVKd/Qp+2SdLZWGbavHtcpE7J7XU3S1zJmcoj9Ck=
-github.com/sagernet/sing-tun v0.1.12-0.20230807042245-c7ebb18a6f97/go.mod h1:XsyIVKd/Qp+2SdLZWGbavHtcpE7J7XU3S1zJmcoj9Ck=
-github.com/sagernet/sing-tun v0.1.12-0.20230807072626-857582039b66/go.mod h1:XsyIVKd/Qp+2SdLZWGbavHtcpE7J7XU3S1zJmcoj9Ck=
-github.com/sagernet/sing-tun v0.1.12-0.20230807111736-0ba0bd509029/go.mod h1:8SV1M7BATTt5P4ZA9Q5m5UCvp5wWoq9c002gipjlRIs=
-github.com/sagernet/sing-tun v0.1.12-0.20230807113616-ffdbe07853b3/go.mod h1:8SV1M7BATTt5P4ZA9Q5m5UCvp5wWoq9c002gipjlRIs=
+github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641 h1:a8lktNrCWZJisB+nPraW+qB73ZofgPtGmlfqNYcO79g=
+github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641/go.mod h1:+YImslQMLgMQcVgZZ9IK4ue1o/605VSU90amHUcp4hA=
github.com/sagernet/sing-vmess v0.1.7 h1:TM8FFLsXmlXH9XT8/oDgc6PC5BOzrg6OzyEe01is2r4=
github.com/sagernet/sing-vmess v0.1.7/go.mod h1:1qkC1L1T2sxnS/NuO6HU72S8TkltV+EXoKGR29m/Yss=
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
-github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 h1:2ItpW1nMNkPzmBTxV0/eClCklHrFSQMnUGcpUmJxVeE=
-github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9/go.mod h1:FUyTEc5ye5NjKnDTDMuiLF2M6T4BE6y6KZuax//UCEg=
+github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 h1:Px+hN4Vzgx+iCGVnWH5A8eR7JhNnIV3rGQmBxA7cw6Q=
+github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6/go.mod h1:zovq6vTvEM6ECiqE3Eeb9rpIylPpamPcmrJ9tv0Bt0M=
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4=
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
-github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 h1:g6QtRWQ2dKX7EQP++1JLNtw4C2TNxd4/ov8YUpOPOSo=
-github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77/go.mod h1:pJDdXzZIwJ+2vmnT0TKzmf8meeum+e2mTDSehw79eE0=
+github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho=
+github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
github.com/spyzhov/ajson v0.9.0 h1:tF46gJGOenYVj+k9K1U1XpCxVWhmiyY5PsVCAs1+OJ0=
@@ -179,6 +171,7 @@ github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695AP
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
@@ -206,6 +199,7 @@ golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaU
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -213,26 +207,30 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -250,6 +248,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/test/tuic_test.go b/test/tuic_test.go
index 3851d132df..cee39d106e 100644
--- a/test/tuic_test.go
+++ b/test/tuic_test.go
@@ -94,7 +94,7 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) {
},
},
})
- testSuit(t, clientPort, testPort)
+ testSuitLargeUDP(t, clientPort, testPort)
}
func TestTUICInbound(t *testing.T) {
@@ -130,6 +130,7 @@ func TestTUICInbound(t *testing.T) {
caPem: "/etc/tuic/ca.pem",
},
})
+ testSuitLargeUDP(t, clientPort, testPort)
}
func TestTUICOutbound(t *testing.T) {
@@ -174,5 +175,5 @@ func TestTUICOutbound(t *testing.T) {
},
},
})
- testSuit(t, clientPort, testPort)
+ testSuitLargeUDP(t, clientPort, testPort)
}
diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go
index e3f1aed805..35e8783fce 100644
--- a/transport/dhcp/server.go
+++ b/transport/dhcp/server.go
@@ -248,10 +248,10 @@ func (t *Transport) recreateServers(iface *net.Interface, serverAddrs []netip.Ad
}), ","), "]")
}
- serverDialer := dialer.NewDefault(t.router, option.DialerOptions{
+ serverDialer := common.Must1(dialer.NewDefault(t.router, option.DialerOptions{
BindInterface: iface.Name,
UDPFragmentDefault: true,
- })
+ }))
var transports []dns.Transport
for _, serverAddr := range serverAddrs {
serverTransport, err := dns.NewUDPTransport(t.name, t.ctx, serverDialer, M.Socksaddr{Addr: serverAddr, Port: 53})
diff --git a/transport/fakeip/memory.go b/transport/fakeip/memory.go
index b9e6a99976..0bca491036 100644
--- a/transport/fakeip/memory.go
+++ b/transport/fakeip/memory.go
@@ -34,6 +34,9 @@ func (s *MemoryStorage) FakeIPSaveMetadata(metadata *adapter.FakeIPMetadata) err
return nil
}
+func (s *MemoryStorage) FakeIPSaveMetadataAsync(metadata *adapter.FakeIPMetadata) {
+}
+
func (s *MemoryStorage) FakeIPStore(address netip.Addr, domain string) error {
s.addressAccess.Lock()
s.domainAccess.Lock()
diff --git a/transport/fakeip/store.go b/transport/fakeip/store.go
index 7571161c3e..96f6bf031e 100644
--- a/transport/fakeip/store.go
+++ b/transport/fakeip/store.go
@@ -99,6 +99,12 @@ func (s *Store) Create(domain string, isIPv6 bool) (netip.Addr, error) {
address = nextAddress
}
s.storage.FakeIPStoreAsync(address, domain, s.logger)
+ s.storage.FakeIPSaveMetadataAsync(&adapter.FakeIPMetadata{
+ Inet4Range: s.inet4Range,
+ Inet6Range: s.inet6Range,
+ Inet4Current: s.inet4Current,
+ Inet6Current: s.inet6Current,
+ })
return address, nil
}
diff --git a/transport/tuic/client.go b/transport/tuic/client.go
index 98cf6281c0..6f8203bf52 100644
--- a/transport/tuic/client.go
+++ b/transport/tuic/client.go
@@ -60,6 +60,7 @@ func NewClient(options ClientOptions) (*Client, error) {
DisablePathMTUDiscovery: !(runtime.GOOS == "windows" || runtime.GOOS == "linux" || runtime.GOOS == "android" || runtime.GOOS == "darwin"),
MaxDatagramFrameSize: 1400,
EnableDatagrams: true,
+ MaxIncomingUniStreams: 1 << 60,
}
switch options.CongestionControl {
case "":
@@ -247,6 +248,7 @@ func (c *clientQUICConnection) closeWithError(err error) {
c.connErr = err
close(c.connDone)
_ = c.quicConn.CloseWithError(0, "")
+ _ = c.rawConn.Close()
})
}
diff --git a/transport/tuic/client_packet.go b/transport/tuic/client_packet.go
index 6460fe2358..b4292e943f 100644
--- a/transport/tuic/client_packet.go
+++ b/transport/tuic/client_packet.go
@@ -11,7 +11,7 @@ import (
func (c *Client) loopMessages(conn *clientQUICConnection) {
for {
- message, err := conn.quicConn.ReceiveMessage()
+ message, err := conn.quicConn.ReceiveMessage(c.ctx)
if err != nil {
conn.closeWithError(E.Cause(err, "receive message"))
return
diff --git a/transport/tuic/packet.go b/transport/tuic/packet.go
index a3a0c35a1b..5e1a80b4f5 100644
--- a/transport/tuic/packet.go
+++ b/transport/tuic/packet.go
@@ -41,7 +41,6 @@ type udpMessage struct {
fragmentTotal uint8
fragmentID uint8
destination M.Socksaddr
- dataLength uint16
data *buf.Buffer
}
@@ -72,7 +71,7 @@ func (m *udpMessage) pack() *buf.Buffer {
}
func (m *udpMessage) headerSize() int {
- return 2 + 10 + addressSerializer.AddrPortLen(m.destination)
+ return 10 + addressSerializer.AddrPortLen(m.destination)
}
func fragUDPMessage(message *udpMessage, maxPacketSize int) []*udpMessage {
@@ -106,18 +105,19 @@ func fragUDPMessage(message *udpMessage, maxPacketSize int) []*udpMessage {
}
type udpPacketConn struct {
- ctx context.Context
- cancel common.ContextCancelCauseFunc
- sessionID uint16
- quicConn quic.Connection
- data chan *udpMessage
- udpStream bool
- udpMTU int
- packetId atomic.Uint32
- closeOnce sync.Once
- isServer bool
- defragger *udpDefragger
- onDestroy func()
+ ctx context.Context
+ cancel common.ContextCancelCauseFunc
+ sessionID uint16
+ quicConn quic.Connection
+ data chan *udpMessage
+ udpStream bool
+ udpMTU int
+ udpMTUTime time.Time
+ packetId atomic.Uint32
+ closeOnce sync.Once
+ isServer bool
+ defragger *udpDefragger
+ onDestroy func()
}
func newUDPPacketConn(ctx context.Context, quicConn quic.Connection, udpStream bool, isServer bool, onDestroy func()) *udpPacketConn {
@@ -186,6 +186,15 @@ func (c *udpPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
}
}
+func (c *udpPacketConn) needFragment() bool {
+ nowTime := time.Now()
+ if c.udpMTU > 0 && nowTime.Sub(c.udpMTUTime) < 5*time.Second {
+ c.udpMTUTime = nowTime
+ return true
+ }
+ return false
+}
+
func (c *udpPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
defer buffer.Release()
select {
@@ -211,7 +220,7 @@ func (c *udpPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr)
}
defer message.releaseMessage()
var err error
- if !c.udpStream && c.udpMTU > 0 && buffer.Len() > c.udpMTU {
+ if !c.udpStream && c.needFragment() && buffer.Len() > c.udpMTU {
err = c.writePackets(fragUDPMessage(message, c.udpMTU))
} else {
err = c.writePacket(message)
@@ -224,6 +233,7 @@ func (c *udpPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr)
return err
}
c.udpMTU = int(tooLargeErr)
+ c.udpMTUTime = time.Now()
return c.writePackets(fragUDPMessage(message, c.udpMTU))
}
@@ -265,6 +275,7 @@ func (c *udpPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
return
}
c.udpMTU = int(tooLargeErr)
+ c.udpMTUTime = time.Now()
err = c.writePackets(fragUDPMessage(message, c.udpMTU))
if err == nil {
return len(p), nil
@@ -414,10 +425,14 @@ func (d *udpDefragger) feed(m *udpMessage) *udpMessage {
}
newMessage := udpMessagePool.Get().(*udpMessage)
*newMessage = *item.messages[0]
- if m.dataLength > 0 {
- newMessage.data = buf.NewSize(int(m.dataLength))
+ var dataLength uint16
+ for _, message := range item.messages {
+ dataLength += uint16(message.data.Len())
+ }
+ if dataLength > 0 {
+ newMessage.data = buf.NewSize(int(dataLength))
for _, message := range item.messages {
- newMessage.data.Write(message.data.Bytes())
+ common.Must1(newMessage.data.Write(message.data.Bytes()))
message.releaseMessage()
}
item.messages = nil
@@ -447,7 +462,8 @@ func readUDPMessage(message *udpMessage, reader io.Reader) error {
if err != nil {
return err
}
- err = binary.Read(reader, binary.BigEndian, &message.dataLength)
+ var dataLength uint16
+ err = binary.Read(reader, binary.BigEndian, &dataLength)
if err != nil {
return err
}
@@ -455,7 +471,7 @@ func readUDPMessage(message *udpMessage, reader io.Reader) error {
if err != nil {
return err
}
- message.data = buf.NewSize(int(message.dataLength))
+ message.data = buf.NewSize(int(dataLength))
_, err = message.data.ReadFullFrom(reader, message.data.FreeLen())
if err != nil {
return err
@@ -481,7 +497,8 @@ func decodeUDPMessage(message *udpMessage, data []byte) error {
if err != nil {
return err
}
- err = binary.Read(reader, binary.BigEndian, &message.dataLength)
+ var dataLength uint16
+ err = binary.Read(reader, binary.BigEndian, &dataLength)
if err != nil {
return err
}
@@ -489,7 +506,7 @@ func decodeUDPMessage(message *udpMessage, data []byte) error {
if err != nil {
return err
}
- if reader.Len() != int(message.dataLength) {
+ if reader.Len() != int(dataLength) {
return io.ErrUnexpectedEOF
}
message.data = buf.As(data[len(data)-reader.Len():])
diff --git a/transport/tuic/server_packet.go b/transport/tuic/server_packet.go
index fe4b57d590..fba6118a33 100644
--- a/transport/tuic/server_packet.go
+++ b/transport/tuic/server_packet.go
@@ -13,7 +13,7 @@ func (s *serverSession) loopMessages() {
case <-s.authDone:
}
for {
- message, err := s.quicConn.ReceiveMessage()
+ message, err := s.quicConn.ReceiveMessage(s.ctx)
if err != nil {
s.closeWithError(E.Cause(err, "receive message"))
return
diff --git a/transport/wireguard/client_bind.go b/transport/wireguard/client_bind.go
index 957e697512..2b56f73a3c 100644
--- a/transport/wireguard/client_bind.go
+++ b/transport/wireguard/client_bind.go
@@ -5,6 +5,7 @@ import (
"net"
"net/netip"
"sync"
+ "time"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
@@ -114,6 +115,7 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint)
}
c.errorHandler.NewError(context.Background(), E.Cause(err, "connect to server"))
err = nil
+ time.Sleep(time.Second)
c.pauseManager.WaitActive()
return
}
diff --git a/transport/wireguard/device_system.go b/transport/wireguard/device_system.go
index f2325a9c65..986264040b 100644
--- a/transport/wireguard/device_system.go
+++ b/transport/wireguard/device_system.go
@@ -10,6 +10,7 @@ import (
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
+ "github.com/sagernet/sing/common"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
wgTun "github.com/sagernet/wireguard-go/tun"
@@ -58,9 +59,9 @@ func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes
inet6Address = inet6Addresses[0].Addr()
}
return &SystemDevice{
- dialer: dialer.NewDefault(router, option.DialerOptions{
+ dialer: common.Must1(dialer.NewDefault(router, option.DialerOptions{
BindInterface: interfaceName,
- }),
+ })),
device: tunInterface,
name: interfaceName,
mtu: int(mtu),