소스 검색

Improve TLS fragments

世界 4 달 전
부모
커밋
24c940c51c

+ 6 - 6
common/tls/ech.go

@@ -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
-		return &STDClientConfig{tlsConfig}, nil
+		stdConfig.config.EncryptedClientHelloConfigList = block.Bytes
+		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) {

+ 15 - 4
common/tls/std_client.go

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

+ 13 - 5
common/tls/utls_client.go

@@ -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
-	id     utls.ClientHelloID
+	ctx                   context.Context
+	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(),
-		id:     e.id,
+		e.ctx, e.config.Clone(), e.id, e.fragment, e.fragmentFallbackDelay, e.recordFragment,
 	}
 	}
 }
 }
 
 
@@ -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 (

+ 31 - 17
common/tlsfragment/conn.go

@@ -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) {
-					err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
-					if err != nil {
-						return
+					if c.splitPacket {
+						payload = buffer.Bytes()
 					}
 					}
-				} else {
-					_, err = c.Conn.Write(payload)
-					if err != nil {
-						return
+				}
+				if c.splitPacket {
+					if c.tcpConn != nil && i != len(splitIndexes) {
+						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 {
-					err = c.tcpConn.SetNoDelay(false)
-					if err != nil {
-						return
-					}
+			}
+			if c.tcpConn != nil {
+				err = c.tcpConn.SetNoDelay(false)
+				if err != nil {
+					return
 				}
 				}
 			}
 			}
 			return len(b), nil
 			return len(b), nil

+ 12 - 2
common/tlsfragment/conn_test.go

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

+ 1 - 8
docs/configuration/route/rule_action.md

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

+ 0 - 6
docs/configuration/route/rule_action.zh.md

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

+ 44 - 0
docs/configuration/shared/tls.md

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

+ 41 - 0
docs/configuration/shared/tls.zh.md

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

+ 16 - 13
option/tls.go

@@ -37,19 +37,22 @@ func (o *InboundTLSOptionsContainer) ReplaceInboundTLSOptions(options *InboundTL
 }
 }
 
 
 type OutboundTLSOptions struct {
 type OutboundTLSOptions struct {
-	Enabled         bool                       `json:"enabled,omitempty"`
-	DisableSNI      bool                       `json:"disable_sni,omitempty"`
-	ServerName      string                     `json:"server_name,omitempty"`
-	Insecure        bool                       `json:"insecure,omitempty"`
-	ALPN            badoption.Listable[string] `json:"alpn,omitempty"`
-	MinVersion      string                     `json:"min_version,omitempty"`
-	MaxVersion      string                     `json:"max_version,omitempty"`
-	CipherSuites    badoption.Listable[string] `json:"cipher_suites,omitempty"`
-	Certificate     badoption.Listable[string] `json:"certificate,omitempty"`
-	CertificatePath string                     `json:"certificate_path,omitempty"`
-	ECH             *OutboundECHOptions        `json:"ech,omitempty"`
-	UTLS            *OutboundUTLSOptions       `json:"utls,omitempty"`
-	Reality         *OutboundRealityOptions    `json:"reality,omitempty"`
+	Enabled               bool                       `json:"enabled,omitempty"`
+	DisableSNI            bool                       `json:"disable_sni,omitempty"`
+	ServerName            string                     `json:"server_name,omitempty"`
+	Insecure              bool                       `json:"insecure,omitempty"`
+	ALPN                  badoption.Listable[string] `json:"alpn,omitempty"`
+	MinVersion            string                     `json:"min_version,omitempty"`
+	MaxVersion            string                     `json:"max_version,omitempty"`
+	CipherSuites          badoption.Listable[string] `json:"cipher_suites,omitempty"`
+	Certificate           badoption.Listable[string] `json:"certificate,omitempty"`
+	CertificatePath       string                     `json:"certificate_path,omitempty"`
+	Fragment              bool                       `json:"fragment,omitempty"`
+	FragmentFallbackDelay badoption.Duration         `json:"fragment_fallback_delay,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 {

+ 2 - 8
route/conn.go

@@ -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 {
-		fallbackDelay := 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)
+	if metadata.TLSFragment || metadata.TLSRecordFragment {
+		remoteConn = tf.NewConn(remoteConn, ctx, metadata.TLSFragment, metadata.TLSRecordFragment, metadata.TLSFragmentFallbackDelay)
 	}
 	}
 	m.access.Lock()
 	m.access.Lock()
 	element := m.connections.PushBack(conn)
 	element := m.connections.PushBack(conn)