mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-05 13:28:13 +03:00
Compare commits
1
Commits
ech-origin
..
h2c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b53ae0a183 |
@@ -27,7 +27,7 @@ require (
|
|||||||
golang.org/x/sys v0.42.0
|
golang.org/x/sys v0.42.0
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
||||||
google.golang.org/grpc v1.80.0
|
google.golang.org/grpc v1.79.3
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.11
|
||||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
|
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
|
||||||
h12.io/socks v1.0.3
|
h12.io/socks v1.0.3
|
||||||
@@ -48,7 +48,7 @@ require (
|
|||||||
golang.org/x/text v0.35.0 // indirect
|
golang.org/x/text v0.35.0 // indirect
|
||||||
golang.org/x/time v0.12.0 // indirect
|
golang.org/x/time v0.12.0 // indirect
|
||||||
golang.org/x/tools v0.42.0 // indirect
|
golang.org/x/tools v0.42.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -131,12 +131,12 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
|
|||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
||||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@@ -243,6 +243,7 @@ type SplitHTTPConfig struct {
|
|||||||
ScMaxBufferedPosts int64 `json:"scMaxBufferedPosts"`
|
ScMaxBufferedPosts int64 `json:"scMaxBufferedPosts"`
|
||||||
ScStreamUpServerSecs Int32Range `json:"scStreamUpServerSecs"`
|
ScStreamUpServerSecs Int32Range `json:"scStreamUpServerSecs"`
|
||||||
ServerMaxHeaderBytes int32 `json:"serverMaxHeaderBytes"`
|
ServerMaxHeaderBytes int32 `json:"serverMaxHeaderBytes"`
|
||||||
|
AllowH2C bool `json:"allowH2C"`
|
||||||
Xmux XmuxConfig `json:"xmux"`
|
Xmux XmuxConfig `json:"xmux"`
|
||||||
DownloadSettings *StreamConfig `json:"downloadSettings"`
|
DownloadSettings *StreamConfig `json:"downloadSettings"`
|
||||||
Extra json.RawMessage `json:"extra"`
|
Extra json.RawMessage `json:"extra"`
|
||||||
@@ -426,6 +427,7 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
|||||||
ScMaxBufferedPosts: c.ScMaxBufferedPosts,
|
ScMaxBufferedPosts: c.ScMaxBufferedPosts,
|
||||||
ScStreamUpServerSecs: newRangeConfig(c.ScStreamUpServerSecs),
|
ScStreamUpServerSecs: newRangeConfig(c.ScStreamUpServerSecs),
|
||||||
ServerMaxHeaderBytes: c.ServerMaxHeaderBytes,
|
ServerMaxHeaderBytes: c.ServerMaxHeaderBytes,
|
||||||
|
AllowH2C: c.AllowH2C,
|
||||||
Xmux: &splithttp.XmuxConfig{
|
Xmux: &splithttp.XmuxConfig{
|
||||||
MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency),
|
MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency),
|
||||||
MaxConnections: newRangeConfig(c.Xmux.MaxConnections),
|
MaxConnections: newRangeConfig(c.Xmux.MaxConnections),
|
||||||
|
|||||||
@@ -83,12 +83,10 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
|
|||||||
inbound.Name = "hysteria"
|
inbound.Name = "hysteria"
|
||||||
inbound.CanSpliceCopy = 3
|
inbound.CanSpliceCopy = 3
|
||||||
|
|
||||||
iConn := stat.TryUnwrapStatsConn(conn)
|
|
||||||
|
|
||||||
var useremail string
|
var useremail string
|
||||||
var userlevel uint32
|
var userlevel uint32
|
||||||
type User interface{ User() *protocol.MemoryUser }
|
type User interface{ User() *protocol.MemoryUser }
|
||||||
if v, ok := iConn.(User); ok {
|
if v, ok := conn.(User); ok {
|
||||||
inbound.User = v.User()
|
inbound.User = v.User()
|
||||||
if inbound.User != nil {
|
if inbound.User != nil {
|
||||||
useremail = inbound.User.Email
|
useremail = inbound.User.Email
|
||||||
@@ -96,6 +94,7 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iConn := stat.TryUnwrapStatsConn(conn)
|
||||||
if _, ok := iConn.(*hysteria.InterUdpConn); ok {
|
if _, ok := iConn.(*hysteria.InterUdpConn); ok {
|
||||||
r := io.Reader(conn)
|
r := io.Reader(conn)
|
||||||
b := make([]byte, MaxUDPSize)
|
b := make([]byte, MaxUDPSize)
|
||||||
|
|||||||
+8
-13
@@ -10,7 +10,6 @@ import (
|
|||||||
"golang.zx2c4.com/wireguard/conn"
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/features/dns"
|
"github.com/xtls/xray-core/features/dns"
|
||||||
@@ -18,7 +17,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type netReadInfo struct {
|
type netReadInfo struct {
|
||||||
buff *buf.Buffer
|
buff []byte
|
||||||
endpoint conn.Endpoint
|
endpoint conn.Endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,8 +82,7 @@ func (bind *netBind) Open(uport uint16) ([]conn.ReceiveFunc, uint16, error) {
|
|||||||
fun := func(bufs [][]byte, sizes []int, eps []conn.Endpoint) (n int, err error) {
|
fun := func(bufs [][]byte, sizes []int, eps []conn.Endpoint) (n int, err error) {
|
||||||
select {
|
select {
|
||||||
case r := <-bind.readQueue:
|
case r := <-bind.readQueue:
|
||||||
sizes[0], eps[0] = copy(bufs[0], r.buff.Bytes()), r.endpoint
|
sizes[0], eps[0] = copy(bufs[0], r.buff), r.endpoint
|
||||||
r.buff.Release()
|
|
||||||
return 1, nil
|
return 1, nil
|
||||||
case <-bind.closedCh:
|
case <-bind.closedCh:
|
||||||
errors.LogDebug(context.Background(), "recv func closed")
|
errors.LogDebug(context.Background(), "recv func closed")
|
||||||
@@ -132,30 +130,27 @@ func (bind *netBindClient) connectTo(endpoint *netEndpoint) error {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
buff := buf.NewWithSize(device.MaxMessageSize)
|
buff := make([]byte, device.MaxMessageSize)
|
||||||
n, err := buff.ReadFrom(c)
|
n, err := c.Read(buff)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
buff.Release()
|
|
||||||
endpoint.conn = nil
|
endpoint.conn = nil
|
||||||
c.Close()
|
c.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rawBytes := buff.Bytes()
|
|
||||||
if n > 3 {
|
if n > 3 {
|
||||||
rawBytes[1] = 0
|
buff[1] = 0
|
||||||
rawBytes[2] = 0
|
buff[2] = 0
|
||||||
rawBytes[3] = 0
|
buff[3] = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case bind.readQueue <- &netReadInfo{
|
case bind.readQueue <- &netReadInfo{
|
||||||
buff: buff,
|
buff: buff[:n],
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
}:
|
}:
|
||||||
case <-bind.closedCh:
|
case <-bind.closedCh:
|
||||||
buff.Release()
|
|
||||||
endpoint.conn = nil
|
endpoint.conn = nil
|
||||||
c.Close()
|
c.Close()
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -101,17 +101,17 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, b := range mb {
|
for i, b := range mb {
|
||||||
|
buff := b.Bytes()
|
||||||
|
|
||||||
rawBytes := b.Bytes()
|
|
||||||
if b.Len() > 3 {
|
if b.Len() > 3 {
|
||||||
rawBytes[1] = 0
|
buff[1] = 0
|
||||||
rawBytes[2] = 0
|
buff[2] = 0
|
||||||
rawBytes[3] = 0
|
buff[3] = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case s.bindServer.readQueue <- &netReadInfo{
|
case s.bindServer.readQueue <- &netReadInfo{
|
||||||
buff: b,
|
buff: buff,
|
||||||
endpoint: nep,
|
endpoint: nep,
|
||||||
}:
|
}:
|
||||||
case <-s.bindServer.closedCh:
|
case <-s.bindServer.closedCh:
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/testing/servers/tcp"
|
"github.com/xtls/xray-core/testing/servers/tcp"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
"github.com/xtls/xray-core/transport/internet/reality"
|
"github.com/xtls/xray-core/transport/internet/reality"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/splithttp"
|
||||||
transtcp "github.com/xtls/xray-core/transport/internet/tcp"
|
transtcp "github.com/xtls/xray-core/transport/internet/tcp"
|
||||||
"github.com/xtls/xray-core/transport/internet/tls"
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
@@ -647,3 +648,121 @@ func TestVlessRealityFingerprints(t *testing.T) {
|
|||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVlessXHTTPH2C(t *testing.T) {
|
||||||
|
tcpServer := tcp.Server{
|
||||||
|
MsgProcessor: xor,
|
||||||
|
}
|
||||||
|
dest, err := tcpServer.Start()
|
||||||
|
common.Must(err)
|
||||||
|
defer tcpServer.Close()
|
||||||
|
|
||||||
|
userID := protocol.NewID(uuid.New())
|
||||||
|
serverPort := tcp.PickPort()
|
||||||
|
|
||||||
|
xhttpSettings := serial.ToTypedMessage(&splithttp.Config{
|
||||||
|
Path: "/h2c",
|
||||||
|
AllowH2C: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
serverConfig := &core.Config{
|
||||||
|
App: []*serial.TypedMessage{
|
||||||
|
serial.ToTypedMessage(&log.Config{
|
||||||
|
ErrorLogLevel: clog.Severity_Debug,
|
||||||
|
ErrorLogType: log.LogType_Console,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
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: "splithttp",
|
||||||
|
TransportSettings: []*internet.TransportConfig{
|
||||||
|
{
|
||||||
|
ProtocolName: "splithttp",
|
||||||
|
Settings: xhttpSettings,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
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{
|
||||||
|
ErrorLogLevel: clog.Severity_Debug,
|
||||||
|
ErrorLogType: log.LogType_Console,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
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: "splithttp",
|
||||||
|
TransportSettings: []*internet.TransportConfig{
|
||||||
|
{
|
||||||
|
ProtocolName: "splithttp",
|
||||||
|
Settings: xhttpSettings,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := InitializeServerConfigs(serverConfig, clientConfig)
|
||||||
|
common.Must(err)
|
||||||
|
defer CloseAllServers(servers)
|
||||||
|
|
||||||
|
var errg errgroup.Group
|
||||||
|
for range 3 {
|
||||||
|
errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))
|
||||||
|
}
|
||||||
|
if err := errg.Wait(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ type Config struct {
|
|||||||
UplinkDataKey string `protobuf:"bytes,25,opt,name=uplinkDataKey,proto3" json:"uplinkDataKey,omitempty"`
|
UplinkDataKey string `protobuf:"bytes,25,opt,name=uplinkDataKey,proto3" json:"uplinkDataKey,omitempty"`
|
||||||
UplinkChunkSize *RangeConfig `protobuf:"bytes,26,opt,name=uplinkChunkSize,proto3" json:"uplinkChunkSize,omitempty"`
|
UplinkChunkSize *RangeConfig `protobuf:"bytes,26,opt,name=uplinkChunkSize,proto3" json:"uplinkChunkSize,omitempty"`
|
||||||
ServerMaxHeaderBytes int32 `protobuf:"varint,27,opt,name=serverMaxHeaderBytes,proto3" json:"serverMaxHeaderBytes,omitempty"`
|
ServerMaxHeaderBytes int32 `protobuf:"varint,27,opt,name=serverMaxHeaderBytes,proto3" json:"serverMaxHeaderBytes,omitempty"`
|
||||||
|
AllowH2C bool `protobuf:"varint,28,opt,name=allow_h2c,json=allowH2c,proto3" json:"allow_h2c,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -410,6 +411,13 @@ func (x *Config) GetServerMaxHeaderBytes() int32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetAllowH2C() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.AllowH2C
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
var File_transport_internet_splithttp_config_proto protoreflect.FileDescriptor
|
var File_transport_internet_splithttp_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
const file_transport_internet_splithttp_config_proto_rawDesc = "" +
|
const file_transport_internet_splithttp_config_proto_rawDesc = "" +
|
||||||
@@ -425,7 +433,7 @@ const file_transport_internet_splithttp_config_proto_rawDesc = "" +
|
|||||||
"\x0ecMaxReuseTimes\x18\x03 \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x0ecMaxReuseTimes\x12Z\n" +
|
"\x0ecMaxReuseTimes\x18\x03 \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x0ecMaxReuseTimes\x12Z\n" +
|
||||||
"\x10hMaxRequestTimes\x18\x04 \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x10hMaxRequestTimes\x12Z\n" +
|
"\x10hMaxRequestTimes\x18\x04 \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x10hMaxRequestTimes\x12Z\n" +
|
||||||
"\x10hMaxReusableSecs\x18\x05 \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x10hMaxReusableSecs\x12*\n" +
|
"\x10hMaxReusableSecs\x18\x05 \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x10hMaxReusableSecs\x12*\n" +
|
||||||
"\x10hKeepAlivePeriod\x18\x06 \x01(\x03R\x10hKeepAlivePeriod\"\xc2\v\n" +
|
"\x10hKeepAlivePeriod\x18\x06 \x01(\x03R\x10hKeepAlivePeriod\"\xdf\v\n" +
|
||||||
"\x06Config\x12\x12\n" +
|
"\x06Config\x12\x12\n" +
|
||||||
"\x04host\x18\x01 \x01(\tR\x04host\x12\x12\n" +
|
"\x04host\x18\x01 \x01(\tR\x04host\x12\x12\n" +
|
||||||
"\x04path\x18\x02 \x01(\tR\x04path\x12\x12\n" +
|
"\x04path\x18\x02 \x01(\tR\x04path\x12\x12\n" +
|
||||||
@@ -456,7 +464,8 @@ const file_transport_internet_splithttp_config_proto_rawDesc = "" +
|
|||||||
"\x13uplinkDataPlacement\x18\x18 \x01(\tR\x13uplinkDataPlacement\x12$\n" +
|
"\x13uplinkDataPlacement\x18\x18 \x01(\tR\x13uplinkDataPlacement\x12$\n" +
|
||||||
"\ruplinkDataKey\x18\x19 \x01(\tR\ruplinkDataKey\x12X\n" +
|
"\ruplinkDataKey\x18\x19 \x01(\tR\ruplinkDataKey\x12X\n" +
|
||||||
"\x0fuplinkChunkSize\x18\x1a \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x0fuplinkChunkSize\x122\n" +
|
"\x0fuplinkChunkSize\x18\x1a \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x0fuplinkChunkSize\x122\n" +
|
||||||
"\x14serverMaxHeaderBytes\x18\x1b \x01(\x05R\x14serverMaxHeaderBytes\x1a:\n" +
|
"\x14serverMaxHeaderBytes\x18\x1b \x01(\x05R\x14serverMaxHeaderBytes\x12\x1b\n" +
|
||||||
|
"\tallow_h2c\x18\x1c \x01(\bR\ballowH2c\x1a:\n" +
|
||||||
"\fHeadersEntry\x12\x10\n" +
|
"\fHeadersEntry\x12\x10\n" +
|
||||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x85\x01\n" +
|
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x85\x01\n" +
|
||||||
|
|||||||
@@ -50,4 +50,5 @@ message Config {
|
|||||||
string uplinkDataKey = 25;
|
string uplinkDataKey = 25;
|
||||||
RangeConfig uplinkChunkSize = 26;
|
RangeConfig uplinkChunkSize = 26;
|
||||||
int32 serverMaxHeaderBytes = 27;
|
int32 serverMaxHeaderBytes = 27;
|
||||||
|
bool allow_h2c = 28;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,11 +79,14 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in
|
|||||||
return xmuxClient.XmuxConn.(DialerClient), xmuxClient
|
return xmuxClient.XmuxConn.(DialerClient), xmuxClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func decideHTTPVersion(tlsConfig *tls.Config, realityConfig *reality.Config) string {
|
func decideHTTPVersion(tlsConfig *tls.Config, realityConfig *reality.Config, allowH2C bool) string {
|
||||||
if realityConfig != nil {
|
if realityConfig != nil {
|
||||||
return "2"
|
return "2"
|
||||||
}
|
}
|
||||||
if tlsConfig == nil {
|
if tlsConfig == nil {
|
||||||
|
if allowH2C {
|
||||||
|
return "2"
|
||||||
|
}
|
||||||
return "1.1"
|
return "1.1"
|
||||||
}
|
}
|
||||||
if len(tlsConfig.NextProtocol) != 1 {
|
if len(tlsConfig.NextProtocol) != 1 {
|
||||||
@@ -101,8 +104,9 @@ func decideHTTPVersion(tlsConfig *tls.Config, realityConfig *reality.Config) str
|
|||||||
func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStreamConfig) DialerClient {
|
func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStreamConfig) DialerClient {
|
||||||
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
|
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
|
||||||
realityConfig := reality.ConfigFromStreamSettings(streamSettings)
|
realityConfig := reality.ConfigFromStreamSettings(streamSettings)
|
||||||
|
transportConfig := streamSettings.ProtocolSettings.(*Config)
|
||||||
|
|
||||||
httpVersion := decideHTTPVersion(tlsConfig, realityConfig)
|
httpVersion := decideHTTPVersion(tlsConfig, realityConfig, transportConfig.AllowH2C)
|
||||||
if httpVersion == "3" {
|
if httpVersion == "3" {
|
||||||
dest.Network = net.Network_UDP // better to keep this line
|
dest.Network = net.Network_UDP // better to keep this line
|
||||||
}
|
}
|
||||||
@@ -113,8 +117,6 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
|
|||||||
gotlsConfig = tlsConfig.GetTLSConfig(tls.WithDestination(dest))
|
gotlsConfig = tlsConfig.GetTLSConfig(tls.WithDestination(dest))
|
||||||
}
|
}
|
||||||
|
|
||||||
transportConfig := streamSettings.ProtocolSettings.(*Config)
|
|
||||||
|
|
||||||
dialContext := func(ctxInner context.Context) (net.Conn, error) {
|
dialContext := func(ctxInner context.Context) (net.Conn, error) {
|
||||||
conn, err := internet.DialSystem(ctxInner, dest, streamSettings.SocketSettings)
|
conn, err := internet.DialSystem(ctxInner, dest, streamSettings.SocketSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -312,6 +314,7 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
|
|||||||
},
|
},
|
||||||
IdleConnTimeout: net.ConnIdleTimeout,
|
IdleConnTimeout: net.ConnIdleTimeout,
|
||||||
ReadIdleTimeout: keepAlivePeriod,
|
ReadIdleTimeout: keepAlivePeriod,
|
||||||
|
AllowHTTP: transportConfig.AllowH2C,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
httpDialContext := func(ctxInner context.Context, network string, addr string) (net.Conn, error) {
|
httpDialContext := func(ctxInner context.Context, network string, addr string) (net.Conn, error) {
|
||||||
@@ -348,13 +351,12 @@ func init() {
|
|||||||
func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {
|
func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {
|
||||||
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
|
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
|
||||||
realityConfig := reality.ConfigFromStreamSettings(streamSettings)
|
realityConfig := reality.ConfigFromStreamSettings(streamSettings)
|
||||||
|
transportConfiguration := streamSettings.ProtocolSettings.(*Config)
|
||||||
|
|
||||||
httpVersion := decideHTTPVersion(tlsConfig, realityConfig)
|
httpVersion := decideHTTPVersion(tlsConfig, realityConfig, transportConfiguration.AllowH2C)
|
||||||
if httpVersion == "3" {
|
if httpVersion == "3" {
|
||||||
dest.Network = net.Network_UDP
|
dest.Network = net.Network_UDP
|
||||||
}
|
}
|
||||||
|
|
||||||
transportConfiguration := streamSettings.ProtocolSettings.(*Config)
|
|
||||||
var requestURL url.URL
|
var requestURL url.URL
|
||||||
|
|
||||||
if tlsConfig != nil || realityConfig != nil {
|
if tlsConfig != nil || realityConfig != nil {
|
||||||
@@ -413,7 +415,8 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
|
|||||||
dest2 := *memory2.Destination // just panic
|
dest2 := *memory2.Destination // just panic
|
||||||
tlsConfig2 := tls.ConfigFromStreamSettings(memory2)
|
tlsConfig2 := tls.ConfigFromStreamSettings(memory2)
|
||||||
realityConfig2 := reality.ConfigFromStreamSettings(memory2)
|
realityConfig2 := reality.ConfigFromStreamSettings(memory2)
|
||||||
httpVersion2 := decideHTTPVersion(tlsConfig2, realityConfig2)
|
config2 := memory2.ProtocolSettings.(*Config)
|
||||||
|
httpVersion2 := decideHTTPVersion(tlsConfig2, realityConfig2, config2.AllowH2C)
|
||||||
if httpVersion2 == "3" {
|
if httpVersion2 == "3" {
|
||||||
dest2.Network = net.Network_UDP
|
dest2.Network = net.Network_UDP
|
||||||
}
|
}
|
||||||
@@ -422,7 +425,6 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
|
|||||||
} else {
|
} else {
|
||||||
requestURL2.Scheme = "http"
|
requestURL2.Scheme = "http"
|
||||||
}
|
}
|
||||||
config2 := memory2.ProtocolSettings.(*Config)
|
|
||||||
requestURL2.Host = config2.Host
|
requestURL2.Host = config2.Host
|
||||||
if requestURL2.Host == "" && tlsConfig2 != nil {
|
if requestURL2.Host == "" && tlsConfig2 != nil {
|
||||||
requestURL2.Host = tlsConfig2.ServerName
|
requestURL2.Host = tlsConfig2.ServerName
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -186,39 +185,60 @@ func Test_ListenXHAndDial_H2C(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
listenPort := tcp.PickPort()
|
listenPort := tcp.PickPort()
|
||||||
|
listen, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{
|
||||||
streamSettings := &internet.MemoryStreamConfig{
|
|
||||||
ProtocolName: "splithttp",
|
ProtocolName: "splithttp",
|
||||||
ProtocolSettings: &Config{
|
ProtocolSettings: &Config{
|
||||||
Path: "shs",
|
Path: "/sh",
|
||||||
|
AllowH2C: true,
|
||||||
},
|
},
|
||||||
|
}, func(conn stat.Connection) {
|
||||||
|
go func(c stat.Connection) {
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
var b [1024]byte
|
||||||
|
c.SetReadDeadline(time.Now().Add(2 * time.Second))
|
||||||
|
_, err := c.Read(b[:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
listen, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) {
|
|
||||||
go func() {
|
common.Must2(c.Write([]byte("Response")))
|
||||||
_ = conn.Close()
|
}(conn)
|
||||||
}()
|
|
||||||
})
|
})
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
defer listen.Close()
|
ctx := context.Background()
|
||||||
|
streamSettings := &internet.MemoryStreamConfig{
|
||||||
protocols := new(http.Protocols)
|
ProtocolName: "splithttp",
|
||||||
protocols.SetUnencryptedHTTP2(true)
|
ProtocolSettings: &Config{Path: "sh", AllowH2C: true},
|
||||||
client := http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
Protocols: protocols,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
conn, err := Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), listenPort), streamSettings)
|
||||||
|
|
||||||
resp, err := client.Get("http://" + net.LocalHostIP.String() + ":" + listenPort.String())
|
common.Must(err)
|
||||||
|
_, err = conn.Write([]byte("Test connection 1"))
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
if resp.StatusCode != 404 {
|
var b [1024]byte
|
||||||
t.Error("Expected 404 but got:", resp.StatusCode)
|
fmt.Println("test2")
|
||||||
|
n, _ := io.ReadFull(conn, b[:])
|
||||||
|
fmt.Println("string is", n)
|
||||||
|
if string(b[:n]) != "Response" {
|
||||||
|
t.Error("response: ", string(b[:n]))
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.ProtoMajor != 2 {
|
common.Must(conn.Close())
|
||||||
t.Error("Expected h2 but got:", resp.ProtoMajor)
|
conn, err = Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), listenPort), streamSettings)
|
||||||
|
|
||||||
|
common.Must(err)
|
||||||
|
_, err = conn.Write([]byte("Test connection 2"))
|
||||||
|
common.Must(err)
|
||||||
|
n, _ = io.ReadFull(conn, b[:])
|
||||||
|
common.Must(err)
|
||||||
|
if string(b[:n]) != "Response" {
|
||||||
|
t.Error("response: ", string(b[:n]))
|
||||||
}
|
}
|
||||||
|
common.Must(conn.Close())
|
||||||
|
|
||||||
|
common.Must(listen.Close())
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_ListenXHAndDial_QUIC(t *testing.T) {
|
func Test_ListenXHAndDial_QUIC(t *testing.T) {
|
||||||
|
|||||||
@@ -469,7 +469,11 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
|
|||||||
if len(c.EchConfigList) > 0 || len(c.EchServerKeys) > 0 {
|
if len(c.EchConfigList) > 0 || len(c.EchServerKeys) > 0 {
|
||||||
err := ApplyECH(c, config)
|
err := ApplyECH(c, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if c.EchForceQuery == "full" {
|
||||||
errors.LogError(context.Background(), err)
|
errors.LogError(context.Background(), err)
|
||||||
|
} else {
|
||||||
|
errors.LogInfo(context.Background(), err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ message Config {
|
|||||||
|
|
||||||
string ech_config_list = 19;
|
string ech_config_list = 19;
|
||||||
|
|
||||||
// Deprecated
|
|
||||||
string ech_force_query = 20;
|
string ech_force_query = 20;
|
||||||
|
|
||||||
SocketConfig ech_socket_settings = 21;
|
SocketConfig ech_socket_settings = 21;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
|
|
||||||
utls "github.com/refraction-networking/utls"
|
utls "github.com/refraction-networking/utls"
|
||||||
"github.com/xtls/xray-core/common/crypto"
|
"github.com/xtls/xray-core/common/crypto"
|
||||||
|
dns2 "github.com/xtls/xray-core/features/dns"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
@@ -48,11 +49,21 @@ func ApplyECH(c *Config, config *tls.Config) error {
|
|||||||
|
|
||||||
// for client
|
// for client
|
||||||
if len(c.EchConfigList) != 0 {
|
if len(c.EchConfigList) != 0 {
|
||||||
|
ECHForceQuery := c.EchForceQuery
|
||||||
|
switch ECHForceQuery {
|
||||||
|
case "none", "half", "full":
|
||||||
|
case "":
|
||||||
|
ECHForceQuery = "full" // default to full
|
||||||
|
default:
|
||||||
|
panic("Invalid ECHForceQuery: " + c.EchForceQuery)
|
||||||
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
// if failed to get ECHConfig, use an invalid one to make connection fail
|
// if failed to get ECHConfig, use an invalid one to make connection fail
|
||||||
if len(ECHConfig) == 0 {
|
if err != nil || len(ECHConfig) == 0 {
|
||||||
|
if ECHForceQuery == "full" {
|
||||||
ECHConfig = []byte{1, 1, 4, 5, 1, 4}
|
ECHConfig = []byte{1, 1, 4, 5, 1, 4}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
config.EncryptedClientHelloConfigList = ECHConfig
|
config.EncryptedClientHelloConfigList = ECHConfig
|
||||||
}()
|
}()
|
||||||
// direct base64 config
|
// direct base64 config
|
||||||
@@ -72,7 +83,7 @@ func ApplyECH(c *Config, config *tls.Config) error {
|
|||||||
if nameToQuery == "" {
|
if nameToQuery == "" {
|
||||||
return errors.New("Using DNS for ECH Config needs serverName or use Server format example.com+https://1.1.1.1/dns-query")
|
return errors.New("Using DNS for ECH Config needs serverName or use Server format example.com+https://1.1.1.1/dns-query")
|
||||||
}
|
}
|
||||||
ECHConfig, err = QueryRecord(nameToQuery, DNSServer, c.EchSocketSettings)
|
ECHConfig, err = QueryRecord(nameToQuery, DNSServer, c.EchForceQuery, c.EchSocketSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("Failed to query ECH DNS record for domain: ", nameToQuery, " at server: ", DNSServer).Base(err)
|
return errors.New("Failed to query ECH DNS record for domain: ", nameToQuery, " at server: ", DNSServer).Base(err)
|
||||||
}
|
}
|
||||||
@@ -96,6 +107,7 @@ type ECHConfigCache struct {
|
|||||||
type echConfigRecord struct {
|
type echConfigRecord struct {
|
||||||
config []byte
|
config []byte
|
||||||
expire time.Time
|
expire time.Time
|
||||||
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -113,34 +125,39 @@ func ECHCacheKey(server, domain string, sockopt *internet.SocketConfig) string {
|
|||||||
// Update updates the ECH config for given domain and server.
|
// Update updates the ECH config for given domain and server.
|
||||||
// this method is concurrent safe, only one update request will be sent, others get the cache.
|
// this method is concurrent safe, only one update request will be sent, others get the cache.
|
||||||
// if isLockedUpdate is true, it will not try to acquire the lock.
|
// if isLockedUpdate is true, it will not try to acquire the lock.
|
||||||
func (c *ECHConfigCache) Update(domain string, server string, isLockedUpdate bool, sockopt *internet.SocketConfig) ([]byte, error) {
|
func (c *ECHConfigCache) Update(domain string, server string, isLockedUpdate bool, forceQuery string, sockopt *internet.SocketConfig) ([]byte, error) {
|
||||||
if !isLockedUpdate {
|
if !isLockedUpdate {
|
||||||
c.UpdateLock.Lock()
|
c.UpdateLock.Lock()
|
||||||
defer c.UpdateLock.Unlock()
|
defer c.UpdateLock.Unlock()
|
||||||
}
|
}
|
||||||
// Double check cache after acquiring lock
|
// Double check cache after acquiring lock
|
||||||
configRecord := c.configRecord.Load()
|
configRecord := c.configRecord.Load()
|
||||||
if configRecord.expire.After(time.Now()) {
|
if configRecord.expire.After(time.Now()) && configRecord.err == nil {
|
||||||
errors.LogDebug(context.Background(), "Cache hit for domain after double check: ", domain)
|
errors.LogDebug(context.Background(), "Cache hit for domain after double check: ", domain)
|
||||||
return configRecord.config, nil
|
return configRecord.config, configRecord.err
|
||||||
}
|
}
|
||||||
// Query ECH config from DNS server
|
// Query ECH config from DNS server
|
||||||
errors.LogDebug(context.Background(), "Trying to query ECH config for domain: ", domain, " with ECH server: ", server)
|
errors.LogDebug(context.Background(), "Trying to query ECH config for domain: ", domain, " with ECH server: ", server)
|
||||||
echConfig, ttl, err := dnsQuery(server, domain, sockopt)
|
echConfig, ttl, err := dnsQuery(server, domain, sockopt)
|
||||||
if err != nil {
|
// if in "full", directly return
|
||||||
|
if err != nil && forceQuery == "full" {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if ttl == 0 {
|
||||||
|
ttl = dns2.DefaultTTL
|
||||||
|
}
|
||||||
configRecord = &echConfigRecord{
|
configRecord = &echConfigRecord{
|
||||||
config: echConfig,
|
config: echConfig,
|
||||||
expire: time.Now().Add(time.Duration(ttl) * time.Second),
|
expire: time.Now().Add(time.Duration(ttl) * time.Second),
|
||||||
|
err: err,
|
||||||
}
|
}
|
||||||
c.configRecord.Store(configRecord)
|
c.configRecord.Store(configRecord)
|
||||||
return configRecord.config, nil
|
return configRecord.config, configRecord.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryRecord returns the ECH config for given domain.
|
// QueryRecord returns the ECH config for given domain.
|
||||||
// If the record is not in cache or expired, it will query the DNS server and update the cache.
|
// If the record is not in cache or expired, it will query the DNS server and update the cache.
|
||||||
func QueryRecord(domain string, server string, sockopt *internet.SocketConfig) ([]byte, error) {
|
func QueryRecord(domain string, server string, forceQuery string, sockopt *internet.SocketConfig) ([]byte, error) {
|
||||||
GlobalECHConfigCacheKey := ECHCacheKey(server, domain, sockopt)
|
GlobalECHConfigCacheKey := ECHCacheKey(server, domain, sockopt)
|
||||||
echConfigCache, ok := GlobalECHConfigCache.Load(GlobalECHConfigCacheKey)
|
echConfigCache, ok := GlobalECHConfigCache.Load(GlobalECHConfigCacheKey)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -149,25 +166,25 @@ func QueryRecord(domain string, server string, sockopt *internet.SocketConfig) (
|
|||||||
echConfigCache, _ = GlobalECHConfigCache.LoadOrStore(GlobalECHConfigCacheKey, echConfigCache)
|
echConfigCache, _ = GlobalECHConfigCache.LoadOrStore(GlobalECHConfigCacheKey, echConfigCache)
|
||||||
}
|
}
|
||||||
configRecord := echConfigCache.configRecord.Load()
|
configRecord := echConfigCache.configRecord.Load()
|
||||||
if configRecord.expire.After(time.Now()) {
|
if configRecord.expire.After(time.Now()) && (configRecord.err == nil || forceQuery == "none") {
|
||||||
errors.LogDebug(context.Background(), "Cache hit for domain: ", domain)
|
errors.LogDebug(context.Background(), "Cache hit for domain: ", domain)
|
||||||
return configRecord.config, nil
|
return configRecord.config, configRecord.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If expire is zero value, it means we are in initial state, wait for the query to finish
|
// 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
|
// otherwise return old value immediately and update in a goroutine
|
||||||
// but if the cache is too old, wait for update
|
// but if the cache is too old, wait for update
|
||||||
if configRecord.expire == (time.Time{}) || configRecord.expire.Add(time.Hour*4).Before(time.Now()) {
|
if configRecord.expire == (time.Time{}) || configRecord.expire.Add(time.Hour*4).Before(time.Now()) {
|
||||||
return echConfigCache.Update(domain, server, false, sockopt)
|
return echConfigCache.Update(domain, server, false, forceQuery, sockopt)
|
||||||
} else {
|
} else {
|
||||||
// If someone already acquired the lock, it means it is updating, do not start another update goroutine
|
// If someone already acquired the lock, it means it is updating, do not start another update goroutine
|
||||||
if echConfigCache.UpdateLock.TryLock() {
|
if echConfigCache.UpdateLock.TryLock() {
|
||||||
go func() {
|
go func() {
|
||||||
defer echConfigCache.UpdateLock.Unlock()
|
defer echConfigCache.UpdateLock.Unlock()
|
||||||
echConfigCache.Update(domain, server, true, sockopt)
|
echConfigCache.Update(domain, server, true, forceQuery, sockopt)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
return configRecord.config, nil
|
return configRecord.config, configRecord.err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,7 +322,8 @@ func dnsQuery(server string, domain string, sockopt *internet.SocketConfig) ([]b
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, 0, errors.New("no valid ECH config found in DNS response")
|
// empty is valid, means no ECH config found
|
||||||
|
return nil, dns2.DefaultTTL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrInvalidLen = errors.New("goech: invalid length")
|
var ErrInvalidLen = errors.New("goech: invalid length")
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package tls
|
|||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -60,11 +59,21 @@ func TestECHDial(t *testing.T) {
|
|||||||
func TestECHDialFail(t *testing.T) {
|
func TestECHDialFail(t *testing.T) {
|
||||||
config := &Config{
|
config := &Config{
|
||||||
ServerName: "cloudflare.com",
|
ServerName: "cloudflare.com",
|
||||||
EchConfigList: "udp://0.0.0.0",
|
EchConfigList: "udp://127.0.0.1",
|
||||||
|
EchForceQuery: "half",
|
||||||
}
|
}
|
||||||
tlsConfig := config.GetTLSConfig()
|
config.GetTLSConfig()
|
||||||
ApplyECH(config, tlsConfig)
|
// check cache
|
||||||
if !slices.Equal(tlsConfig.EncryptedClientHelloConfigList, []byte{1, 1, 4, 5, 1, 4}) {
|
echConfigCache, ok := GlobalECHConfigCache.Load(ECHCacheKey("udp://127.0.0.1", "cloudflare.com", nil))
|
||||||
t.Error("ECH config should be invalid when query failed", " but got ", tlsConfig.EncryptedClientHelloConfigList)
|
if !ok {
|
||||||
|
t.Error("ECH config cache not found")
|
||||||
|
}
|
||||||
|
configRecord := echConfigCache.configRecord.Load()
|
||||||
|
if configRecord == nil {
|
||||||
|
t.Error("ECH config record not found in cache")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if configRecord.err == nil {
|
||||||
|
t.Error("unexpected nil error in ECH config record")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user