Skip to content
This repository has been archived by the owner on Jan 28, 2025. It is now read-only.

Commit

Permalink
splithttp: Add support for H2C and http/1.1 ALPN on server (XTLS#3465)
Browse files Browse the repository at this point in the history
* Add H2C support to server

* update comment

* Make http1.1 ALPN work on SplitHTTP client

Users that encounter protocol version issues will likely try to set the
ALPN explicitly. In that case we should simply grant their wish, because
the intent is obvious.
  • Loading branch information
mmmray authored Jun 23, 2024
1 parent 74d233d commit ee2000f
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 9 deletions.
5 changes: 3 additions & 2 deletions transport/internet/splithttp/dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in
}

tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
isH2 := tlsConfig != nil && !(len(tlsConfig.NextProtocol) == 1 && tlsConfig.NextProtocol[0] == "http/1.1")

var gotlsConfig *gotls.Config

Expand Down Expand Up @@ -88,7 +89,7 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in
var uploadTransport http.RoundTripper
var downloadTransport http.RoundTripper

if tlsConfig != nil {
if isH2 {
downloadTransport = &http2.Transport{
DialTLSContext: func(ctxInner context.Context, network string, addr string, cfg *gotls.Config) (net.Conn, error) {
return dialContext(ctxInner)
Expand Down Expand Up @@ -121,7 +122,7 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in
upload: &http.Client{
Transport: uploadTransport,
},
isH2: tlsConfig != nil,
isH2: isH2,
uploadRawPool: &sync.Pool{},
dialUploadConn: dialContext,
}
Expand Down
21 changes: 14 additions & 7 deletions transport/internet/splithttp/hub.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/stat"
v2tls "github.com/xtls/xray-core/transport/internet/tls"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)

type requestHandler struct {
Expand Down Expand Up @@ -268,16 +270,21 @@ func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSet
}
}

handler := &requestHandler{
host: shSettings.Host,
path: shSettings.GetNormalizedPath(),
ln: l,
sessions: sync.Map{},
localAddr: localAddr,
}

// h2cHandler can handle both plaintext HTTP/1.1 and h2c
h2cHandler := h2c.NewHandler(handler, &http2.Server{})

l.listener = listener

l.server = http.Server{
Handler: &requestHandler{
host: shSettings.Host,
path: shSettings.GetNormalizedPath(),
ln: l,
sessions: sync.Map{},
localAddr: localAddr,
},
Handler: h2cHandler,
ReadHeaderTimeout: time.Second * 4,
MaxHeaderBytes: 8192,
}
Expand Down
51 changes: 51 additions & 0 deletions transport/internet/splithttp/splithttp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package splithttp_test

import (
"context"
gotls "crypto/tls"
"fmt"
gonet "net"
"net/http"
"runtime"
"testing"
"time"
Expand All @@ -15,6 +18,7 @@ import (
. "github.com/xtls/xray-core/transport/internet/splithttp"
"github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
"golang.org/x/net/http2"
)

func Test_listenSHAndDial(t *testing.T) {
Expand Down Expand Up @@ -152,3 +156,50 @@ func Test_listenSHAndDial_TLS(t *testing.T) {
t.Error("end: ", end, " start: ", start)
}
}

func Test_listenSHAndDial_H2C(t *testing.T) {
if runtime.GOARCH == "arm64" {
return
}

listenPort := tcp.PickPort()

streamSettings := &internet.MemoryStreamConfig{
ProtocolName: "splithttp",
ProtocolSettings: &Config{
Path: "shs",
},
}
listen, err := ListenSH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) {
go func() {
_ = conn.Close()
}()
})
common.Must(err)
defer listen.Close()

client := http.Client{
Transport: &http2.Transport{
// So http2.Transport doesn't complain the URL scheme isn't 'https'
AllowHTTP: true,
// even with AllowHTTP, http2.Transport will attempt to establish
// the connection using DialTLSContext. Disable TLS with custom
// dial context.
DialTLSContext: func(ctx context.Context, network, addr string, cfg *gotls.Config) (gonet.Conn, error) {
var d gonet.Dialer
return d.DialContext(ctx, network, addr)
},
},
}

resp, err := client.Get("http://" + net.LocalHostIP.String() + ":" + listenPort.String())
common.Must(err)

if resp.StatusCode != 404 {
t.Error("Expected 404 but got:", resp.StatusCode)
}

if resp.ProtoMajor != 2 {
t.Error("Expected h2 but got:", resp.ProtoMajor)
}
}

0 comments on commit ee2000f

Please sign in to comment.