mirror of
https://github.com/XTLS/Xray-core.git
synced 2024-11-25 10:01:28 +00:00
SplitHTTP: Server supports HTTP/3 (#3554)
Co-authored-by: mmmray <142015632+mmmray@users.noreply.github.com>
This commit is contained in:
parent
d8994b7603
commit
a0040f13dd
|
@ -11,6 +11,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/quic-go/quic-go"
|
||||||
|
"github.com/quic-go/quic-go/http3"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"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"
|
||||||
|
@ -234,9 +236,12 @@ func (c *httpResponseBodyWriter) Close() error {
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
server http.Server
|
server http.Server
|
||||||
|
h3server *http3.Server
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
|
h3listener *quic.EarlyListener
|
||||||
config *Config
|
config *Config
|
||||||
addConn internet.ConnHandler
|
addConn internet.ConnHandler
|
||||||
|
isH3 bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (internet.Listener, error) {
|
func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (internet.Listener, error) {
|
||||||
|
@ -253,6 +258,17 @@ func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSet
|
||||||
var listener net.Listener
|
var listener net.Listener
|
||||||
var err error
|
var err error
|
||||||
var localAddr = gonet.TCPAddr{}
|
var localAddr = gonet.TCPAddr{}
|
||||||
|
handler := &requestHandler{
|
||||||
|
host: shSettings.Host,
|
||||||
|
path: shSettings.GetNormalizedPath(),
|
||||||
|
ln: l,
|
||||||
|
sessionMu: &sync.Mutex{},
|
||||||
|
sessions: sync.Map{},
|
||||||
|
localAddr: localAddr,
|
||||||
|
}
|
||||||
|
tlsConfig := getTLSConfig(streamSettings)
|
||||||
|
l.isH3 = len(tlsConfig.NextProtos) == 1 && tlsConfig.NextProtos[0] == "h3"
|
||||||
|
|
||||||
|
|
||||||
if port == net.Port(0) { // unix
|
if port == net.Port(0) { // unix
|
||||||
listener, err = internet.ListenSystem(ctx, &net.UnixAddr{
|
listener, err = internet.ListenSystem(ctx, &net.UnixAddr{
|
||||||
|
@ -263,6 +279,29 @@ func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSet
|
||||||
return nil, errors.New("failed to listen unix domain socket(for SH) on ", address).Base(err)
|
return nil, errors.New("failed to listen unix domain socket(for SH) on ", address).Base(err)
|
||||||
}
|
}
|
||||||
errors.LogInfo(ctx, "listening unix domain socket(for SH) on ", address)
|
errors.LogInfo(ctx, "listening unix domain socket(for SH) on ", address)
|
||||||
|
} else if l.isH3 { // quic
|
||||||
|
Conn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{
|
||||||
|
IP: address.IP(),
|
||||||
|
Port: int(port),
|
||||||
|
}, streamSettings.SocketSettings)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to listen UDP(for SH3) on ", address, ":", port).Base(err)
|
||||||
|
}
|
||||||
|
h3listener, err := quic.ListenEarly(Conn,tlsConfig, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to listen QUIC(for SH3) on ", address, ":", port).Base(err)
|
||||||
|
}
|
||||||
|
l.h3listener = h3listener
|
||||||
|
errors.LogInfo(ctx, "listening QUIC(for SH3) on ", address, ":", port)
|
||||||
|
|
||||||
|
l.h3server = &http3.Server{
|
||||||
|
Handler: handler,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
if err := l.h3server.ServeListener(l.h3listener); err != nil {
|
||||||
|
errors.LogWarningInner(ctx, err, "failed to serve http3 for splithttp")
|
||||||
|
}
|
||||||
|
}()
|
||||||
} else { // tcp
|
} else { // tcp
|
||||||
localAddr = gonet.TCPAddr{
|
localAddr = gonet.TCPAddr{
|
||||||
IP: address.IP(),
|
IP: address.IP(),
|
||||||
|
@ -275,40 +314,28 @@ func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSet
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to listen TCP(for SH) on ", address, ":", port).Base(err)
|
return nil, errors.New("failed to listen TCP(for SH) on ", address, ":", port).Base(err)
|
||||||
}
|
}
|
||||||
|
l.listener = listener
|
||||||
errors.LogInfo(ctx, "listening TCP(for SH) on ", address, ":", port)
|
errors.LogInfo(ctx, "listening TCP(for SH) on ", address, ":", port)
|
||||||
}
|
|
||||||
|
|
||||||
if config := v2tls.ConfigFromStreamSettings(streamSettings); config != nil {
|
|
||||||
if tlsConfig := config.GetTLSConfig(); tlsConfig != nil {
|
|
||||||
listener = tls.NewListener(listener, tlsConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handler := &requestHandler{
|
|
||||||
host: shSettings.Host,
|
|
||||||
path: shSettings.GetNormalizedPath(),
|
|
||||||
ln: l,
|
|
||||||
sessionMu: &sync.Mutex{},
|
|
||||||
sessions: sync.Map{},
|
|
||||||
localAddr: localAddr,
|
|
||||||
}
|
|
||||||
|
|
||||||
// h2cHandler can handle both plaintext HTTP/1.1 and h2c
|
// h2cHandler can handle both plaintext HTTP/1.1 and h2c
|
||||||
h2cHandler := h2c.NewHandler(handler, &http2.Server{})
|
h2cHandler := h2c.NewHandler(handler, &http2.Server{})
|
||||||
|
|
||||||
l.listener = listener
|
|
||||||
|
|
||||||
l.server = http.Server{
|
l.server = http.Server{
|
||||||
Handler: h2cHandler,
|
Handler: h2cHandler,
|
||||||
ReadHeaderTimeout: time.Second * 4,
|
ReadHeaderTimeout: time.Second * 4,
|
||||||
MaxHeaderBytes: 8192,
|
MaxHeaderBytes: 8192,
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := l.server.Serve(l.listener); err != nil {
|
if err := l.server.Serve(l.listener); err != nil {
|
||||||
errors.LogWarningInner(ctx, err, "failed to serve http for splithttp")
|
errors.LogWarningInner(ctx, err, "failed to serve http for splithttp")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
}
|
||||||
|
l.listener = listener
|
||||||
|
if config := v2tls.ConfigFromStreamSettings(streamSettings); config != nil {
|
||||||
|
if tlsConfig := config.GetTLSConfig(); tlsConfig != nil {
|
||||||
|
listener = tls.NewListener(listener, tlsConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return l, err
|
return l, err
|
||||||
}
|
}
|
||||||
|
@ -320,9 +347,22 @@ func (ln *Listener) Addr() net.Addr {
|
||||||
|
|
||||||
// Close implements net.Listener.Close().
|
// Close implements net.Listener.Close().
|
||||||
func (ln *Listener) Close() error {
|
func (ln *Listener) Close() error {
|
||||||
|
if ln.h3server != nil {
|
||||||
|
if err := ln.h3server.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if ln.listener != nil {
|
||||||
return ln.listener.Close()
|
return ln.listener.Close()
|
||||||
|
}
|
||||||
|
return errors.New("listener does not have an HTTP/3 server or a net.listener")
|
||||||
|
}
|
||||||
|
func getTLSConfig(streamSettings *internet.MemoryStreamConfig) *tls.Config {
|
||||||
|
config := v2tls.ConfigFromStreamSettings(streamSettings)
|
||||||
|
if config == nil {
|
||||||
|
return &tls.Config{}
|
||||||
|
}
|
||||||
|
return config.GetTLSConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
common.Must(internet.RegisterTransportListener(protocolName, ListenSH))
|
common.Must(internet.RegisterTransportListener(protocolName, ListenSH))
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/protocol/tls/cert"
|
"github.com/xtls/xray-core/common/protocol/tls/cert"
|
||||||
"github.com/xtls/xray-core/testing/servers/tcp"
|
"github.com/xtls/xray-core/testing/servers/tcp"
|
||||||
|
"github.com/xtls/xray-core/testing/servers/udp"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
. "github.com/xtls/xray-core/transport/internet/splithttp"
|
. "github.com/xtls/xray-core/transport/internet/splithttp"
|
||||||
"github.com/xtls/xray-core/transport/internet/stat"
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
|
@ -204,3 +205,42 @@ func Test_listenSHAndDial_H2C(t *testing.T) {
|
||||||
t.Error("Expected h2 but got:", resp.ProtoMajor)
|
t.Error("Expected h2 but got:", resp.ProtoMajor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_listenSHAndDial_QUIC(t *testing.T) {
|
||||||
|
if runtime.GOARCH == "arm64" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
listenPort := udp.PickPort()
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
streamSettings := &internet.MemoryStreamConfig{
|
||||||
|
ProtocolName: "splithttp",
|
||||||
|
ProtocolSettings: &Config{
|
||||||
|
Path: "shs",
|
||||||
|
},
|
||||||
|
SecurityType: "tls",
|
||||||
|
SecuritySettings: &tls.Config{
|
||||||
|
AllowInsecure: true,
|
||||||
|
Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil, cert.CommonName("localhost")))},
|
||||||
|
NextProtocol: []string{"h3"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
listen, err := ListenSH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) {
|
||||||
|
go func() {
|
||||||
|
_ = conn.Close()
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
common.Must(err)
|
||||||
|
defer listen.Close()
|
||||||
|
|
||||||
|
conn, err := Dial(context.Background(), net.UDPDestination(net.DomainAddress("localhost"), listenPort), streamSettings)
|
||||||
|
common.Must(err)
|
||||||
|
_ = conn.Close()
|
||||||
|
|
||||||
|
end := time.Now()
|
||||||
|
if !end.Before(start.Add(time.Second * 5)) {
|
||||||
|
t.Error("end: ", end, " start: ", start)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue