Add TLS spoof support

This commit is contained in:
世界
2026-04-15 17:59:18 +08:00
parent 6ef38042f3
commit 08ce083b9b
45 changed files with 4227 additions and 6 deletions
+3
View File
@@ -155,6 +155,9 @@ func ValidateAppleTLSOptions(ctx context.Context, options option.OutboundTLSOpti
if options.KernelTx || options.KernelRx {
return AppleTLSValidated{}, E.New("ktls is unsupported in ", engineName)
}
if options.Spoof != "" || options.SpoofMethod != "" {
return AppleTLSValidated{}, E.New("spoof is unsupported in ", engineName)
}
if len(options.CertificatePublicKeySHA256) > 0 && (len(options.Certificate) > 0 || options.CertificatePath != "") {
return AppleTLSValidated{}, E.New("certificate_public_key_sha256 is conflict with certificate or certificate_path")
}
+32
View File
@@ -8,6 +8,7 @@ import (
"os"
"github.com/sagernet/sing-box/common/badtls"
"github.com/sagernet/sing-box/common/tlsspoof"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
@@ -19,6 +20,37 @@ import (
var errMissingServerName = E.New("missing server_name or insecure=true")
func parseTLSSpoofOptions(serverName string, options option.OutboundTLSOptions) (string, tlsspoof.Method, error) {
if options.Spoof == "" {
if options.SpoofMethod != "" {
return "", 0, E.New("`spoof_method` requires `spoof`")
}
return "", 0, nil
}
if !tlsspoof.PlatformSupported {
return "", 0, E.New("`spoof` is not supported on this platform")
}
if options.DisableSNI || serverName == "" {
return "", 0, E.New("`spoof` requires TLS ClientHello with SNI")
}
method, err := tlsspoof.ParseMethod(options.SpoofMethod)
if err != nil {
return "", 0, err
}
return options.Spoof, method, nil
}
func applyTLSSpoof(conn net.Conn, spoof string, method tlsspoof.Method) (net.Conn, error) {
if spoof == "" {
return conn, nil
}
spoofer, err := tlsspoof.NewSpoofer(conn, method)
if err != nil {
return nil, err
}
return tlsspoof.NewConn(conn, spoofer, spoof), nil
}
func NewDialerFromOptions(ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
if !options.Enabled {
return dialer, nil
+3
View File
@@ -59,6 +59,9 @@ func newRealityClient(ctx context.Context, logger logger.ContextLogger, serverAd
if options.UTLS == nil || !options.UTLS.Enabled {
return nil, E.New("uTLS is required by reality client")
}
if options.Spoof != "" || options.SpoofMethod != "" {
return nil, E.New("spoof is unsupported in reality")
}
uClient, err := newUTLSClient(ctx, logger, serverAddress, options, allowEmptyServerName)
if err != nil {
+15
View File
@@ -14,6 +14,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tlsfragment"
"github.com/sagernet/sing-box/common/tlsspoof"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
@@ -31,6 +32,8 @@ type STDClientConfig struct {
fragment bool
fragmentFallbackDelay time.Duration
recordFragment bool
spoof string
spoofMethod tlsspoof.Method
}
func (c *STDClientConfig) ServerName() string {
@@ -75,6 +78,10 @@ func (c *STDClientConfig) Client(conn net.Conn) (Conn, error) {
if c.recordFragment {
conn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay)
}
conn, err := applyTLSSpoof(conn, c.spoof, c.spoofMethod)
if err != nil {
return nil, err
}
return tls.Client(conn, c.config), nil
}
@@ -89,6 +96,8 @@ func (c *STDClientConfig) Clone() Config {
fragment: c.fragment,
fragmentFallbackDelay: c.fragmentFallbackDelay,
recordFragment: c.recordFragment,
spoof: c.spoof,
spoofMethod: c.spoofMethod,
}
cloned.SetServerName(cloned.serverName)
return cloned
@@ -218,6 +227,10 @@ func newSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
} else {
handshakeTimeout = C.TCPTimeout
}
spoof, spoofMethod, err := parseTLSSpoofOptions(serverName, options)
if err != nil {
return nil, err
}
var config Config = &STDClientConfig{
ctx: ctx,
config: &tlsConfig,
@@ -228,6 +241,8 @@ func newSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
fragment: options.Fragment,
fragmentFallbackDelay: time.Duration(options.FragmentFallbackDelay),
recordFragment: options.RecordFragment,
spoof: spoof,
spoofMethod: spoofMethod,
}
config.SetServerName(serverName)
if options.ECH != nil && options.ECH.Enabled {
+15
View File
@@ -14,6 +14,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tlsfragment"
"github.com/sagernet/sing-box/common/tlsspoof"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
@@ -36,6 +37,8 @@ type UTLSClientConfig struct {
fragment bool
fragmentFallbackDelay time.Duration
recordFragment bool
spoof string
spoofMethod tlsspoof.Method
}
func (c *UTLSClientConfig) ServerName() string {
@@ -83,6 +86,10 @@ func (c *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
if c.recordFragment {
conn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay)
}
conn, err := applyTLSSpoof(conn, c.spoof, c.spoofMethod)
if err != nil {
return nil, err
}
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, c.config.Clone(), c.id)}, c.config.NextProtos}, nil
}
@@ -102,6 +109,8 @@ func (c *UTLSClientConfig) Clone() Config {
fragment: c.fragment,
fragmentFallbackDelay: c.fragmentFallbackDelay,
recordFragment: c.recordFragment,
spoof: c.spoof,
spoofMethod: c.spoofMethod,
}
cloned.SetServerName(cloned.serverName)
return cloned
@@ -290,6 +299,10 @@ func newUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddre
} else {
handshakeTimeout = C.TCPTimeout
}
spoof, spoofMethod, err := parseTLSSpoofOptions(serverName, options)
if err != nil {
return nil, err
}
id, err := uTLSClientHelloID(options.UTLS.Fingerprint)
if err != nil {
return nil, err
@@ -305,6 +318,8 @@ func newUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddre
fragment: options.Fragment,
fragmentFallbackDelay: time.Duration(options.FragmentFallbackDelay),
recordFragment: options.RecordFragment,
spoof: spoof,
spoofMethod: spoofMethod,
}
config.SetServerName(serverName)
if options.ECH != nil && options.ECH.Enabled {