Compare commits

..

22 Commits

Author SHA1 Message Date
renovate[bot]
5e96b57825
[dependencies] Update github-actions 2025-09-08 03:48:52 +00:00
世界
12e8d43925
Add support for kTLS
Reference: https://gitlab.com/go-extension/tls
2025-09-08 11:48:26 +08:00
世界
f13327f4d2
documentation: Bump version 2025-09-08 09:13:10 +08:00
世界
e1f54d429a
Add proxy support for ICMP echo request 2025-09-08 09:13:10 +08:00
世界
19f9af5945
Fix resolve using resolved 2025-09-08 09:13:10 +08:00
世界
b0fc436a16
documentation: Update behavior of local DNS server on darwin 2025-09-08 09:13:10 +08:00
世界
53bb549809
Stop using DHCP on iOS and tvOS
We do not have the `com.apple.developer.networking.multicast` entitlement and are unable to obtain it for non-technical reasons.
2025-09-08 09:13:10 +08:00
世界
e7de4f5a88
Remove use of ldflags -checklinkname=0 on darwin 2025-09-08 09:13:10 +08:00
世界
8583c6e509
Fix local DNS server on darwin
We mistakenly believed that `libresolv`'s `search` function worked correctly in NetworkExtension, but it seems only `getaddrinfo` does.

This commit changes the behavior of the `local` DNS server in NetworkExtension to prefer DHCP, falling back to `getaddrinfo` if DHCP servers are unavailable.

It's worth noting that `prefer_go` does not disable DHCP since it respects Dial Fields, but `getaddrinfo` does the opposite. The new behavior only applies to NetworkExtension, not to all scenarios (primarily command-line binaries) as it did previously.

In addition, this commit also improves the DHCP DNS server to use the same robust query logic as `local`.
2025-09-08 09:13:09 +08:00
世界
a67f3d8be2
Fix legacy DNS config 2025-09-08 09:13:09 +08:00
世界
87ed5f86d8
Fix rule-set format 2025-09-08 09:13:09 +08:00
世界
e53122d255
documentation: Remove outdated icons 2025-09-08 09:13:08 +08:00
世界
5982358407
documentation: Improve local DNS server 2025-09-08 09:13:08 +08:00
世界
490daad0a7
Use libresolv in local DNS server on darwin 2025-09-08 09:13:08 +08:00
世界
b1ea333bd4
Use resolved in local DNS server if available 2025-09-08 09:13:08 +08:00
xchacha20-poly1305
e69b61747f
Fix rule set version 2025-09-08 09:13:07 +08:00
世界
16d8f108ca
documentation: Add preferred_by route rule item 2025-09-08 09:13:07 +08:00
世界
ffdfb3237c
Add preferred_by route rule item 2025-09-08 09:13:07 +08:00
世界
92b6cc4904
documentation: Add interface address rule items 2025-09-08 09:13:07 +08:00
世界
c630819ee9
Add interface address rule items 2025-09-08 09:13:07 +08:00
neletor
e9f519aafc
Add support for ech retry configs 2025-09-08 09:13:07 +08:00
Zephyruso
84279243ce
Add /dns/flush-clash meta api 2025-09-08 09:13:06 +08:00
20 changed files with 108 additions and 335 deletions

View File

@ -46,7 +46,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: ^1.25.1 go-version: ^1.25.0
- name: Check input version - name: Check input version
if: github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch'
run: |- run: |-
@ -110,7 +110,7 @@ jobs:
if: ${{ ! (matrix.legacy_go123 || matrix.legacy_go124) }} if: ${{ ! (matrix.legacy_go123 || matrix.legacy_go124) }}
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: ^1.25.1 go-version: ^1.25.0
- name: Setup Go 1.24 - name: Setup Go 1.24
if: matrix.legacy_go124 if: matrix.legacy_go124
uses: actions/setup-go@v6 uses: actions/setup-go@v6
@ -300,7 +300,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: ^1.25.1 go-version: ^1.25.0
- name: Setup Android NDK - name: Setup Android NDK
id: setup-ndk id: setup-ndk
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1
@ -380,7 +380,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: ^1.25.1 go-version: ^1.25.0
- name: Setup Android NDK - name: Setup Android NDK
id: setup-ndk id: setup-ndk
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1
@ -478,7 +478,7 @@ jobs:
if: matrix.if if: matrix.if
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: ^1.25.1 go-version: ^1.25.0
- name: Setup Xcode stable - name: Setup Xcode stable
if: matrix.if && github.ref == 'refs/heads/main-next' if: matrix.if && github.ref == 'refs/heads/main-next'
run: |- run: |-

View File

@ -30,7 +30,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: ^1.25.1 go-version: ^1.25.0
- name: Check input version - name: Check input version
if: github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch'
run: |- run: |-
@ -71,7 +71,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: ^1.25.1 go-version: ^1.25.0
- name: Setup Android NDK - name: Setup Android NDK
if: matrix.os == 'android' if: matrix.os == 'android'
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1

View File

@ -5,8 +5,6 @@ import (
"strings" "strings"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"golang.org/x/mod/semver"
) )
type Version struct { type Version struct {
@ -18,19 +16,7 @@ type Version struct {
PreReleaseVersion int PreReleaseVersion int
} }
func (v Version) LessThan(anotherVersion Version) bool { func (v Version) After(anotherVersion Version) bool {
return !v.GreaterThanOrEqual(anotherVersion)
}
func (v Version) LessThanOrEqual(anotherVersion Version) bool {
return v == anotherVersion || anotherVersion.GreaterThan(v)
}
func (v Version) GreaterThanOrEqual(anotherVersion Version) bool {
return v == anotherVersion || v.GreaterThan(anotherVersion)
}
func (v Version) GreaterThan(anotherVersion Version) bool {
if v.Major > anotherVersion.Major { if v.Major > anotherVersion.Major {
return true return true
} else if v.Major < anotherVersion.Major { } else if v.Major < anotherVersion.Major {
@ -58,27 +44,21 @@ func (v Version) GreaterThan(anotherVersion Version) bool {
} else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion { } else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion {
return false return false
} }
} } else if v.PreReleaseIdentifier == "rc" && anotherVersion.PreReleaseIdentifier == "beta" {
preReleaseIdentifier := parsePreReleaseIdentifier(v.PreReleaseIdentifier)
anotherPreReleaseIdentifier := parsePreReleaseIdentifier(anotherVersion.PreReleaseIdentifier)
if preReleaseIdentifier < anotherPreReleaseIdentifier {
return true return true
} else if preReleaseIdentifier > anotherPreReleaseIdentifier { } else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "rc" {
return false
} else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "alpha" {
return true
} else if v.PreReleaseIdentifier == "alpha" && anotherVersion.PreReleaseIdentifier == "beta" {
return false return false
} }
} }
return false return false
} }
func parsePreReleaseIdentifier(identifier string) int { func (v Version) VersionString() string {
if strings.HasPrefix(identifier, "rc") { return F.ToString(v.Major, ".", v.Minor, ".", v.Patch)
return 1
} else if strings.HasPrefix(identifier, "beta") {
return 2
} else if strings.HasPrefix(identifier, "alpha") {
return 3
}
return 0
} }
func (v Version) String() string { func (v Version) String() string {
@ -103,10 +83,6 @@ func (v Version) BadString() string {
return version return version
} }
func IsValid(versionName string) bool {
return semver.IsValid("v" + versionName)
}
func Parse(versionName string) (version Version) { func Parse(versionName string) (version Version) {
if strings.HasPrefix(versionName, "v") { if strings.HasPrefix(versionName, "v") {
versionName = versionName[1:] versionName = versionName[1:]

View File

@ -10,9 +10,9 @@ func TestCompareVersion(t *testing.T) {
t.Parallel() t.Parallel()
require.Equal(t, "1.3.0-beta.1", Parse("v1.3.0-beta1").String()) require.Equal(t, "1.3.0-beta.1", Parse("v1.3.0-beta1").String())
require.Equal(t, "1.3-beta1", Parse("v1.3.0-beta.1").BadString()) require.Equal(t, "1.3-beta1", Parse("v1.3.0-beta.1").BadString())
require.True(t, Parse("1.3.0").GreaterThan(Parse("1.3-beta1"))) require.True(t, Parse("1.3.0").After(Parse("1.3-beta1")))
require.True(t, Parse("1.3.0").GreaterThan(Parse("1.3.0-beta1"))) require.True(t, Parse("1.3.0").After(Parse("1.3.0-beta1")))
require.True(t, Parse("1.3.0-beta1").GreaterThan(Parse("1.3.0-alpha1"))) require.True(t, Parse("1.3.0-beta1").After(Parse("1.3.0-alpha1")))
require.True(t, Parse("1.3.1").GreaterThan(Parse("1.3.0"))) require.True(t, Parse("1.3.1").After(Parse("1.3.0")))
require.True(t, Parse("1.4").GreaterThan(Parse("1.3"))) require.True(t, Parse("1.4").After(Parse("1.3")))
} }

View File

@ -11,6 +11,7 @@ import (
"syscall" "syscall"
"github.com/sagernet/sing-box/common/badtls" "github.com/sagernet/sing-box/common/badtls"
// C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
@ -28,6 +29,8 @@ type Conn struct {
readWaitOptions N.ReadWaitOptions readWaitOptions N.ReadWaitOptions
kernelTx bool kernelTx bool
kernelRx bool kernelRx bool
kernelDidRead bool
kernelDidWrite bool
} }
func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) { func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) {
@ -61,7 +64,7 @@ func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, t
for rawConn.Hand.Len() > 0 { for rawConn.Hand.Len() > 0 {
err = rawConn.HandlePostHandshakeMessage() err = rawConn.HandlePostHandshakeMessage()
if err != nil { if err != nil {
return nil, E.Cause(err, "handle post-handshake messages") return nil, E.Cause(err, "ktls: failed to handle post-handshake messages")
} }
} }
} }
@ -82,15 +85,36 @@ func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, t
} }
func (c *Conn) Upstream() any { func (c *Conn) Upstream() any {
return c.Conn return c.conn
}
func (c *Conn) ReaderReplaceable() bool {
if !c.kernelRx {
return true
}
c.rawConn.In.Lock()
defer c.rawConn.In.Unlock()
return !c.kernelDidRead
}
func (c *Conn) WriterReplaceable() bool {
if !c.kernelTx {
return true
}
/*c.rawConn.Out.Lock()
defer c.rawConn.Out.Unlock()
return !c.kernelDidWrite*/
return true
} }
func (c *Conn) SyscallConnForRead() syscall.Conn { func (c *Conn) SyscallConnForRead() syscall.Conn {
if !c.kernelRx { if !c.kernelRx {
return nil return nil
} }
if !*c.rawConn.IsClient { c.rawConn.In.Lock()
c.logger.WarnContext(c.ctx, "ktls: RX splice is unavailable on the server size, since it will cause an unknown failure") defer c.rawConn.In.Unlock()
if c.kernelDidRead {
c.logger.DebugContext(c.ctx, "ktls: RX splice not possible, since did read from user space")
return nil return nil
} }
c.logger.DebugContext(c.ctx, "ktls: RX splice requested") c.logger.DebugContext(c.ctx, "ktls: RX splice requested")
@ -101,6 +125,13 @@ func (c *Conn) SyscallConnForWrite() syscall.Conn {
if !c.kernelTx { if !c.kernelTx {
return nil return nil
} }
/*c.rawConn.Out.Lock()
defer c.rawConn.Out.Unlock()
if c.kernelDidWrite {
c.logger.DebugContext(c.ctx, "ktls: TX splice not possible, since did write from user space")
return nil
}
*/
c.logger.DebugContext(c.ctx, "ktls: TX splice requested") c.logger.DebugContext(c.ctx, "ktls: TX splice requested")
return c.syscallConn return c.syscallConn
} }

View File

@ -12,11 +12,11 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/sagernet/sing-box/common/badversion"
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/shell" "github.com/sagernet/sing/common/shell"
"github.com/blang/semver/v4"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
@ -55,42 +55,46 @@ var KernelSupport = sync.OnceValues(func() (*Support, error) {
return nil, err return nil, err
} }
kernelVersion := badversion.Parse(strings.Trim(string(uname.Release[:]), "\x00")) kernelVersion, err := semver.Parse(strings.Trim(string(uname.Release[:]), "\x00"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
kernelVersion.Pre = nil
kernelVersion.Build = nil
var support Support var support Support
switch { switch {
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 6, Minor: 14}): case kernelVersion.GTE(semver.Version{Major: 6, Minor: 14}):
support.TLS_Version13_KeyUpdate = true support.TLS_Version13_KeyUpdate = true
fallthrough fallthrough
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 6, Minor: 1}): case kernelVersion.GTE(semver.Version{Major: 6, Minor: 1}):
support.TLS_ARIA_GCM = true support.TLS_ARIA_GCM = true
fallthrough fallthrough
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 6}): case kernelVersion.GTE(semver.Version{Major: 6}):
support.TLS_Version13_RX = true support.TLS_Version13_RX = true
support.TLS_RX_NOPADDING = true support.TLS_RX_NOPADDING = true
fallthrough fallthrough
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 19}): case kernelVersion.GTE(semver.Version{Major: 5, Minor: 19}):
support.TLS_TX_ZEROCOPY = true support.TLS_TX_ZEROCOPY = true
fallthrough fallthrough
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 16}): case kernelVersion.GTE(semver.Version{Major: 5, Minor: 16}):
support.TLS_SM4 = true support.TLS_SM4 = true
fallthrough fallthrough
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 11}): case kernelVersion.GTE(semver.Version{Major: 5, Minor: 11}):
support.TLS_CHACHA20_POLY1305 = true support.TLS_CHACHA20_POLY1305 = true
fallthrough fallthrough
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 2}): case kernelVersion.GTE(semver.Version{Major: 5, Minor: 2}):
support.TLS_AES_128_CCM = true support.TLS_AES_128_CCM = true
fallthrough fallthrough
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 1}): case kernelVersion.GTE(semver.Version{Major: 5, Minor: 1}):
support.TLS_AES_256_GCM = true support.TLS_AES_256_GCM = true
support.TLS_Version13 = true support.TLS_Version13 = true
fallthrough fallthrough
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 4, Minor: 17}): case kernelVersion.GTE(semver.Version{Major: 4, Minor: 17}):
support.TLS_RX = true support.TLS_RX = true
fallthrough fallthrough
case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 4, Minor: 13}): case kernelVersion.GTE(semver.Version{Major: 4, Minor: 13}):
support.TLS = true support.TLS = true
} }
@ -114,7 +118,7 @@ var KernelSupport = sync.OnceValues(func() (*Support, error) {
func Load() error { func Load() error {
support, err := KernelSupport() support, err := KernelSupport()
if err != nil { if err != nil {
return E.Cause(err, "ktls: check availability") return err
} }
if !support.TLS || !support.TLS_Version13 { if !support.TLS || !support.TLS_Version13 {
return E.New("ktls: kernel does not support TLS 1.3") return E.New("ktls: kernel does not support TLS 1.3")
@ -128,10 +132,10 @@ func (c *Conn) setupKernel(txOffload, rxOffload bool) error {
} }
support, err := KernelSupport() support, err := KernelSupport()
if err != nil { if err != nil {
return E.Cause(err, "check availability") return err
} }
if !support.TLS || !support.TLS_Version13 { if !support.TLS || !support.TLS_Version13 {
return E.New("kernel does not support TLS 1.3") return E.New("ktls: kernel does not support TLS 1.3")
} }
c.rawConn.Out.Lock() c.rawConn.Out.Lock()
defer c.rawConn.Out.Unlock() defer c.rawConn.Out.Unlock()
@ -139,13 +143,13 @@ func (c *Conn) setupKernel(txOffload, rxOffload bool) error {
return syscall.SetsockoptString(int(fd), unix.SOL_TCP, unix.TCP_ULP, "tls") return syscall.SetsockoptString(int(fd), unix.SOL_TCP, unix.TCP_ULP, "tls")
}) })
if err != nil { if err != nil {
return os.NewSyscallError("setsockopt", err) return E.Cause(err, "initialize kernel TLS")
} }
if txOffload { if txOffload {
txCrypto := kernelCipher(support, c.rawConn.Out, *c.rawConn.CipherSuite, false) txCrypto := kernelCipher(support, c.rawConn.Out, *c.rawConn.CipherSuite, false)
if txCrypto == nil { if txCrypto == nil {
return E.New("unsupported cipher suite") return E.New("kTLS: unsupported cipher suite")
} }
err = control.Raw(c.rawSyscallConn, func(fd uintptr) error { err = control.Raw(c.rawSyscallConn, func(fd uintptr) error {
return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_TX, txCrypto.String()) return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_TX, txCrypto.String())
@ -168,7 +172,7 @@ func (c *Conn) setupKernel(txOffload, rxOffload bool) error {
if rxOffload { if rxOffload {
rxCrypto := kernelCipher(support, c.rawConn.In, *c.rawConn.CipherSuite, true) rxCrypto := kernelCipher(support, c.rawConn.In, *c.rawConn.CipherSuite, true)
if rxCrypto == nil { if rxCrypto == nil {
return E.New("unsupported cipher suite") return E.New("kTLS: unsupported cipher suite")
} }
err = control.Raw(c.rawSyscallConn, func(fd uintptr) error { err = control.Raw(c.rawSyscallConn, func(fd uintptr) error {
return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_RX, rxCrypto.String()) return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_RX, rxCrypto.String())
@ -274,6 +278,7 @@ func (c *Conn) readKernelRecord() (uint8, []byte, error) {
default: default:
return 0, nil, err return 0, nil, err
} }
c.kernelDidRead = true
if n <= 0 { if n <= 0 {
return 0, nil, io.EOF return 0, nil, io.EOF
@ -319,6 +324,7 @@ func (c *Conn) writeKernelRecord(typ uint16, data []byte) (int, error) {
if ew != nil { if ew != nil {
return 0, ew return 0, ew
} }
c.kernelDidWrite = true
return n, err return n, err
} }

View File

@ -20,12 +20,7 @@ func NewDialerFromOptions(ctx context.Context, logger logger.ContextLogger, dial
if !options.Enabled { if !options.Enabled {
return dialer, nil return dialer, nil
} }
config, err := NewClientWithOptions(ClientOptions{ config, err := NewClient(ctx, logger, serverAddress, options)
Context: ctx,
Logger: logger,
ServerAddress: serverAddress,
Options: options,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -33,40 +28,15 @@ func NewDialerFromOptions(ctx context.Context, logger logger.ContextLogger, dial
} }
func NewClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) { func NewClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
return NewClientWithOptions(ClientOptions{ if !options.Enabled {
Context: ctx,
Logger: logger,
ServerAddress: serverAddress,
Options: options,
})
}
type ClientOptions struct {
Context context.Context
Logger logger.ContextLogger
ServerAddress string
Options option.OutboundTLSOptions
KTLSCompatible bool
}
func NewClientWithOptions(options ClientOptions) (Config, error) {
if !options.Options.Enabled {
return nil, nil return nil, nil
} }
if !options.KTLSCompatible { if options.Reality != nil && options.Reality.Enabled {
if options.Options.KernelTx { return NewRealityClient(ctx, logger, serverAddress, options)
options.Logger.Warn("enabling kTLS TX in current scenarios will definitely reduce performance, please checkout https://sing-box.sagernet.org/configuration/shared/tls/#kernel_tx") } else if options.UTLS != nil && options.UTLS.Enabled {
} return NewUTLSClient(ctx, logger, serverAddress, options)
} }
if options.Options.KernelRx { return NewSTDClient(ctx, logger, serverAddress, options)
options.Logger.Warn("enabling kTLS RX will definitely reduce performance, please checkout https://sing-box.sagernet.org/configuration/shared/tls/#kernel_rx")
}
if options.Options.Reality != nil && options.Options.Reality.Enabled {
return NewRealityClient(options.Context, options.Logger, options.ServerAddress, options.Options)
} else if options.Options.UTLS != nil && options.Options.UTLS.Enabled {
return NewUTLSClient(options.Context, options.Logger, options.ServerAddress, options.Options)
}
return NewSTDClient(options.Context, options.Logger, options.ServerAddress, options.Options)
} }
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) { func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {

View File

@ -5,7 +5,6 @@ import (
"net" "net"
"github.com/sagernet/sing-box/common/ktls" "github.com/sagernet/sing-box/common/ktls"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
aTLS "github.com/sagernet/sing/common/tls" aTLS "github.com/sagernet/sing/common/tls"
) )
@ -21,12 +20,7 @@ func (w *KTLSClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (
if err != nil { if err != nil {
return nil, err return nil, err
} }
kConn, err := ktls.NewConn(ctx, w.logger, tlsConn, w.kernelTx, w.kernelRx) return ktls.NewConn(ctx, w.logger, tlsConn, w.kernelTx, w.kernelRx)
if err != nil {
tlsConn.Close()
return nil, E.Cause(err, "initialize kernel TLS")
}
return kConn, nil
} }
func (w *KTLSClientConfig) Clone() Config { func (w *KTLSClientConfig) Clone() Config {
@ -49,12 +43,7 @@ func (w *KTlSServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (
if err != nil { if err != nil {
return nil, err return nil, err
} }
kConn, err := ktls.NewConn(ctx, w.logger, tlsConn, w.kernelTx, w.kernelRx) return ktls.NewConn(ctx, w.logger, tlsConn, w.kernelTx, w.kernelRx)
if err != nil {
tlsConn.Close()
return nil, E.Cause(err, "initialize kernel TLS")
}
return kConn, nil
} }
func (w *KTlSServerConfig) Clone() Config { func (w *KTlSServerConfig) Clone() Config {

View File

@ -12,37 +12,14 @@ import (
aTLS "github.com/sagernet/sing/common/tls" aTLS "github.com/sagernet/sing/common/tls"
) )
type ServerOptions struct {
Context context.Context
Logger log.ContextLogger
Options option.InboundTLSOptions
KTLSCompatible bool
}
func NewServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) { func NewServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) {
return NewServerWithOptions(ServerOptions{ if !options.Enabled {
Context: ctx,
Logger: logger,
Options: options,
})
}
func NewServerWithOptions(options ServerOptions) (ServerConfig, error) {
if !options.Options.Enabled {
return nil, nil return nil, nil
} }
if !options.KTLSCompatible { if options.Reality != nil && options.Reality.Enabled {
if options.Options.KernelTx { return NewRealityServer(ctx, logger, options)
options.Logger.Warn("enabling kTLS TX in current scenarios will definitely reduce performance, please checkout https://sing-box.sagernet.org/configuration/shared/tls/#kernel_tx")
}
} }
if options.Options.KernelRx { return NewSTDServer(ctx, logger, options)
options.Logger.Warn("enabling kTLS RX will definitely reduce performance, please checkout https://sing-box.sagernet.org/configuration/shared/tls/#kernel_rx")
}
if options.Options.Reality != nil && options.Options.Reality.Enabled {
return NewRealityServer(options.Context, options.Logger, options.Options)
}
return NewSTDServer(options.Context, options.Logger, options.Options)
} }
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) { func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {

View File

@ -4,13 +4,8 @@ icon: material/alert-decagram
#### 1.13.0-alpha.9 #### 1.13.0-alpha.9
* Add kTLS support **1**
* Fixes and improvements * Fixes and improvements
**1**:
See [TLS](/configuration/shared/tls/).
#### 1.12.4 #### 1.12.4
* Fixes and improvements * Fixes and improvements

View File

@ -1,12 +1,7 @@
--- ---
icon: material/new-box icon: material/alert-decagram
--- ---
!!! quote "Changes in sing-box 1.13.0"
:material-plus: [kernel_tx](#kernel_tx)
:material-plus: [kernel_rx](#kernel_rx)
!!! quote "Changes in sing-box 1.12.0" !!! quote "Changes in sing-box 1.12.0"
:material-plus: [fragment](#fragment) :material-plus: [fragment](#fragment)
@ -33,8 +28,6 @@ icon: material/new-box
"certificate_path": "", "certificate_path": "",
"key": [], "key": [],
"key_path": "", "key_path": "",
"kernel_tx": false,
"kernel_rx": false,
"acme": { "acme": {
"domain": [], "domain": [],
"data_directory": "", "data_directory": "",
@ -195,8 +188,7 @@ By default, the maximum version is currently TLS 1.3.
#### cipher_suites #### cipher_suites
A list of enabled TLS 1.01.2 cipher suites. The order of the list is ignored. A list of enabled TLS 1.01.2 cipher suites. The order of the list is ignored. Note that TLS 1.3 cipher suites are not configurable.
Note that TLS 1.3 cipher suites are not configurable.
If empty, a safe default list is used. The default cipher suites might change over time. If empty, a safe default list is used. The default cipher suites might change over time.
@ -228,50 +220,6 @@ The server private key line array, in PEM format.
The path to the server private key, in PEM format. The path to the server private key, in PEM format.
#### kernel_tx
!!! question "Since sing-box 1.13.0"
!!! quote ""
Only supported on Linux 5.1+, use a newer kernel if possible.
!!! quote ""
Only TLS 1.3 is supported.
!!! warning ""
uTLS is compatible, but not other custom TLS.
!!! warning ""
kTLS TX may only improve performance when `splice(2)` is available (both ends must be TCP or TLS without additional protocols after handshake); otherwise, it will definitely degrade performance.
Enable kernel TLS transmit support.
#### kernel_rx
!!! question "Since sing-box 1.13.0"
!!! quote ""
Only supported on Linux 5.1+, use a newer kernel if possible.
!!! quote ""
Only TLS 1.3 is supported.
!!! warning ""
uTLS is compatible, but not other custom TLS.
!!! failure ""
kTLS RX will definitely degrade performance even if `splice(2)` is in use, so enabling it is not recommended.
Enable kernel TLS receive support.
## Custom TLS support ## Custom TLS support
!!! info "QUIC support" !!! info "QUIC support"

View File

@ -2,11 +2,6 @@
icon: material/alert-decagram icon: material/alert-decagram
--- ---
!!! quote "sing-box 1.13.0 中的更改"
:material-plus: [kernel_tx](#kernel_tx)
:material-plus: [kernel_rx](#kernel_rx)
!!! quote "sing-box 1.12.0 中的更改" !!! quote "sing-box 1.12.0 中的更改"
:material-plus: [tls_fragment](#tls_fragment) :material-plus: [tls_fragment](#tls_fragment)
@ -33,8 +28,6 @@ icon: material/alert-decagram
"certificate_path": "", "certificate_path": "",
"key": [], "key": [],
"key_path": "", "key_path": "",
"kernel_tx": false,
"kernel_rx": false,
"acme": { "acme": {
"domain": [], "domain": [],
"data_directory": "", "data_directory": "",
@ -223,56 +216,6 @@ TLS 版本值:
服务器 PEM 私钥路径。 服务器 PEM 私钥路径。
#### kernel_tx
!!! question "自 sing-box 1.13.0 起"
!!! quote ""
仅支持 Linux 5.1+,如果可能,使用较新的内核。
!!! quote ""
仅支持 TLS 1.3。
!!! warning ""
兼容 uTLS但不兼容其他自定义 TLS。
!!! warning ""
kTLS TX 仅当 `splice(2)` 可用时(两端经过握手后必须为没有附加协议的 TCP 或 TLS才能提高性能否则肯定会降低性能。
启用内核 TLS 发送支持。
#### kernel_rx
!!! question "自 sing-box 1.13.0 起"
!!! quote ""
仅支持 Linux 5.1+,如果可能,使用较新的内核。
!!! quote ""
仅支持 TLS 1.3。
!!! warning ""
兼容 uTLS但不兼容其他自定义 TLS。
!!! failure ""
即使使用 `splice(2)`kTLS RX 也肯定会降低性能,因此不建议启用。
启用内核 TLS 接收支持。
## 自定义 TLS 支持
!!! info "QUIC 支持"
只有 ECH 在 QUIC 中被支持.
#### utls #### utls
==仅客户端== ==仅客户端==

3
go.mod
View File

@ -4,6 +4,7 @@ go 1.23.1
require ( require (
github.com/anytls/sing-anytls v0.0.8 github.com/anytls/sing-anytls v0.0.8
github.com/blang/semver/v4 v4.0.0
github.com/caddyserver/certmagic v0.23.0 github.com/caddyserver/certmagic v0.23.0
github.com/coder/websocket v1.8.13 github.com/coder/websocket v1.8.13
github.com/cretz/bine v0.2.0 github.com/cretz/bine v0.2.0
@ -27,7 +28,7 @@ require (
github.com/sagernet/gomobile v0.1.8 github.com/sagernet/gomobile v0.1.8
github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237
github.com/sagernet/quic-go v0.52.0-beta.1 github.com/sagernet/quic-go v0.52.0-beta.1
github.com/sagernet/sing v0.7.8-0.20250908063931-beb351e61b89 github.com/sagernet/sing v0.7.8-0.20250908034727-1ceb6b183d75
github.com/sagernet/sing-mux v0.3.3 github.com/sagernet/sing-mux v0.3.3
github.com/sagernet/sing-quic v0.5.2-0.20250908021228-186e280a524e github.com/sagernet/sing-quic v0.5.2-0.20250908021228-186e280a524e
github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks v0.2.8

6
go.sum
View File

@ -12,6 +12,8 @@ github.com/anytls/sing-anytls v0.0.8 h1:1u/fnH1HoeeMV5mX7/eUOjLBvPdkd1UJRmXiRi6V
github.com/anytls/sing-anytls v0.0.8/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= github.com/anytls/sing-anytls v0.0.8/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU= github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=
github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4= github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4=
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
@ -167,8 +169,8 @@ github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/l
github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs= github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs=
github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4= github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.7.8-0.20250908063931-beb351e61b89 h1:Of7kq85JItQmR4Y8m3L/DFFxaxyjK9rJvLMpmXPQ7g0= github.com/sagernet/sing v0.7.8-0.20250908034727-1ceb6b183d75 h1:jc7lqbvND3htLpTeu6xPlhcVz01z7dEmikCZ46LmvxU=
github.com/sagernet/sing v0.7.8-0.20250908063931-beb351e61b89/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.7.8-0.20250908034727-1ceb6b183d75/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw= github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw=
github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
github.com/sagernet/sing-quic v0.5.2-0.20250908021228-186e280a524e h1:l+DICzp1OecGSDoiHE0Ebviaz3zqSp7XM27UcRbGkBM= github.com/sagernet/sing-quic v0.5.2-0.20250908021228-186e280a524e h1:l+DICzp1OecGSDoiHE0Ebviaz3zqSp7XM27UcRbGkBM=

View File

@ -43,12 +43,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
authenticator: auth.NewAuthenticator(options.Users), authenticator: auth.NewAuthenticator(options.Users),
} }
if options.TLS != nil { if options.TLS != nil {
tlsConfig, err := tls.NewServerWithOptions(tls.ServerOptions{ tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
Context: ctx,
Logger: logger,
Options: common.PtrValueOrDefault(options.TLS),
KTLSCompatible: true,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -8,12 +8,10 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/listener"
"github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/common/uot" "github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth" "github.com/sagernet/sing/common/auth"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
@ -35,7 +33,6 @@ type Inbound struct {
logger log.ContextLogger logger log.ContextLogger
listener *listener.Listener listener *listener.Listener
authenticator *auth.Authenticator authenticator *auth.Authenticator
tlsConfig tls.ServerConfig
} }
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (adapter.Inbound, error) { func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (adapter.Inbound, error) {
@ -45,18 +42,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
logger: logger, logger: logger,
authenticator: auth.NewAuthenticator(options.Users), authenticator: auth.NewAuthenticator(options.Users),
} }
if options.TLS != nil {
tlsConfig, err := tls.NewServerWithOptions(tls.ServerOptions{
Context: ctx,
Logger: logger,
Options: common.PtrValueOrDefault(options.TLS),
KTLSCompatible: true,
})
if err != nil {
return nil, err
}
inbound.tlsConfig = tlsConfig
}
inbound.listener = listener.New(listener.Options{ inbound.listener = listener.New(listener.Options{
Context: ctx, Context: ctx,
Logger: logger, Logger: logger,
@ -73,21 +58,13 @@ func (h *Inbound) Start(stage adapter.StartStage) error {
if stage != adapter.StartStateStart { if stage != adapter.StartStateStart {
return nil return nil
} }
if h.tlsConfig != nil {
err := h.tlsConfig.Start()
if err != nil {
return E.Cause(err, "create TLS config")
}
}
return h.listener.Start() return h.listener.Start()
} }
func (h *Inbound) Close() error { func (h *Inbound) Close() error {
return common.Close( return h.listener.Close()
h.listener,
h.tlsConfig,
)
} }
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
err := h.newConnection(ctx, conn, metadata, onClose) err := h.newConnection(ctx, conn, metadata, onClose)
N.CloseOnHandshakeFailure(conn, onClose, err) N.CloseOnHandshakeFailure(conn, onClose, err)
@ -101,13 +78,6 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a
} }
func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {
if h.tlsConfig != nil {
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
if err != nil {
return E.Cause(err, "TLS handshake")
}
conn = tlsConn
}
reader := std_bufio.NewReader(conn) reader := std_bufio.NewReader(conn)
headerBytes, err := reader.Peek(1) headerBytes, err := reader.Peek(1)
if err != nil { if err != nil {

View File

@ -50,13 +50,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
users: options.Users, users: options.Users,
} }
if options.TLS != nil { if options.TLS != nil {
tlsConfig, err := tls.NewServerWithOptions(tls.ServerOptions{ tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
Context: ctx,
Logger: logger,
Options: common.PtrValueOrDefault(options.TLS),
KTLSCompatible: common.PtrValueOrDefault(options.Transport).Type == "" &&
!common.PtrValueOrDefault(options.Multiplex).Enabled,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -51,14 +51,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
key: trojan.Key(options.Password), key: trojan.Key(options.Password),
} }
if options.TLS != nil { if options.TLS != nil {
outbound.tlsConfig, err = tls.NewClientWithOptions(tls.ClientOptions{ outbound.tlsConfig, err = tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS))
Context: ctx,
Logger: logger,
ServerAddress: options.Server,
Options: common.PtrValueOrDefault(options.TLS),
KTLSCompatible: common.PtrValueOrDefault(options.Transport).Type == "" &&
!common.PtrValueOrDefault(options.Multiplex).Enabled,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -68,16 +68,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
})) }))
inbound.service = service inbound.service = service
if options.TLS != nil { if options.TLS != nil {
inbound.tlsConfig, err = tls.NewServerWithOptions(tls.ServerOptions{ inbound.tlsConfig, err = tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
Context: ctx,
Logger: logger,
Options: common.PtrValueOrDefault(options.TLS),
KTLSCompatible: common.PtrValueOrDefault(options.Transport).Type == "" &&
!common.PtrValueOrDefault(options.Multiplex).Enabled &&
common.All(options.Users, func(it option.VLESSUser) bool {
return it.Flow == ""
}),
})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -53,15 +53,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
serverAddr: options.ServerOptions.Build(), serverAddr: options.ServerOptions.Build(),
} }
if options.TLS != nil { if options.TLS != nil {
outbound.tlsConfig, err = tls.NewClientWithOptions(tls.ClientOptions{ outbound.tlsConfig, err = tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS))
Context: ctx,
Logger: logger,
ServerAddress: options.Server,
Options: common.PtrValueOrDefault(options.TLS),
KTLSCompatible: common.PtrValueOrDefault(options.Transport).Type == "" &&
!common.PtrValueOrDefault(options.Multiplex).Enabled &&
options.Flow == "",
})
if err != nil { if err != nil {
return nil, err return nil, err
} }