mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-05-08 14:13:22 +00:00
TLS ECH: Avoid outer ALPN http/1.1 for WSS & HUS; Change echForceQuery's default value to "full"; Update github.com/refraction-networking/utls to 20260301010127; Add irrelevant tests for uTLS-REALITY (#5725)
https://github.com/XTLS/Xray-core/pull/5725#issuecomment-3982680111
This commit is contained in:
2
go.mod
2
go.mod
@@ -13,7 +13,7 @@ require (
|
||||
github.com/miekg/dns v1.1.72
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/pires/go-proxyproto v0.11.0
|
||||
github.com/refraction-networking/utls v1.8.2
|
||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
|
||||
github.com/sagernet/sing v0.5.1
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||
github.com/stretchr/testify v1.11.1
|
||||
|
||||
4
go.sum
4
go.sum
@@ -51,8 +51,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
|
||||
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af h1:er2acxbi3N1nvEq6HXHUAR1nTWEJmQfqiGR8EVT9rfs=
|
||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
|
||||
|
||||
@@ -3,6 +3,7 @@ package scenarios
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -497,3 +498,152 @@ func TestVlessXtlsVisionReality(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// This testing test all known utls fingerprint in tls.PresetFingerprints that support reality (expect unsafe and random*)
|
||||
// Beacuse figerprint support may be broken after utls/reality update
|
||||
// Known broken fingerprint: android, 360
|
||||
func TestVlessRealityFingerprints(t *testing.T) {
|
||||
TestFingerprint := func(fingerprint string) error {
|
||||
tcpServer := tcp.Server{
|
||||
MsgProcessor: xor,
|
||||
}
|
||||
dest, err := tcpServer.Start()
|
||||
common.Must(err)
|
||||
defer tcpServer.Close()
|
||||
|
||||
userID := protocol.NewID(uuid.New())
|
||||
serverPort := tcp.PickPort()
|
||||
privateKey, _ := base64.RawURLEncoding.DecodeString("aGSYystUbf59_9_6LKRxD27rmSW_-2_nyd9YG_Gwbks")
|
||||
publicKey, _ := base64.RawURLEncoding.DecodeString("E59WjnvZcQMu7tR7_BgyhycuEdBS-CtKxfImRCdAvFM")
|
||||
shortIds := make([][]byte, 1)
|
||||
shortIds[0] = make([]byte, 8)
|
||||
hex.Decode(shortIds[0], []byte("0123456789abcdef"))
|
||||
serverConfig := &core.Config{
|
||||
App: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&log.Config{
|
||||
ErrorLogType: log.LogType_None,
|
||||
}),
|
||||
},
|
||||
Inbound: []*core.InboundHandlerConfig{
|
||||
{
|
||||
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
||||
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
|
||||
Listen: net.NewIPOrDomain(net.LocalHostIP),
|
||||
StreamSettings: &internet.StreamConfig{
|
||||
ProtocolName: "tcp",
|
||||
SecurityType: serial.GetMessageType(&reality.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&reality.Config{
|
||||
Show: false,
|
||||
Dest: "www.google.com:443", // use google for now, may fail in some region
|
||||
ServerNames: []string{"www.google.com"},
|
||||
PrivateKey: privateKey,
|
||||
ShortIds: shortIds,
|
||||
Type: "tcp",
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&inbound.Config{
|
||||
Clients: []*protocol.User{
|
||||
{
|
||||
Account: serial.ToTypedMessage(&vless.Account{
|
||||
Id: userID.String(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
clientPort := tcp.PickPort()
|
||||
clientConfig := &core.Config{
|
||||
App: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&log.Config{
|
||||
ErrorLogType: log.LogType_None,
|
||||
}),
|
||||
},
|
||||
Inbound: []*core.InboundHandlerConfig{
|
||||
{
|
||||
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
||||
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},
|
||||
Listen: net.NewIPOrDomain(net.LocalHostIP),
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
|
||||
Address: net.NewIPOrDomain(dest.Address),
|
||||
Port: uint32(dest.Port),
|
||||
Networks: []net.Network{net.Network_TCP},
|
||||
}),
|
||||
},
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&outbound.Config{
|
||||
Vnext: &protocol.ServerEndpoint{
|
||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||
Port: uint32(serverPort),
|
||||
User: &protocol.User{
|
||||
Account: serial.ToTypedMessage(&vless.Account{
|
||||
Id: userID.String(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
|
||||
StreamSettings: &internet.StreamConfig{
|
||||
ProtocolName: "tcp",
|
||||
TransportSettings: []*internet.TransportConfig{
|
||||
{
|
||||
ProtocolName: "tcp",
|
||||
Settings: serial.ToTypedMessage(&transtcp.Config{}),
|
||||
},
|
||||
},
|
||||
SecurityType: serial.GetMessageType(&reality.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&reality.Config{
|
||||
Show: false,
|
||||
Fingerprint: fingerprint,
|
||||
ServerName: "www.google.com",
|
||||
PublicKey: publicKey,
|
||||
ShortId: shortIds[0],
|
||||
SpiderX: "/",
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
servers, err := InitializeServerConfigs(serverConfig, clientConfig)
|
||||
common.Must(err)
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
err = testTCPConn(clientPort, 1024*1024, time.Second*15)()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
fingerPrints := []string{"chrome", "firefox", "safari", "ios", "edge", "qq"}
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(fingerPrints))
|
||||
for _, fp := range fingerPrints {
|
||||
go func() {
|
||||
err := TestFingerprint(fp)
|
||||
if err != nil {
|
||||
t.Errorf("Fingerprint %s test failed: %v", fp, err)
|
||||
} else {
|
||||
t.Logf("Fingerprint %s test passed", fp)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ func ApplyECH(c *Config, config *tls.Config) error {
|
||||
switch ECHForceQuery {
|
||||
case "none", "half", "full":
|
||||
case "":
|
||||
ECHForceQuery = "none" // default to none
|
||||
ECHForceQuery = "full" // default to full
|
||||
default:
|
||||
panic("Invalid ECHForceQuery: " + c.EchForceQuery)
|
||||
}
|
||||
@@ -174,7 +174,7 @@ func QueryRecord(domain string, server string, forceQuery string, sockopt *inter
|
||||
// If expire is zero value, it means we are in initial state, wait for the query to finish
|
||||
// otherwise return old value immediately and update in a goroutine
|
||||
// but if the cache is too old, wait for update
|
||||
if configRecord.expire == (time.Time{}) || configRecord.expire.Add(time.Hour*6).Before(time.Now()) {
|
||||
if configRecord.expire == (time.Time{}) || configRecord.expire.Add(time.Hour*4).Before(time.Now()) {
|
||||
return echConfigCache.Update(domain, server, false, forceQuery, sockopt)
|
||||
} else {
|
||||
// If someone already acquired the lock, it means it is updating, do not start another update goroutine
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
utls "github.com/refraction-networking/utls"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
@@ -97,6 +98,12 @@ func (c *UConn) WebsocketHandshakeContext(ctx context.Context) error {
|
||||
if err := c.BuildHandshakeState(); err != nil {
|
||||
return err
|
||||
}
|
||||
config := *utils.AccessField[*utls.Config](c, "config")
|
||||
// Do not modify outer ALPN to http/1.1 if ECH is used
|
||||
// Outer ALPN will be h2,http/1.1, and real ALPN in config will be hidden in ECH
|
||||
if config.EncryptedClientHelloConfigList != nil {
|
||||
return c.HandshakeContext(ctx)
|
||||
}
|
||||
// Iterate over extensions and check for utls.ALPNExtension
|
||||
hasALPNExtension := false
|
||||
for _, extension := range c.Extensions {
|
||||
@@ -131,7 +138,7 @@ func GeneraticUClient(c net.Conn, config *tls.Config) *utls.UConn {
|
||||
}
|
||||
|
||||
func copyConfig(c *tls.Config) *utls.Config {
|
||||
return &utls.Config{
|
||||
config := &utls.Config{
|
||||
Rand: c.Rand,
|
||||
RootCAs: c.RootCAs,
|
||||
ServerName: c.ServerName,
|
||||
@@ -140,6 +147,10 @@ func copyConfig(c *tls.Config) *utls.Config {
|
||||
KeyLogWriter: c.KeyLogWriter,
|
||||
EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList,
|
||||
}
|
||||
if config.EncryptedClientHelloConfigList != nil {
|
||||
config.NextProtos = c.NextProtos
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
Reference in New Issue
Block a user