mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
Improve TLS fragments
This commit is contained in:
parent
e7ae3ddf31
commit
0a5f09f147
@ -25,7 +25,7 @@ import (
|
|||||||
"golang.org/x/crypto/cryptobyte"
|
"golang.org/x/crypto/cryptobyte"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions, tlsConfig *tls.Config) (Config, error) {
|
func parseECHClientConfig(ctx context.Context, stdConfig *STDClientConfig, options option.OutboundTLSOptions) (Config, error) {
|
||||||
var echConfig []byte
|
var echConfig []byte
|
||||||
if len(options.ECH.Config) > 0 {
|
if len(options.ECH.Config) > 0 {
|
||||||
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
|
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
|
||||||
@ -45,11 +45,11 @@ func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions
|
|||||||
if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 {
|
if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 {
|
||||||
return nil, E.New("invalid ECH configs pem")
|
return nil, E.New("invalid ECH configs pem")
|
||||||
}
|
}
|
||||||
tlsConfig.EncryptedClientHelloConfigList = block.Bytes
|
stdConfig.config.EncryptedClientHelloConfigList = block.Bytes
|
||||||
return &STDClientConfig{tlsConfig}, nil
|
return stdConfig, nil
|
||||||
} else {
|
} else {
|
||||||
return &STDECHClientConfig{
|
return &STDECHClientConfig{
|
||||||
STDClientConfig: STDClientConfig{tlsConfig},
|
STDClientConfig: stdConfig,
|
||||||
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -103,7 +103,7 @@ func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type STDECHClientConfig struct {
|
type STDECHClientConfig struct {
|
||||||
STDClientConfig
|
*STDClientConfig
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
dnsRouter adapter.DNSRouter
|
dnsRouter adapter.DNSRouter
|
||||||
lastTTL time.Duration
|
lastTTL time.Duration
|
||||||
@ -171,7 +171,7 @@ func (s *STDECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Con
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *STDECHClientConfig) Clone() Config {
|
func (s *STDECHClientConfig) Clone() Config {
|
||||||
return &STDECHClientConfig{STDClientConfig: STDClientConfig{s.config.Clone()}, dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
|
return &STDECHClientConfig{STDClientConfig: s.STDClientConfig.Clone().(*STDClientConfig), dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
||||||
|
@ -7,15 +7,21 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/tlsfragment"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type STDClientConfig struct {
|
type STDClientConfig struct {
|
||||||
config *tls.Config
|
ctx context.Context
|
||||||
|
config *tls.Config
|
||||||
|
fragment bool
|
||||||
|
fragmentFallbackDelay time.Duration
|
||||||
|
recordFragment bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *STDClientConfig) ServerName() string {
|
func (s *STDClientConfig) ServerName() string {
|
||||||
@ -39,11 +45,14 @@ func (s *STDClientConfig) Config() (*STDConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
|
if s.recordFragment {
|
||||||
|
conn = tf.NewConn(conn, s.ctx, s.fragment, s.recordFragment, s.fragmentFallbackDelay)
|
||||||
|
}
|
||||||
return tls.Client(conn, s.config), nil
|
return tls.Client(conn, s.config), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *STDClientConfig) Clone() Config {
|
func (s *STDClientConfig) Clone() Config {
|
||||||
return &STDClientConfig{s.config.Clone()}
|
return &STDClientConfig{s.ctx, s.config.Clone(), s.fragment, s.fragmentFallbackDelay, s.recordFragment}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
@ -127,8 +136,10 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
}
|
}
|
||||||
tlsConfig.RootCAs = certPool
|
tlsConfig.RootCAs = certPool
|
||||||
}
|
}
|
||||||
|
stdConfig := &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
return parseECHClientConfig(ctx, options, &tlsConfig)
|
return parseECHClientConfig(ctx, stdConfig, options)
|
||||||
|
} else {
|
||||||
|
return stdConfig, nil
|
||||||
}
|
}
|
||||||
return &STDClientConfig{&tlsConfig}, nil
|
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,10 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/tlsfragment"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
@ -22,8 +24,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UTLSClientConfig struct {
|
type UTLSClientConfig struct {
|
||||||
config *utls.Config
|
ctx context.Context
|
||||||
id utls.ClientHelloID
|
config *utls.Config
|
||||||
|
id utls.ClientHelloID
|
||||||
|
fragment bool
|
||||||
|
fragmentFallbackDelay time.Duration
|
||||||
|
recordFragment bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *UTLSClientConfig) ServerName() string {
|
func (e *UTLSClientConfig) ServerName() string {
|
||||||
@ -50,6 +56,9 @@ func (e *UTLSClientConfig) Config() (*STDConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
|
if e.recordFragment {
|
||||||
|
conn = tf.NewConn(conn, e.ctx, e.fragment, e.recordFragment, e.fragmentFallbackDelay)
|
||||||
|
}
|
||||||
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, e.config.NextProtos}, nil
|
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, e.config.NextProtos}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,8 +68,7 @@ func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []by
|
|||||||
|
|
||||||
func (e *UTLSClientConfig) Clone() Config {
|
func (e *UTLSClientConfig) Clone() Config {
|
||||||
return &UTLSClientConfig{
|
return &UTLSClientConfig{
|
||||||
config: e.config.Clone(),
|
e.ctx, e.config.Clone(), e.id, e.fragment, e.fragmentFallbackDelay, e.recordFragment,
|
||||||
id: e.id,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +200,7 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &UTLSClientConfig{&tlsConfig, id}, nil
|
return &UTLSClientConfig{ctx, &tlsConfig, id, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
@ -19,16 +20,21 @@ type Conn struct {
|
|||||||
tcpConn *net.TCPConn
|
tcpConn *net.TCPConn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
firstPacketWritten bool
|
firstPacketWritten bool
|
||||||
|
splitPacket bool
|
||||||
splitRecord bool
|
splitRecord bool
|
||||||
fallbackDelay time.Duration
|
fallbackDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConn(conn net.Conn, ctx context.Context, splitRecord bool, fallbackDelay time.Duration) *Conn {
|
func NewConn(conn net.Conn, ctx context.Context, splitPacket bool, splitRecord bool, fallbackDelay time.Duration) *Conn {
|
||||||
|
if fallbackDelay == 0 {
|
||||||
|
fallbackDelay = C.TLSFragmentFallbackDelay
|
||||||
|
}
|
||||||
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
||||||
return &Conn{
|
return &Conn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
tcpConn: tcpConn,
|
tcpConn: tcpConn,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
splitPacket: splitPacket,
|
||||||
splitRecord: splitRecord,
|
splitRecord: splitRecord,
|
||||||
fallbackDelay: fallbackDelay,
|
fallbackDelay: fallbackDelay,
|
||||||
}
|
}
|
||||||
@ -41,7 +47,7 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
}()
|
}()
|
||||||
serverName := indexTLSServerName(b)
|
serverName := indexTLSServerName(b)
|
||||||
if serverName != nil {
|
if serverName != nil {
|
||||||
if !c.splitRecord {
|
if c.splitPacket {
|
||||||
if c.tcpConn != nil {
|
if c.tcpConn != nil {
|
||||||
err = c.tcpConn.SetNoDelay(true)
|
err = c.tcpConn.SetNoDelay(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -81,33 +87,41 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
||||||
}
|
}
|
||||||
if c.splitRecord {
|
if c.splitRecord {
|
||||||
|
if c.splitPacket {
|
||||||
|
buffer.Reset()
|
||||||
|
}
|
||||||
payloadLen := uint16(len(payload))
|
payloadLen := uint16(len(payload))
|
||||||
buffer.Write(b[:3])
|
buffer.Write(b[:3])
|
||||||
binary.Write(&buffer, binary.BigEndian, payloadLen)
|
binary.Write(&buffer, binary.BigEndian, payloadLen)
|
||||||
buffer.Write(payload)
|
buffer.Write(payload)
|
||||||
} else if c.tcpConn != nil && i != len(splitIndexes) {
|
if c.splitPacket {
|
||||||
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
payload = buffer.Bytes()
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
_, err = c.Conn.Write(payload)
|
if c.splitPacket {
|
||||||
if err != nil {
|
if c.tcpConn != nil && i != len(splitIndexes) {
|
||||||
return
|
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err = c.Conn.Write(payload)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.splitRecord {
|
if c.splitRecord && !c.splitPacket {
|
||||||
_, err = c.Conn.Write(buffer.Bytes())
|
_, err = c.Conn.Write(buffer.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
if c.tcpConn != nil {
|
if c.tcpConn != nil {
|
||||||
err = c.tcpConn.SetNoDelay(false)
|
err = c.tcpConn.SetNoDelay(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return len(b), nil
|
return len(b), nil
|
||||||
|
@ -15,7 +15,7 @@ func TestTLSFragment(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), false, 0), &tls.Config{
|
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, false, 0), &tls.Config{
|
||||||
ServerName: "www.cloudflare.com",
|
ServerName: "www.cloudflare.com",
|
||||||
})
|
})
|
||||||
require.NoError(t, tlsConn.Handshake())
|
require.NoError(t, tlsConn.Handshake())
|
||||||
@ -25,7 +25,17 @@ func TestTLSRecordFragment(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, 0), &tls.Config{
|
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), false, true, 0), &tls.Config{
|
||||||
|
ServerName: "www.cloudflare.com",
|
||||||
|
})
|
||||||
|
require.NoError(t, tlsConn.Handshake())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTLS2Fragment(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
||||||
|
require.NoError(t, err)
|
||||||
|
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, true, 0), &tls.Config{
|
||||||
ServerName: "www.cloudflare.com",
|
ServerName: "www.cloudflare.com",
|
||||||
})
|
})
|
||||||
require.NoError(t, tlsConn.Handshake())
|
require.NoError(t, tlsConn.Handshake())
|
||||||
|
@ -172,14 +172,12 @@ and should not be used to circumvent real censorship.
|
|||||||
Due to poor performance, try `tls_record_fragment` first, and only apply to server names known to be blocked.
|
Due to poor performance, try `tls_record_fragment` first, and only apply to server names known to be blocked.
|
||||||
|
|
||||||
On Linux, Apple platforms, (administrator privileges required) Windows,
|
On Linux, Apple platforms, (administrator privileges required) Windows,
|
||||||
the wait time can be automatically detected, otherwise it will fall back to
|
the wait time can be automatically detected. Otherwise, it will fall back to
|
||||||
waiting for a fixed time specified by `tls_fragment_fallback_delay`.
|
waiting for a fixed time specified by `tls_fragment_fallback_delay`.
|
||||||
|
|
||||||
In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time,
|
In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time,
|
||||||
because the target is considered to be local or behind a transparent proxy.
|
because the target is considered to be local or behind a transparent proxy.
|
||||||
|
|
||||||
Conflict with `tls_record_fragment`.
|
|
||||||
|
|
||||||
#### tls_fragment_fallback_delay
|
#### tls_fragment_fallback_delay
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
!!! question "Since sing-box 1.12.0"
|
||||||
@ -194,11 +192,6 @@ The fallback value used when TLS segmentation cannot automatically determine the
|
|||||||
|
|
||||||
Fragment TLS handshake into multiple TLS records to bypass firewalls.
|
Fragment TLS handshake into multiple TLS records to bypass firewalls.
|
||||||
|
|
||||||
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**,
|
|
||||||
and should not be used to circumvent real censorship.
|
|
||||||
|
|
||||||
Conflict with `tls_fragment`.
|
|
||||||
|
|
||||||
### sniff
|
### sniff
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
@ -170,8 +170,6 @@ UDP 连接超时时间。
|
|||||||
|
|
||||||
此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
|
此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
|
||||||
|
|
||||||
与 `tls_record_fragment` 冲突。
|
|
||||||
|
|
||||||
#### tls_fragment_fallback_delay
|
#### tls_fragment_fallback_delay
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
@ -186,10 +184,6 @@ UDP 连接超时时间。
|
|||||||
|
|
||||||
通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。
|
通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。
|
||||||
|
|
||||||
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
|
|
||||||
|
|
||||||
与 `tls_fragment` 冲突。
|
|
||||||
|
|
||||||
### sniff
|
### sniff
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
@ -4,6 +4,9 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
|
:material-plus: [fragment](#fragment)
|
||||||
|
:material-plus: [fragment_fallback_delay](#fragment_fallback_delay)
|
||||||
|
:material-plus: [record_fragment](#record_fragment)
|
||||||
:material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled)
|
:material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled)
|
||||||
:material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled)
|
:material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled)
|
||||||
|
|
||||||
@ -82,6 +85,9 @@ icon: material/alert-decagram
|
|||||||
"cipher_suites": [],
|
"cipher_suites": [],
|
||||||
"certificate": "",
|
"certificate": "",
|
||||||
"certificate_path": "",
|
"certificate_path": "",
|
||||||
|
"fragment": false,
|
||||||
|
"fragment_fallback_delay": "",
|
||||||
|
"record_fragment": false,
|
||||||
"ech": {
|
"ech": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"config": [],
|
"config": [],
|
||||||
@ -313,6 +319,44 @@ The path to ECH configuration, in PEM format.
|
|||||||
|
|
||||||
If empty, load from DNS will be attempted.
|
If empty, load from DNS will be attempted.
|
||||||
|
|
||||||
|
#### fragment
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.12.0"
|
||||||
|
|
||||||
|
==Client only==
|
||||||
|
|
||||||
|
Fragment TLS handshakes to bypass firewalls.
|
||||||
|
|
||||||
|
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**,
|
||||||
|
and should not be used to circumvent real censorship.
|
||||||
|
|
||||||
|
Due to poor performance, try `record_fragment` first, and only apply to server names known to be blocked.
|
||||||
|
|
||||||
|
On Linux, Apple platforms, (administrator privileges required) Windows,
|
||||||
|
the wait time can be automatically detected. Otherwise, it will fall back to
|
||||||
|
waiting for a fixed time specified by `fragment_fallback_delay`.
|
||||||
|
|
||||||
|
In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time,
|
||||||
|
because the target is considered to be local or behind a transparent proxy.
|
||||||
|
|
||||||
|
#### fragment_fallback_delay
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.12.0"
|
||||||
|
|
||||||
|
==Client only==
|
||||||
|
|
||||||
|
The fallback value used when TLS segmentation cannot automatically determine the wait time.
|
||||||
|
|
||||||
|
`500ms` is used by default.
|
||||||
|
|
||||||
|
#### record_fragment
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.12.0"
|
||||||
|
|
||||||
|
==Client only==
|
||||||
|
|
||||||
|
Fragment TLS handshake into multiple TLS records to bypass firewalls.
|
||||||
|
|
||||||
### ACME Fields
|
### ACME Fields
|
||||||
|
|
||||||
#### domain
|
#### domain
|
||||||
|
@ -4,6 +4,9 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: [tls_fragment](#tls_fragment)
|
||||||
|
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
||||||
|
:material-plus: [tls_record_fragment](#tls_record_fragment)
|
||||||
:material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled)
|
:material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled)
|
||||||
:material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled)
|
:material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled)
|
||||||
|
|
||||||
@ -82,6 +85,9 @@ icon: material/alert-decagram
|
|||||||
"cipher_suites": [],
|
"cipher_suites": [],
|
||||||
"certificate": [],
|
"certificate": [],
|
||||||
"certificate_path": "",
|
"certificate_path": "",
|
||||||
|
"fragment": false,
|
||||||
|
"fragment_fallback_delay": "",
|
||||||
|
"record_fragment": false,
|
||||||
"ech": {
|
"ech": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"pq_signature_schemes_enabled": false,
|
"pq_signature_schemes_enabled": false,
|
||||||
@ -305,6 +311,41 @@ ECH PEM 配置路径
|
|||||||
如果为 true,则始终使用最大可能的 TLS 记录大小。
|
如果为 true,则始终使用最大可能的 TLS 记录大小。
|
||||||
如果为 false,则可能会调整 TLS 记录的大小以尝试改善延迟。
|
如果为 false,则可能会调整 TLS 记录的大小以尝试改善延迟。
|
||||||
|
|
||||||
|
#### tls_fragment
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
|
|
||||||
|
==仅客户端==
|
||||||
|
|
||||||
|
通过分段 TLS 握手数据包来绕过防火墙检测。
|
||||||
|
|
||||||
|
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
|
||||||
|
|
||||||
|
由于性能不佳,请首先尝试 `tls_record_fragment`,且仅应用于已知被阻止的服务器名称。
|
||||||
|
|
||||||
|
在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。
|
||||||
|
若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。
|
||||||
|
|
||||||
|
此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
|
||||||
|
|
||||||
|
#### tls_fragment_fallback_delay
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
|
|
||||||
|
==仅客户端==
|
||||||
|
|
||||||
|
当 TLS 分片功能无法自动判定等待时间时使用的回退值。
|
||||||
|
|
||||||
|
默认使用 `500ms`。
|
||||||
|
|
||||||
|
#### tls_record_fragment
|
||||||
|
|
||||||
|
==仅客户端==
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
|
|
||||||
|
通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。
|
||||||
|
|
||||||
### ACME 字段
|
### ACME 字段
|
||||||
|
|
||||||
#### domain
|
#### domain
|
||||||
|
@ -37,19 +37,22 @@ func (o *InboundTLSOptionsContainer) ReplaceInboundTLSOptions(options *InboundTL
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OutboundTLSOptions struct {
|
type OutboundTLSOptions struct {
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
DisableSNI bool `json:"disable_sni,omitempty"`
|
DisableSNI bool `json:"disable_sni,omitempty"`
|
||||||
ServerName string `json:"server_name,omitempty"`
|
ServerName string `json:"server_name,omitempty"`
|
||||||
Insecure bool `json:"insecure,omitempty"`
|
Insecure bool `json:"insecure,omitempty"`
|
||||||
ALPN badoption.Listable[string] `json:"alpn,omitempty"`
|
ALPN badoption.Listable[string] `json:"alpn,omitempty"`
|
||||||
MinVersion string `json:"min_version,omitempty"`
|
MinVersion string `json:"min_version,omitempty"`
|
||||||
MaxVersion string `json:"max_version,omitempty"`
|
MaxVersion string `json:"max_version,omitempty"`
|
||||||
CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
|
CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
|
||||||
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
|
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
|
||||||
CertificatePath string `json:"certificate_path,omitempty"`
|
CertificatePath string `json:"certificate_path,omitempty"`
|
||||||
ECH *OutboundECHOptions `json:"ech,omitempty"`
|
Fragment bool `json:"fragment,omitempty"`
|
||||||
UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
|
FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"`
|
||||||
Reality *OutboundRealityOptions `json:"reality,omitempty"`
|
RecordFragment bool `json:"record_fragment,omitempty"`
|
||||||
|
ECH *OutboundECHOptions `json:"ech,omitempty"`
|
||||||
|
UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
|
||||||
|
Reality *OutboundRealityOptions `json:"reality,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundTLSOptionsContainer struct {
|
type OutboundTLSOptionsContainer struct {
|
||||||
|
@ -90,14 +90,8 @@ func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, co
|
|||||||
m.logger.ErrorContext(ctx, err)
|
m.logger.ErrorContext(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if metadata.TLSFragment {
|
if metadata.TLSFragment || metadata.TLSRecordFragment {
|
||||||
fallbackDelay := metadata.TLSFragmentFallbackDelay
|
remoteConn = tf.NewConn(remoteConn, ctx, metadata.TLSFragment, metadata.TLSRecordFragment, metadata.TLSFragmentFallbackDelay)
|
||||||
if fallbackDelay == 0 {
|
|
||||||
fallbackDelay = C.TLSFragmentFallbackDelay
|
|
||||||
}
|
|
||||||
remoteConn = tf.NewConn(remoteConn, ctx, false, fallbackDelay)
|
|
||||||
} else if metadata.TLSRecordFragment {
|
|
||||||
remoteConn = tf.NewConn(remoteConn, ctx, true, 0)
|
|
||||||
}
|
}
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
element := m.connections.PushBack(conn)
|
element := m.connections.PushBack(conn)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user