Skip to content

Commit b53ae0a

Browse files
committed
h2c
1 parent d2758a0 commit b53ae0a

6 files changed

Lines changed: 186 additions & 33 deletions

File tree

infra/conf/transport_internet.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ type SplitHTTPConfig struct {
243243
ScMaxBufferedPosts int64 `json:"scMaxBufferedPosts"`
244244
ScStreamUpServerSecs Int32Range `json:"scStreamUpServerSecs"`
245245
ServerMaxHeaderBytes int32 `json:"serverMaxHeaderBytes"`
246+
AllowH2C bool `json:"allowH2C"`
246247
Xmux XmuxConfig `json:"xmux"`
247248
DownloadSettings *StreamConfig `json:"downloadSettings"`
248249
Extra json.RawMessage `json:"extra"`
@@ -426,6 +427,7 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
426427
ScMaxBufferedPosts: c.ScMaxBufferedPosts,
427428
ScStreamUpServerSecs: newRangeConfig(c.ScStreamUpServerSecs),
428429
ServerMaxHeaderBytes: c.ServerMaxHeaderBytes,
430+
AllowH2C: c.AllowH2C,
429431
Xmux: &splithttp.XmuxConfig{
430432
MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency),
431433
MaxConnections: newRangeConfig(c.Xmux.MaxConnections),

testing/scenarios/vless_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/xtls/xray-core/testing/servers/tcp"
2626
"github.com/xtls/xray-core/transport/internet"
2727
"github.com/xtls/xray-core/transport/internet/reality"
28+
"github.com/xtls/xray-core/transport/internet/splithttp"
2829
transtcp "github.com/xtls/xray-core/transport/internet/tcp"
2930
"github.com/xtls/xray-core/transport/internet/tls"
3031
"golang.org/x/sync/errgroup"
@@ -647,3 +648,121 @@ func TestVlessRealityFingerprints(t *testing.T) {
647648
}
648649
wg.Wait()
649650
}
651+
652+
func TestVlessXHTTPH2C(t *testing.T) {
653+
tcpServer := tcp.Server{
654+
MsgProcessor: xor,
655+
}
656+
dest, err := tcpServer.Start()
657+
common.Must(err)
658+
defer tcpServer.Close()
659+
660+
userID := protocol.NewID(uuid.New())
661+
serverPort := tcp.PickPort()
662+
663+
xhttpSettings := serial.ToTypedMessage(&splithttp.Config{
664+
Path: "/h2c",
665+
AllowH2C: true,
666+
})
667+
668+
serverConfig := &core.Config{
669+
App: []*serial.TypedMessage{
670+
serial.ToTypedMessage(&log.Config{
671+
ErrorLogLevel: clog.Severity_Debug,
672+
ErrorLogType: log.LogType_Console,
673+
}),
674+
},
675+
Inbound: []*core.InboundHandlerConfig{
676+
{
677+
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
678+
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
679+
Listen: net.NewIPOrDomain(net.LocalHostIP),
680+
StreamSettings: &internet.StreamConfig{
681+
ProtocolName: "splithttp",
682+
TransportSettings: []*internet.TransportConfig{
683+
{
684+
ProtocolName: "splithttp",
685+
Settings: xhttpSettings,
686+
},
687+
},
688+
},
689+
}),
690+
ProxySettings: serial.ToTypedMessage(&inbound.Config{
691+
Clients: []*protocol.User{
692+
{
693+
Account: serial.ToTypedMessage(&vless.Account{
694+
Id: userID.String(),
695+
}),
696+
},
697+
},
698+
}),
699+
},
700+
},
701+
Outbound: []*core.OutboundHandlerConfig{
702+
{
703+
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
704+
},
705+
},
706+
}
707+
708+
clientPort := tcp.PickPort()
709+
clientConfig := &core.Config{
710+
App: []*serial.TypedMessage{
711+
serial.ToTypedMessage(&log.Config{
712+
ErrorLogLevel: clog.Severity_Debug,
713+
ErrorLogType: log.LogType_Console,
714+
}),
715+
},
716+
Inbound: []*core.InboundHandlerConfig{
717+
{
718+
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
719+
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},
720+
Listen: net.NewIPOrDomain(net.LocalHostIP),
721+
}),
722+
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
723+
Address: net.NewIPOrDomain(dest.Address),
724+
Port: uint32(dest.Port),
725+
Networks: []net.Network{net.Network_TCP},
726+
}),
727+
},
728+
},
729+
Outbound: []*core.OutboundHandlerConfig{
730+
{
731+
ProxySettings: serial.ToTypedMessage(&outbound.Config{
732+
Vnext: &protocol.ServerEndpoint{
733+
Address: net.NewIPOrDomain(net.LocalHostIP),
734+
Port: uint32(serverPort),
735+
User: &protocol.User{
736+
Account: serial.ToTypedMessage(&vless.Account{
737+
Id: userID.String(),
738+
}),
739+
},
740+
},
741+
}),
742+
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
743+
StreamSettings: &internet.StreamConfig{
744+
ProtocolName: "splithttp",
745+
TransportSettings: []*internet.TransportConfig{
746+
{
747+
ProtocolName: "splithttp",
748+
Settings: xhttpSettings,
749+
},
750+
},
751+
},
752+
}),
753+
},
754+
},
755+
}
756+
757+
servers, err := InitializeServerConfigs(serverConfig, clientConfig)
758+
common.Must(err)
759+
defer CloseAllServers(servers)
760+
761+
var errg errgroup.Group
762+
for range 3 {
763+
errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))
764+
}
765+
if err := errg.Wait(); err != nil {
766+
t.Error(err)
767+
}
768+
}

transport/internet/splithttp/config.pb.go

Lines changed: 11 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

transport/internet/splithttp/config.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,5 @@ message Config {
5050
string uplinkDataKey = 25;
5151
RangeConfig uplinkChunkSize = 26;
5252
int32 serverMaxHeaderBytes = 27;
53+
bool allow_h2c = 28;
5354
}

transport/internet/splithttp/dialer.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,14 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in
7979
return xmuxClient.XmuxConn.(DialerClient), xmuxClient
8080
}
8181

82-
func decideHTTPVersion(tlsConfig *tls.Config, realityConfig *reality.Config) string {
82+
func decideHTTPVersion(tlsConfig *tls.Config, realityConfig *reality.Config, allowH2C bool) string {
8383
if realityConfig != nil {
8484
return "2"
8585
}
8686
if tlsConfig == nil {
87+
if allowH2C {
88+
return "2"
89+
}
8790
return "1.1"
8891
}
8992
if len(tlsConfig.NextProtocol) != 1 {
@@ -101,8 +104,9 @@ func decideHTTPVersion(tlsConfig *tls.Config, realityConfig *reality.Config) str
101104
func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStreamConfig) DialerClient {
102105
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
103106
realityConfig := reality.ConfigFromStreamSettings(streamSettings)
107+
transportConfig := streamSettings.ProtocolSettings.(*Config)
104108

105-
httpVersion := decideHTTPVersion(tlsConfig, realityConfig)
109+
httpVersion := decideHTTPVersion(tlsConfig, realityConfig, transportConfig.AllowH2C)
106110
if httpVersion == "3" {
107111
dest.Network = net.Network_UDP // better to keep this line
108112
}
@@ -113,8 +117,6 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
113117
gotlsConfig = tlsConfig.GetTLSConfig(tls.WithDestination(dest))
114118
}
115119

116-
transportConfig := streamSettings.ProtocolSettings.(*Config)
117-
118120
dialContext := func(ctxInner context.Context) (net.Conn, error) {
119121
conn, err := internet.DialSystem(ctxInner, dest, streamSettings.SocketSettings)
120122
if err != nil {
@@ -312,6 +314,7 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
312314
},
313315
IdleConnTimeout: net.ConnIdleTimeout,
314316
ReadIdleTimeout: keepAlivePeriod,
317+
AllowHTTP: transportConfig.AllowH2C,
315318
}
316319
} else {
317320
httpDialContext := func(ctxInner context.Context, network string, addr string) (net.Conn, error) {
@@ -348,13 +351,12 @@ func init() {
348351
func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {
349352
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
350353
realityConfig := reality.ConfigFromStreamSettings(streamSettings)
354+
transportConfiguration := streamSettings.ProtocolSettings.(*Config)
351355

352-
httpVersion := decideHTTPVersion(tlsConfig, realityConfig)
356+
httpVersion := decideHTTPVersion(tlsConfig, realityConfig, transportConfiguration.AllowH2C)
353357
if httpVersion == "3" {
354358
dest.Network = net.Network_UDP
355359
}
356-
357-
transportConfiguration := streamSettings.ProtocolSettings.(*Config)
358360
var requestURL url.URL
359361

360362
if tlsConfig != nil || realityConfig != nil {
@@ -413,7 +415,8 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
413415
dest2 := *memory2.Destination // just panic
414416
tlsConfig2 := tls.ConfigFromStreamSettings(memory2)
415417
realityConfig2 := reality.ConfigFromStreamSettings(memory2)
416-
httpVersion2 := decideHTTPVersion(tlsConfig2, realityConfig2)
418+
config2 := memory2.ProtocolSettings.(*Config)
419+
httpVersion2 := decideHTTPVersion(tlsConfig2, realityConfig2, config2.AllowH2C)
417420
if httpVersion2 == "3" {
418421
dest2.Network = net.Network_UDP
419422
}
@@ -422,7 +425,6 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
422425
} else {
423426
requestURL2.Scheme = "http"
424427
}
425-
config2 := memory2.ProtocolSettings.(*Config)
426428
requestURL2.Host = config2.Host
427429
if requestURL2.Host == "" && tlsConfig2 != nil {
428430
requestURL2.Host = tlsConfig2.ServerName

transport/internet/splithttp/splithttp_test.go

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"crypto/rand"
77
"fmt"
88
"io"
9-
"net/http"
109
"runtime"
1110
"testing"
1211
"time"
@@ -186,39 +185,60 @@ func Test_ListenXHAndDial_H2C(t *testing.T) {
186185
}
187186

188187
listenPort := tcp.PickPort()
189-
190-
streamSettings := &internet.MemoryStreamConfig{
188+
listen, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{
191189
ProtocolName: "splithttp",
192190
ProtocolSettings: &Config{
193-
Path: "shs",
191+
Path: "/sh",
192+
AllowH2C: true,
194193
},
195-
}
196-
listen, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) {
197-
go func() {
198-
_ = conn.Close()
199-
}()
194+
}, func(conn stat.Connection) {
195+
go func(c stat.Connection) {
196+
defer c.Close()
197+
198+
var b [1024]byte
199+
c.SetReadDeadline(time.Now().Add(2 * time.Second))
200+
_, err := c.Read(b[:])
201+
if err != nil {
202+
return
203+
}
204+
205+
common.Must2(c.Write([]byte("Response")))
206+
}(conn)
200207
})
201208
common.Must(err)
202-
defer listen.Close()
203-
204-
protocols := new(http.Protocols)
205-
protocols.SetUnencryptedHTTP2(true)
206-
client := http.Client{
207-
Transport: &http.Transport{
208-
Protocols: protocols,
209-
},
209+
ctx := context.Background()
210+
streamSettings := &internet.MemoryStreamConfig{
211+
ProtocolName: "splithttp",
212+
ProtocolSettings: &Config{Path: "sh", AllowH2C: true},
210213
}
214+
conn, err := Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), listenPort), streamSettings)
211215

212-
resp, err := client.Get("http://" + net.LocalHostIP.String() + ":" + listenPort.String())
216+
common.Must(err)
217+
_, err = conn.Write([]byte("Test connection 1"))
213218
common.Must(err)
214219

215-
if resp.StatusCode != 404 {
216-
t.Error("Expected 404 but got:", resp.StatusCode)
220+
var b [1024]byte
221+
fmt.Println("test2")
222+
n, _ := io.ReadFull(conn, b[:])
223+
fmt.Println("string is", n)
224+
if string(b[:n]) != "Response" {
225+
t.Error("response: ", string(b[:n]))
217226
}
218227

219-
if resp.ProtoMajor != 2 {
220-
t.Error("Expected h2 but got:", resp.ProtoMajor)
228+
common.Must(conn.Close())
229+
conn, err = Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), listenPort), streamSettings)
230+
231+
common.Must(err)
232+
_, err = conn.Write([]byte("Test connection 2"))
233+
common.Must(err)
234+
n, _ = io.ReadFull(conn, b[:])
235+
common.Must(err)
236+
if string(b[:n]) != "Response" {
237+
t.Error("response: ", string(b[:n]))
221238
}
239+
common.Must(conn.Close())
240+
241+
common.Must(listen.Close())
222242
}
223243

224244
func Test_ListenXHAndDial_QUIC(t *testing.T) {

0 commit comments

Comments
 (0)