mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-05-08 14:13:22 +00:00
h2c
This commit is contained in:
@@ -243,6 +243,7 @@ type SplitHTTPConfig struct {
|
||||
ScMaxBufferedPosts int64 `json:"scMaxBufferedPosts"`
|
||||
ScStreamUpServerSecs Int32Range `json:"scStreamUpServerSecs"`
|
||||
ServerMaxHeaderBytes int32 `json:"serverMaxHeaderBytes"`
|
||||
AllowH2C bool `json:"allowH2C"`
|
||||
Xmux XmuxConfig `json:"xmux"`
|
||||
DownloadSettings *StreamConfig `json:"downloadSettings"`
|
||||
Extra json.RawMessage `json:"extra"`
|
||||
@@ -426,6 +427,7 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
ScMaxBufferedPosts: c.ScMaxBufferedPosts,
|
||||
ScStreamUpServerSecs: newRangeConfig(c.ScStreamUpServerSecs),
|
||||
ServerMaxHeaderBytes: c.ServerMaxHeaderBytes,
|
||||
AllowH2C: c.AllowH2C,
|
||||
Xmux: &splithttp.XmuxConfig{
|
||||
MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency),
|
||||
MaxConnections: newRangeConfig(c.Xmux.MaxConnections),
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/xtls/xray-core/testing/servers/tcp"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"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"
|
||||
"github.com/xtls/xray-core/transport/internet/tls"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -647,3 +648,121 @@ func TestVlessRealityFingerprints(t *testing.T) {
|
||||
}
|
||||
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"`
|
||||
UplinkChunkSize *RangeConfig `protobuf:"bytes,26,opt,name=uplinkChunkSize,proto3" json:"uplinkChunkSize,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
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -410,6 +411,13 @@ func (x *Config) GetServerMaxHeaderBytes() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetAllowH2C() bool {
|
||||
if x != nil {
|
||||
return x.AllowH2C
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var File_transport_internet_splithttp_config_proto protoreflect.FileDescriptor
|
||||
|
||||
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" +
|
||||
"\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" +
|
||||
"\x10hKeepAlivePeriod\x18\x06 \x01(\x03R\x10hKeepAlivePeriod\"\xc2\v\n" +
|
||||
"\x10hKeepAlivePeriod\x18\x06 \x01(\x03R\x10hKeepAlivePeriod\"\xdf\v\n" +
|
||||
"\x06Config\x12\x12\n" +
|
||||
"\x04host\x18\x01 \x01(\tR\x04host\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" +
|
||||
"\ruplinkDataKey\x18\x19 \x01(\tR\ruplinkDataKey\x12X\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" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x85\x01\n" +
|
||||
|
||||
@@ -50,4 +50,5 @@ message Config {
|
||||
string uplinkDataKey = 25;
|
||||
RangeConfig uplinkChunkSize = 26;
|
||||
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
|
||||
}
|
||||
|
||||
func decideHTTPVersion(tlsConfig *tls.Config, realityConfig *reality.Config) string {
|
||||
func decideHTTPVersion(tlsConfig *tls.Config, realityConfig *reality.Config, allowH2C bool) string {
|
||||
if realityConfig != nil {
|
||||
return "2"
|
||||
}
|
||||
if tlsConfig == nil {
|
||||
if allowH2C {
|
||||
return "2"
|
||||
}
|
||||
return "1.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 {
|
||||
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
|
||||
realityConfig := reality.ConfigFromStreamSettings(streamSettings)
|
||||
transportConfig := streamSettings.ProtocolSettings.(*Config)
|
||||
|
||||
httpVersion := decideHTTPVersion(tlsConfig, realityConfig)
|
||||
httpVersion := decideHTTPVersion(tlsConfig, realityConfig, transportConfig.AllowH2C)
|
||||
if httpVersion == "3" {
|
||||
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))
|
||||
}
|
||||
|
||||
transportConfig := streamSettings.ProtocolSettings.(*Config)
|
||||
|
||||
dialContext := func(ctxInner context.Context) (net.Conn, error) {
|
||||
conn, err := internet.DialSystem(ctxInner, dest, streamSettings.SocketSettings)
|
||||
if err != nil {
|
||||
@@ -312,6 +314,7 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
|
||||
},
|
||||
IdleConnTimeout: net.ConnIdleTimeout,
|
||||
ReadIdleTimeout: keepAlivePeriod,
|
||||
AllowHTTP: transportConfig.AllowH2C,
|
||||
}
|
||||
} else {
|
||||
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) {
|
||||
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
|
||||
realityConfig := reality.ConfigFromStreamSettings(streamSettings)
|
||||
transportConfiguration := streamSettings.ProtocolSettings.(*Config)
|
||||
|
||||
httpVersion := decideHTTPVersion(tlsConfig, realityConfig)
|
||||
httpVersion := decideHTTPVersion(tlsConfig, realityConfig, transportConfiguration.AllowH2C)
|
||||
if httpVersion == "3" {
|
||||
dest.Network = net.Network_UDP
|
||||
}
|
||||
|
||||
transportConfiguration := streamSettings.ProtocolSettings.(*Config)
|
||||
var requestURL url.URL
|
||||
|
||||
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
|
||||
tlsConfig2 := tls.ConfigFromStreamSettings(memory2)
|
||||
realityConfig2 := reality.ConfigFromStreamSettings(memory2)
|
||||
httpVersion2 := decideHTTPVersion(tlsConfig2, realityConfig2)
|
||||
config2 := memory2.ProtocolSettings.(*Config)
|
||||
httpVersion2 := decideHTTPVersion(tlsConfig2, realityConfig2, config2.AllowH2C)
|
||||
if httpVersion2 == "3" {
|
||||
dest2.Network = net.Network_UDP
|
||||
}
|
||||
@@ -422,7 +425,6 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
|
||||
} else {
|
||||
requestURL2.Scheme = "http"
|
||||
}
|
||||
config2 := memory2.ProtocolSettings.(*Config)
|
||||
requestURL2.Host = config2.Host
|
||||
if requestURL2.Host == "" && tlsConfig2 != nil {
|
||||
requestURL2.Host = tlsConfig2.ServerName
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -186,39 +185,60 @@ func Test_ListenXHAndDial_H2C(t *testing.T) {
|
||||
}
|
||||
|
||||
listenPort := tcp.PickPort()
|
||||
|
||||
streamSettings := &internet.MemoryStreamConfig{
|
||||
listen, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{
|
||||
ProtocolName: "splithttp",
|
||||
ProtocolSettings: &Config{
|
||||
Path: "shs",
|
||||
Path: "/sh",
|
||||
AllowH2C: true,
|
||||
},
|
||||
}
|
||||
listen, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) {
|
||||
go func() {
|
||||
_ = conn.Close()
|
||||
}()
|
||||
}, 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
|
||||
}
|
||||
|
||||
common.Must2(c.Write([]byte("Response")))
|
||||
}(conn)
|
||||
})
|
||||
common.Must(err)
|
||||
defer listen.Close()
|
||||
|
||||
protocols := new(http.Protocols)
|
||||
protocols.SetUnencryptedHTTP2(true)
|
||||
client := http.Client{
|
||||
Transport: &http.Transport{
|
||||
Protocols: protocols,
|
||||
},
|
||||
ctx := context.Background()
|
||||
streamSettings := &internet.MemoryStreamConfig{
|
||||
ProtocolName: "splithttp",
|
||||
ProtocolSettings: &Config{Path: "sh", AllowH2C: true},
|
||||
}
|
||||
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)
|
||||
|
||||
if resp.StatusCode != 404 {
|
||||
t.Error("Expected 404 but got:", resp.StatusCode)
|
||||
var b [1024]byte
|
||||
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 {
|
||||
t.Error("Expected h2 but got:", resp.ProtoMajor)
|
||||
common.Must(conn.Close())
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user