Explorar o código

XHTTP XMUX: Add `hMaxRequestTimes` and `hKeepAlivePeriod` (#4163)

Fixes https://github.com/XTLS/Xray-core/discussions/4113#discussioncomment-11492833
RPRX hai 10 meses
pai
achega
73e0d4a666

+ 24 - 23
infra/conf/transport_internet.go

@@ -228,22 +228,23 @@ type SplitHTTPConfig struct {
 	NoSSEHeader          bool              `json:"noSSEHeader"`
 	ScMaxEachPostBytes   Int32Range        `json:"scMaxEachPostBytes"`
 	ScMinPostsIntervalMs Int32Range        `json:"scMinPostsIntervalMs"`
-	ScMaxBufferedPosts   int64             `json:"scMaxConcurrentPosts"`
-	KeepAlivePeriod      int64             `json:"keepAlivePeriod"`
-	Xmux                 Xmux              `json:"xmux"`
+	ScMaxBufferedPosts   int64             `json:"scMaxBufferedPosts"`
+	Xmux                 XmuxConfig        `json:"xmux"`
 	DownloadSettings     *StreamConfig     `json:"downloadSettings"`
 	Extra                json.RawMessage   `json:"extra"`
 }
 
-type Xmux struct {
-	MaxConcurrency Int32Range `json:"maxConcurrency"`
-	MaxConnections Int32Range `json:"maxConnections"`
-	CMaxReuseTimes Int32Range `json:"cMaxReuseTimes"`
-	CMaxLifetimeMs Int32Range `json:"cMaxLifetimeMs"`
+type XmuxConfig struct {
+	MaxConcurrency   Int32Range `json:"maxConcurrency"`
+	MaxConnections   Int32Range `json:"maxConnections"`
+	CMaxReuseTimes   Int32Range `json:"cMaxReuseTimes"`
+	CMaxLifetimeMs   Int32Range `json:"cMaxLifetimeMs"`
+	HMaxRequestTimes Int32Range `json:"hMaxRequestTimes"`
+	HKeepAlivePeriod int64      `json:"hKeepAlivePeriod"`
 }
 
-func splithttpNewRandRangeConfig(input Int32Range) *splithttp.RandRangeConfig {
-	return &splithttp.RandRangeConfig{
+func newRangeConfig(input Int32Range) *splithttp.RangeConfig {
+	return &splithttp.RangeConfig{
 		From: input.From,
 		To:   input.To,
 	}
@@ -281,14 +282,13 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
 	if c.Xmux.MaxConnections.To > 0 && c.Xmux.MaxConcurrency.To > 0 {
 		return nil, errors.New("maxConnections cannot be specified together with maxConcurrency")
 	}
-	if c.Xmux.MaxConcurrency.To == 0 &&
-		c.Xmux.MaxConnections.To == 0 &&
-		c.Xmux.CMaxReuseTimes.To == 0 &&
-		c.Xmux.CMaxLifetimeMs.To == 0 {
+	if c.Xmux == (XmuxConfig{}) {
 		c.Xmux.MaxConcurrency.From = 16
 		c.Xmux.MaxConcurrency.To = 32
 		c.Xmux.CMaxReuseTimes.From = 64
 		c.Xmux.CMaxReuseTimes.To = 128
+		c.Xmux.HMaxRequestTimes.From = 800
+		c.Xmux.HMaxRequestTimes.To = 900
 	}
 
 	config := &splithttp.Config{
@@ -296,18 +296,19 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
 		Path:                 c.Path,
 		Mode:                 c.Mode,
 		Headers:              c.Headers,
-		XPaddingBytes:        splithttpNewRandRangeConfig(c.XPaddingBytes),
+		XPaddingBytes:        newRangeConfig(c.XPaddingBytes),
 		NoGRPCHeader:         c.NoGRPCHeader,
 		NoSSEHeader:          c.NoSSEHeader,
-		ScMaxEachPostBytes:   splithttpNewRandRangeConfig(c.ScMaxEachPostBytes),
-		ScMinPostsIntervalMs: splithttpNewRandRangeConfig(c.ScMinPostsIntervalMs),
+		ScMaxEachPostBytes:   newRangeConfig(c.ScMaxEachPostBytes),
+		ScMinPostsIntervalMs: newRangeConfig(c.ScMinPostsIntervalMs),
 		ScMaxBufferedPosts:   c.ScMaxBufferedPosts,
-		KeepAlivePeriod:      c.KeepAlivePeriod,
-		Xmux: &splithttp.Multiplexing{
-			MaxConcurrency: splithttpNewRandRangeConfig(c.Xmux.MaxConcurrency),
-			MaxConnections: splithttpNewRandRangeConfig(c.Xmux.MaxConnections),
-			CMaxReuseTimes: splithttpNewRandRangeConfig(c.Xmux.CMaxReuseTimes),
-			CMaxLifetimeMs: splithttpNewRandRangeConfig(c.Xmux.CMaxLifetimeMs),
+		Xmux: &splithttp.XmuxConfig{
+			MaxConcurrency:   newRangeConfig(c.Xmux.MaxConcurrency),
+			MaxConnections:   newRangeConfig(c.Xmux.MaxConnections),
+			CMaxReuseTimes:   newRangeConfig(c.Xmux.CMaxReuseTimes),
+			CMaxLifetimeMs:   newRangeConfig(c.Xmux.CMaxLifetimeMs),
+			HMaxRequestTimes: newRangeConfig(c.Xmux.HMaxRequestTimes),
+			HKeepAlivePeriod: c.Xmux.HKeepAlivePeriod,
 		},
 	}
 

+ 4 - 0
transport/internet/splithttp/browser_client.go

@@ -13,6 +13,10 @@ import (
 // has no fields because everything is global state :O)
 type BrowserDialerClient struct{}
 
+func (c *BrowserDialerClient) IsClosed() bool {
+	panic("not implemented yet")
+}
+
 func (c *BrowserDialerClient) Open(ctx context.Context, pureURL string) (io.WriteCloser, io.ReadCloser) {
 	panic("not implemented yet")
 }

+ 21 - 1
transport/internet/splithttp/client.go

@@ -18,6 +18,8 @@ import (
 
 // interface to abstract between use of browser dialer, vs net/http
 type DialerClient interface {
+	IsClosed() bool
+
 	// (ctx, baseURL, payload) -> err
 	// baseURL already contains sessionId and seq
 	SendUploadRequest(context.Context, string, io.ReadWriteCloser, int64) error
@@ -39,12 +41,17 @@ type DialerClient interface {
 type DefaultDialerClient struct {
 	transportConfig *Config
 	client          *http.Client
+	closed          bool
 	httpVersion     string
 	// pool of net.Conn, created using dialUploadConn
 	uploadRawPool  *sync.Pool
 	dialUploadConn func(ctxInner context.Context) (net.Conn, error)
 }
 
+func (c *DefaultDialerClient) IsClosed() bool {
+	return c.closed
+}
+
 func (c *DefaultDialerClient) Open(ctx context.Context, pureURL string) (io.WriteCloser, io.ReadCloser) {
 	reader, writer := io.Pipe()
 	req, _ := http.NewRequestWithContext(ctx, "POST", pureURL, reader)
@@ -59,6 +66,8 @@ func (c *DefaultDialerClient) Open(ctx context.Context, pureURL string) (io.Writ
 			if err != nil {
 				errors.LogInfoInner(ctx, err, "failed to open ", pureURL)
 			} else {
+				// c.closed = true
+				response.Body.Close()
 				errors.LogInfo(ctx, "unexpected status ", response.StatusCode)
 			}
 			wrc.Close()
@@ -76,7 +85,14 @@ func (c *DefaultDialerClient) OpenUpload(ctx context.Context, baseURL string) io
 	if !c.transportConfig.NoGRPCHeader {
 		req.Header.Set("Content-Type", "application/grpc")
 	}
-	go c.client.Do(req)
+	go func() {
+		if resp, err := c.client.Do(req); err == nil {
+			if resp.StatusCode != 200 {
+				// c.closed = true
+			}
+			resp.Body.Close()
+		}
+	}()
 	return writer
 }
 
@@ -130,6 +146,7 @@ func (c *DefaultDialerClient) OpenDownload(ctx context.Context, baseURL string)
 		}
 
 		if response.StatusCode != 200 {
+			// c.closed = true
 			response.Body.Close()
 			errors.LogInfo(ctx, "invalid status code on download:", response.Status)
 			gotDownResponse.Close()
@@ -180,6 +197,7 @@ func (c *DefaultDialerClient) SendUploadRequest(ctx context.Context, url string,
 		defer resp.Body.Close()
 
 		if resp.StatusCode != 200 {
+			// c.closed = true
 			return errors.New("bad status code:", resp.Status)
 		}
 	} else {
@@ -214,6 +232,8 @@ func (c *DefaultDialerClient) SendUploadRequest(ctx context.Context, url string,
 						return fmt.Errorf("error while reading response: %s", err.Error())
 					}
 					if resp.StatusCode != 200 {
+						// c.closed = true
+						// resp.Body.Close() // I'm not sure
 						return fmt.Errorf("got non-200 error response code: %d", resp.StatusCode)
 					}
 				}

+ 29 - 18
transport/internet/splithttp/config.go

@@ -37,7 +37,7 @@ func (c *Config) GetNormalizedQuery() string {
 		query += "&"
 	}
 
-	paddingLen := c.GetNormalizedXPaddingBytes().roll()
+	paddingLen := c.GetNormalizedXPaddingBytes().rand()
 	if paddingLen > 0 {
 		query += "x_padding=" + strings.Repeat("0", int(paddingLen))
 	}
@@ -58,7 +58,7 @@ func (c *Config) WriteResponseHeader(writer http.ResponseWriter) {
 	// CORS headers for the browser dialer
 	writer.Header().Set("Access-Control-Allow-Origin", "*")
 	writer.Header().Set("Access-Control-Allow-Methods", "GET, POST")
-	paddingLen := c.GetNormalizedXPaddingBytes().roll()
+	paddingLen := c.GetNormalizedXPaddingBytes().rand()
 	if paddingLen > 0 {
 		writer.Header().Set("X-Padding", strings.Repeat("0", int(paddingLen)))
 	}
@@ -72,9 +72,9 @@ func (c *Config) GetNormalizedScMaxBufferedPosts() int {
 	return int(c.ScMaxBufferedPosts)
 }
 
-func (c *Config) GetNormalizedScMaxEachPostBytes() RandRangeConfig {
+func (c *Config) GetNormalizedScMaxEachPostBytes() RangeConfig {
 	if c.ScMaxEachPostBytes == nil || c.ScMaxEachPostBytes.To == 0 {
-		return RandRangeConfig{
+		return RangeConfig{
 			From: 1000000,
 			To:   1000000,
 		}
@@ -83,9 +83,9 @@ func (c *Config) GetNormalizedScMaxEachPostBytes() RandRangeConfig {
 	return *c.ScMaxEachPostBytes
 }
 
-func (c *Config) GetNormalizedScMinPostsIntervalMs() RandRangeConfig {
+func (c *Config) GetNormalizedScMinPostsIntervalMs() RangeConfig {
 	if c.ScMinPostsIntervalMs == nil || c.ScMinPostsIntervalMs.To == 0 {
-		return RandRangeConfig{
+		return RangeConfig{
 			From: 30,
 			To:   30,
 		}
@@ -94,9 +94,9 @@ func (c *Config) GetNormalizedScMinPostsIntervalMs() RandRangeConfig {
 	return *c.ScMinPostsIntervalMs
 }
 
-func (c *Config) GetNormalizedXPaddingBytes() RandRangeConfig {
+func (c *Config) GetNormalizedXPaddingBytes() RangeConfig {
 	if c.XPaddingBytes == nil || c.XPaddingBytes.To == 0 {
-		return RandRangeConfig{
+		return RangeConfig{
 			From: 100,
 			To:   1000,
 		}
@@ -105,9 +105,20 @@ func (c *Config) GetNormalizedXPaddingBytes() RandRangeConfig {
 	return *c.XPaddingBytes
 }
 
-func (m *Multiplexing) GetNormalizedCMaxReuseTimes() RandRangeConfig {
+func (m *XmuxConfig) GetNormalizedCMaxRequestTimes() RangeConfig {
+	if m.HMaxRequestTimes == nil {
+		return RangeConfig{
+			From: 0,
+			To:   0,
+		}
+	}
+
+	return *m.HMaxRequestTimes
+}
+
+func (m *XmuxConfig) GetNormalizedCMaxReuseTimes() RangeConfig {
 	if m.CMaxReuseTimes == nil {
-		return RandRangeConfig{
+		return RangeConfig{
 			From: 0,
 			To:   0,
 		}
@@ -116,9 +127,9 @@ func (m *Multiplexing) GetNormalizedCMaxReuseTimes() RandRangeConfig {
 	return *m.CMaxReuseTimes
 }
 
-func (m *Multiplexing) GetNormalizedCMaxLifetimeMs() RandRangeConfig {
-	if m.CMaxLifetimeMs == nil || m.CMaxLifetimeMs.To == 0 {
-		return RandRangeConfig{
+func (m *XmuxConfig) GetNormalizedCMaxLifetimeMs() RangeConfig {
+	if m.CMaxLifetimeMs == nil {
+		return RangeConfig{
 			From: 0,
 			To:   0,
 		}
@@ -126,9 +137,9 @@ func (m *Multiplexing) GetNormalizedCMaxLifetimeMs() RandRangeConfig {
 	return *m.CMaxLifetimeMs
 }
 
-func (m *Multiplexing) GetNormalizedMaxConnections() RandRangeConfig {
+func (m *XmuxConfig) GetNormalizedMaxConnections() RangeConfig {
 	if m.MaxConnections == nil {
-		return RandRangeConfig{
+		return RangeConfig{
 			From: 0,
 			To:   0,
 		}
@@ -137,9 +148,9 @@ func (m *Multiplexing) GetNormalizedMaxConnections() RandRangeConfig {
 	return *m.MaxConnections
 }
 
-func (m *Multiplexing) GetNormalizedMaxConcurrency() RandRangeConfig {
+func (m *XmuxConfig) GetNormalizedMaxConcurrency() RangeConfig {
 	if m.MaxConcurrency == nil {
-		return RandRangeConfig{
+		return RangeConfig{
 			From: 0,
 			To:   0,
 		}
@@ -154,7 +165,7 @@ func init() {
 	}))
 }
 
-func (c RandRangeConfig) roll() int32 {
+func (c RangeConfig) rand() int32 {
 	if c.From == c.To {
 		return c.From
 	}

+ 235 - 222
transport/internet/splithttp/config.pb.go

@@ -21,40 +21,29 @@ const (
 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
 )
 
-type Config struct {
+type RangeConfig struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Host                 string                 `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
-	Path                 string                 `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
-	Mode                 string                 `protobuf:"bytes,3,opt,name=mode,proto3" json:"mode,omitempty"`
-	Headers              map[string]string      `protobuf:"bytes,4,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
-	XPaddingBytes        *RandRangeConfig       `protobuf:"bytes,5,opt,name=xPaddingBytes,proto3" json:"xPaddingBytes,omitempty"`
-	NoGRPCHeader         bool                   `protobuf:"varint,6,opt,name=noGRPCHeader,proto3" json:"noGRPCHeader,omitempty"`
-	NoSSEHeader          bool                   `protobuf:"varint,7,opt,name=noSSEHeader,proto3" json:"noSSEHeader,omitempty"`
-	ScMaxEachPostBytes   *RandRangeConfig       `protobuf:"bytes,8,opt,name=scMaxEachPostBytes,proto3" json:"scMaxEachPostBytes,omitempty"`
-	ScMinPostsIntervalMs *RandRangeConfig       `protobuf:"bytes,9,opt,name=scMinPostsIntervalMs,proto3" json:"scMinPostsIntervalMs,omitempty"`
-	ScMaxBufferedPosts   int64                  `protobuf:"varint,10,opt,name=scMaxBufferedPosts,proto3" json:"scMaxBufferedPosts,omitempty"`
-	KeepAlivePeriod      int64                  `protobuf:"varint,11,opt,name=keepAlivePeriod,proto3" json:"keepAlivePeriod,omitempty"`
-	Xmux                 *Multiplexing          `protobuf:"bytes,12,opt,name=xmux,proto3" json:"xmux,omitempty"`
-	DownloadSettings     *internet.StreamConfig `protobuf:"bytes,13,opt,name=downloadSettings,proto3" json:"downloadSettings,omitempty"`
+	From int32 `protobuf:"varint,1,opt,name=from,proto3" json:"from,omitempty"`
+	To   int32 `protobuf:"varint,2,opt,name=to,proto3" json:"to,omitempty"`
 }
 
-func (x *Config) Reset() {
-	*x = Config{}
+func (x *RangeConfig) Reset() {
+	*x = RangeConfig{}
 	mi := &file_transport_internet_splithttp_config_proto_msgTypes[0]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
 
-func (x *Config) String() string {
+func (x *RangeConfig) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*Config) ProtoMessage() {}
+func (*RangeConfig) ProtoMessage() {}
 
-func (x *Config) ProtoReflect() protoreflect.Message {
+func (x *RangeConfig) ProtoReflect() protoreflect.Message {
 	mi := &file_transport_internet_splithttp_config_proto_msgTypes[0]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -66,126 +55,144 @@ func (x *Config) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use Config.ProtoReflect.Descriptor instead.
-func (*Config) Descriptor() ([]byte, []int) {
+// Deprecated: Use RangeConfig.ProtoReflect.Descriptor instead.
+func (*RangeConfig) Descriptor() ([]byte, []int) {
 	return file_transport_internet_splithttp_config_proto_rawDescGZIP(), []int{0}
 }
 
-func (x *Config) GetHost() string {
+func (x *RangeConfig) GetFrom() int32 {
 	if x != nil {
-		return x.Host
+		return x.From
 	}
-	return ""
+	return 0
 }
 
-func (x *Config) GetPath() string {
+func (x *RangeConfig) GetTo() int32 {
 	if x != nil {
-		return x.Path
+		return x.To
 	}
-	return ""
+	return 0
 }
 
-func (x *Config) GetMode() string {
-	if x != nil {
-		return x.Mode
-	}
-	return ""
+type XmuxConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	MaxConcurrency   *RangeConfig `protobuf:"bytes,1,opt,name=maxConcurrency,proto3" json:"maxConcurrency,omitempty"`
+	MaxConnections   *RangeConfig `protobuf:"bytes,2,opt,name=maxConnections,proto3" json:"maxConnections,omitempty"`
+	CMaxReuseTimes   *RangeConfig `protobuf:"bytes,3,opt,name=cMaxReuseTimes,proto3" json:"cMaxReuseTimes,omitempty"`
+	CMaxLifetimeMs   *RangeConfig `protobuf:"bytes,4,opt,name=cMaxLifetimeMs,proto3" json:"cMaxLifetimeMs,omitempty"`
+	HMaxRequestTimes *RangeConfig `protobuf:"bytes,5,opt,name=hMaxRequestTimes,proto3" json:"hMaxRequestTimes,omitempty"`
+	HKeepAlivePeriod int64        `protobuf:"varint,6,opt,name=hKeepAlivePeriod,proto3" json:"hKeepAlivePeriod,omitempty"`
 }
 
-func (x *Config) GetHeaders() map[string]string {
-	if x != nil {
-		return x.Headers
-	}
-	return nil
+func (x *XmuxConfig) Reset() {
+	*x = XmuxConfig{}
+	mi := &file_transport_internet_splithttp_config_proto_msgTypes[1]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
-func (x *Config) GetXPaddingBytes() *RandRangeConfig {
-	if x != nil {
-		return x.XPaddingBytes
-	}
-	return nil
+func (x *XmuxConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
 }
 
-func (x *Config) GetNoGRPCHeader() bool {
+func (*XmuxConfig) ProtoMessage() {}
+
+func (x *XmuxConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_transport_internet_splithttp_config_proto_msgTypes[1]
 	if x != nil {
-		return x.NoGRPCHeader
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
 	}
-	return false
+	return mi.MessageOf(x)
 }
 
-func (x *Config) GetNoSSEHeader() bool {
-	if x != nil {
-		return x.NoSSEHeader
-	}
-	return false
+// Deprecated: Use XmuxConfig.ProtoReflect.Descriptor instead.
+func (*XmuxConfig) Descriptor() ([]byte, []int) {
+	return file_transport_internet_splithttp_config_proto_rawDescGZIP(), []int{1}
 }
 
-func (x *Config) GetScMaxEachPostBytes() *RandRangeConfig {
+func (x *XmuxConfig) GetMaxConcurrency() *RangeConfig {
 	if x != nil {
-		return x.ScMaxEachPostBytes
+		return x.MaxConcurrency
 	}
 	return nil
 }
 
-func (x *Config) GetScMinPostsIntervalMs() *RandRangeConfig {
+func (x *XmuxConfig) GetMaxConnections() *RangeConfig {
 	if x != nil {
-		return x.ScMinPostsIntervalMs
+		return x.MaxConnections
 	}
 	return nil
 }
 
-func (x *Config) GetScMaxBufferedPosts() int64 {
+func (x *XmuxConfig) GetCMaxReuseTimes() *RangeConfig {
 	if x != nil {
-		return x.ScMaxBufferedPosts
+		return x.CMaxReuseTimes
 	}
-	return 0
+	return nil
 }
 
-func (x *Config) GetKeepAlivePeriod() int64 {
+func (x *XmuxConfig) GetCMaxLifetimeMs() *RangeConfig {
 	if x != nil {
-		return x.KeepAlivePeriod
+		return x.CMaxLifetimeMs
 	}
-	return 0
+	return nil
 }
 
-func (x *Config) GetXmux() *Multiplexing {
+func (x *XmuxConfig) GetHMaxRequestTimes() *RangeConfig {
 	if x != nil {
-		return x.Xmux
+		return x.HMaxRequestTimes
 	}
 	return nil
 }
 
-func (x *Config) GetDownloadSettings() *internet.StreamConfig {
+func (x *XmuxConfig) GetHKeepAlivePeriod() int64 {
 	if x != nil {
-		return x.DownloadSettings
+		return x.HKeepAlivePeriod
 	}
-	return nil
+	return 0
 }
 
-type RandRangeConfig struct {
+type Config struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	From int32 `protobuf:"varint,1,opt,name=from,proto3" json:"from,omitempty"`
-	To   int32 `protobuf:"varint,2,opt,name=to,proto3" json:"to,omitempty"`
+	Host                 string                 `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
+	Path                 string                 `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
+	Mode                 string                 `protobuf:"bytes,3,opt,name=mode,proto3" json:"mode,omitempty"`
+	Headers              map[string]string      `protobuf:"bytes,4,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	XPaddingBytes        *RangeConfig           `protobuf:"bytes,5,opt,name=xPaddingBytes,proto3" json:"xPaddingBytes,omitempty"`
+	NoGRPCHeader         bool                   `protobuf:"varint,6,opt,name=noGRPCHeader,proto3" json:"noGRPCHeader,omitempty"`
+	NoSSEHeader          bool                   `protobuf:"varint,7,opt,name=noSSEHeader,proto3" json:"noSSEHeader,omitempty"`
+	ScMaxEachPostBytes   *RangeConfig           `protobuf:"bytes,8,opt,name=scMaxEachPostBytes,proto3" json:"scMaxEachPostBytes,omitempty"`
+	ScMinPostsIntervalMs *RangeConfig           `protobuf:"bytes,9,opt,name=scMinPostsIntervalMs,proto3" json:"scMinPostsIntervalMs,omitempty"`
+	ScMaxBufferedPosts   int64                  `protobuf:"varint,10,opt,name=scMaxBufferedPosts,proto3" json:"scMaxBufferedPosts,omitempty"`
+	Xmux                 *XmuxConfig            `protobuf:"bytes,11,opt,name=xmux,proto3" json:"xmux,omitempty"`
+	DownloadSettings     *internet.StreamConfig `protobuf:"bytes,12,opt,name=downloadSettings,proto3" json:"downloadSettings,omitempty"`
 }
 
-func (x *RandRangeConfig) Reset() {
-	*x = RandRangeConfig{}
-	mi := &file_transport_internet_splithttp_config_proto_msgTypes[1]
+func (x *Config) Reset() {
+	*x = Config{}
+	mi := &file_transport_internet_splithttp_config_proto_msgTypes[2]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
 
-func (x *RandRangeConfig) String() string {
+func (x *Config) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*RandRangeConfig) ProtoMessage() {}
+func (*Config) ProtoMessage() {}
 
-func (x *RandRangeConfig) ProtoReflect() protoreflect.Message {
-	mi := &file_transport_internet_splithttp_config_proto_msgTypes[1]
+func (x *Config) ProtoReflect() protoreflect.Message {
+	mi := &file_transport_internet_splithttp_config_proto_msgTypes[2]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -196,90 +203,91 @@ func (x *RandRangeConfig) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use RandRangeConfig.ProtoReflect.Descriptor instead.
-func (*RandRangeConfig) Descriptor() ([]byte, []int) {
-	return file_transport_internet_splithttp_config_proto_rawDescGZIP(), []int{1}
+// Deprecated: Use Config.ProtoReflect.Descriptor instead.
+func (*Config) Descriptor() ([]byte, []int) {
+	return file_transport_internet_splithttp_config_proto_rawDescGZIP(), []int{2}
 }
 
-func (x *RandRangeConfig) GetFrom() int32 {
+func (x *Config) GetHost() string {
 	if x != nil {
-		return x.From
+		return x.Host
 	}
-	return 0
+	return ""
 }
 
-func (x *RandRangeConfig) GetTo() int32 {
+func (x *Config) GetPath() string {
 	if x != nil {
-		return x.To
+		return x.Path
 	}
-	return 0
+	return ""
 }
 
-type Multiplexing struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	MaxConcurrency *RandRangeConfig `protobuf:"bytes,1,opt,name=maxConcurrency,proto3" json:"maxConcurrency,omitempty"`
-	MaxConnections *RandRangeConfig `protobuf:"bytes,2,opt,name=maxConnections,proto3" json:"maxConnections,omitempty"`
-	CMaxReuseTimes *RandRangeConfig `protobuf:"bytes,3,opt,name=cMaxReuseTimes,proto3" json:"cMaxReuseTimes,omitempty"`
-	CMaxLifetimeMs *RandRangeConfig `protobuf:"bytes,4,opt,name=cMaxLifetimeMs,proto3" json:"cMaxLifetimeMs,omitempty"`
+func (x *Config) GetMode() string {
+	if x != nil {
+		return x.Mode
+	}
+	return ""
 }
 
-func (x *Multiplexing) Reset() {
-	*x = Multiplexing{}
-	mi := &file_transport_internet_splithttp_config_proto_msgTypes[2]
-	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-	ms.StoreMessageInfo(mi)
+func (x *Config) GetHeaders() map[string]string {
+	if x != nil {
+		return x.Headers
+	}
+	return nil
 }
 
-func (x *Multiplexing) String() string {
-	return protoimpl.X.MessageStringOf(x)
+func (x *Config) GetXPaddingBytes() *RangeConfig {
+	if x != nil {
+		return x.XPaddingBytes
+	}
+	return nil
 }
 
-func (*Multiplexing) ProtoMessage() {}
-
-func (x *Multiplexing) ProtoReflect() protoreflect.Message {
-	mi := &file_transport_internet_splithttp_config_proto_msgTypes[2]
+func (x *Config) GetNoGRPCHeader() bool {
 	if x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
+		return x.NoGRPCHeader
 	}
-	return mi.MessageOf(x)
+	return false
 }
 
-// Deprecated: Use Multiplexing.ProtoReflect.Descriptor instead.
-func (*Multiplexing) Descriptor() ([]byte, []int) {
-	return file_transport_internet_splithttp_config_proto_rawDescGZIP(), []int{2}
+func (x *Config) GetNoSSEHeader() bool {
+	if x != nil {
+		return x.NoSSEHeader
+	}
+	return false
 }
 
-func (x *Multiplexing) GetMaxConcurrency() *RandRangeConfig {
+func (x *Config) GetScMaxEachPostBytes() *RangeConfig {
 	if x != nil {
-		return x.MaxConcurrency
+		return x.ScMaxEachPostBytes
 	}
 	return nil
 }
 
-func (x *Multiplexing) GetMaxConnections() *RandRangeConfig {
+func (x *Config) GetScMinPostsIntervalMs() *RangeConfig {
 	if x != nil {
-		return x.MaxConnections
+		return x.ScMinPostsIntervalMs
 	}
 	return nil
 }
 
-func (x *Multiplexing) GetCMaxReuseTimes() *RandRangeConfig {
+func (x *Config) GetScMaxBufferedPosts() int64 {
 	if x != nil {
-		return x.CMaxReuseTimes
+		return x.ScMaxBufferedPosts
+	}
+	return 0
+}
+
+func (x *Config) GetXmux() *XmuxConfig {
+	if x != nil {
+		return x.Xmux
 	}
 	return nil
 }
 
-func (x *Multiplexing) GetCMaxLifetimeMs() *RandRangeConfig {
+func (x *Config) GetDownloadSettings() *internet.StreamConfig {
 	if x != nil {
-		return x.CMaxLifetimeMs
+		return x.DownloadSettings
 	}
 	return nil
 }
@@ -294,94 +302,98 @@ var file_transport_internet_splithttp_config_proto_rawDesc = []byte{
 	0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x1a, 0x1f,
 	0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
 	0x65, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
-	0xb0, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f,
-	0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x12,
-	0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61,
-	0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x50, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72,
-	0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74,
-	0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65,
-	0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x66,
-	0x69, 0x67, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
-	0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x58, 0x0a, 0x0d, 0x78, 0x50, 0x61, 0x64,
-	0x64, 0x69, 0x6e, 0x67, 0x42, 0x79, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x32, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74,
-	0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68,
-	0x74, 0x74, 0x70, 0x2e, 0x52, 0x61, 0x6e, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e,
-	0x66, 0x69, 0x67, 0x52, 0x0d, 0x78, 0x50, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x79, 0x74,
-	0x65, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x6f, 0x47, 0x52, 0x50, 0x43, 0x48, 0x65, 0x61, 0x64,
-	0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6e, 0x6f, 0x47, 0x52, 0x50, 0x43,
-	0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x6e, 0x6f, 0x53, 0x53, 0x45, 0x48,
-	0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6e, 0x6f, 0x53,
-	0x53, 0x45, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x62, 0x0a, 0x12, 0x73, 0x63, 0x4d, 0x61,
-	0x78, 0x45, 0x61, 0x63, 0x68, 0x50, 0x6f, 0x73, 0x74, 0x42, 0x79, 0x74, 0x65, 0x73, 0x18, 0x08,
-	0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e,
-	0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73,
-	0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x52, 0x61, 0x6e, 0x64, 0x52, 0x61, 0x6e,
-	0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x12, 0x73, 0x63, 0x4d, 0x61, 0x78, 0x45,
-	0x61, 0x63, 0x68, 0x50, 0x6f, 0x73, 0x74, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x66, 0x0a, 0x14,
-	0x73, 0x63, 0x4d, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x74, 0x73, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76,
-	0x61, 0x6c, 0x4d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x78, 0x72, 0x61,
+	0x31, 0x0a, 0x0b, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12,
+	0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x66, 0x72,
+	0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02,
+	0x74, 0x6f, 0x22, 0xf4, 0x03, 0x0a, 0x0a, 0x58, 0x6d, 0x75, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x12, 0x56, 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65,
+	0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x78, 0x72, 0x61, 0x79,
+	0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72,
+	0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x52, 0x61,
+	0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x43, 0x6f,
+	0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x56, 0x0a, 0x0e, 0x6d, 0x61, 0x78,
+	0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x2e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f,
+	0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69,
+	0x74, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
+	0x73, 0x12, 0x56, 0x0a, 0x0e, 0x63, 0x4d, 0x61, 0x78, 0x52, 0x65, 0x75, 0x73, 0x65, 0x54, 0x69,
+	0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x78, 0x72, 0x61, 0x79,
+	0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72,
+	0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x52, 0x61,
+	0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x63, 0x4d, 0x61, 0x78, 0x52,
+	0x65, 0x75, 0x73, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x12, 0x56, 0x0a, 0x0e, 0x63, 0x4d, 0x61,
+	0x78, 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x2e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f,
+	0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69,
+	0x74, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x52, 0x0e, 0x63, 0x4d, 0x61, 0x78, 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x4d,
+	0x73, 0x12, 0x5a, 0x0a, 0x10, 0x68, 0x4d, 0x61, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x54, 0x69, 0x6d, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x78, 0x72,
+	0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74,
+	0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x2e,
+	0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x10, 0x68, 0x4d, 0x61,
+	0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x12, 0x2a, 0x0a,
+	0x10, 0x68, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x50, 0x65, 0x72, 0x69, 0x6f,
+	0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x68, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c,
+	0x69, 0x76, 0x65, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x22, 0xf8, 0x05, 0x0a, 0x06, 0x43, 0x6f,
+	0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04,
+	0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65,
+	0x12, 0x50, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28,
+	0x0b, 0x32, 0x36, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f,
+	0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69,
+	0x74, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x48, 0x65, 0x61,
+	0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65,
+	0x72, 0x73, 0x12, 0x54, 0x0a, 0x0d, 0x78, 0x50, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x79,
+	0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x78, 0x72, 0x61, 0x79,
+	0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72,
+	0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x52, 0x61,
+	0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x78, 0x50, 0x61, 0x64, 0x64,
+	0x69, 0x6e, 0x67, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x6f, 0x47, 0x52,
+	0x50, 0x43, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c,
+	0x6e, 0x6f, 0x47, 0x52, 0x50, 0x43, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b,
+	0x6e, 0x6f, 0x53, 0x53, 0x45, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28,
+	0x08, 0x52, 0x0b, 0x6e, 0x6f, 0x53, 0x53, 0x45, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x5e,
+	0x0a, 0x12, 0x73, 0x63, 0x4d, 0x61, 0x78, 0x45, 0x61, 0x63, 0x68, 0x50, 0x6f, 0x73, 0x74, 0x42,
+	0x79, 0x74, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x78, 0x72, 0x61,
 	0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
 	0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x52,
-	0x61, 0x6e, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x14,
-	0x73, 0x63, 0x4d, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x74, 0x73, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76,
-	0x61, 0x6c, 0x4d, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x73, 0x63, 0x4d, 0x61, 0x78, 0x42, 0x75, 0x66,
-	0x66, 0x65, 0x72, 0x65, 0x64, 0x50, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03,
-	0x52, 0x12, 0x73, 0x63, 0x4d, 0x61, 0x78, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x65, 0x64, 0x50,
-	0x6f, 0x73, 0x74, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x6b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76,
-	0x65, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6b,
-	0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x12, 0x43,
-	0x0a, 0x04, 0x78, 0x6d, 0x75, 0x78, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x78,
+	0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x12, 0x73, 0x63, 0x4d, 0x61,
+	0x78, 0x45, 0x61, 0x63, 0x68, 0x50, 0x6f, 0x73, 0x74, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x62,
+	0x0a, 0x14, 0x73, 0x63, 0x4d, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x74, 0x73, 0x49, 0x6e, 0x74, 0x65,
+	0x72, 0x76, 0x61, 0x6c, 0x4d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x78,
 	0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e,
 	0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70,
-	0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x52, 0x04, 0x78,
-	0x6d, 0x75, 0x78, 0x12, 0x51, 0x0a, 0x10, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x53,
-	0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e,
-	0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69,
-	0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f,
-	0x6e, 0x66, 0x69, 0x67, 0x52, 0x10, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x65,
-	0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72,
-	0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
-	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
-	0x38, 0x01, 0x22, 0x35, 0x0a, 0x0f, 0x52, 0x61, 0x6e, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43,
-	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x05, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x74, 0x6f, 0x22, 0xfe, 0x02, 0x0a, 0x0c, 0x4d, 0x75,
-	0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x12, 0x5a, 0x0a, 0x0e, 0x6d, 0x61,
-	0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x32, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70,
-	0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c,
-	0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x52, 0x61, 0x6e, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65,
-	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75,
-	0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x5a, 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e,
-	0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32,
-	0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e,
-	0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74,
-	0x74, 0x70, 0x2e, 0x52, 0x61, 0x6e, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66,
-	0x69, 0x67, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f,
-	0x6e, 0x73, 0x12, 0x5a, 0x0a, 0x0e, 0x63, 0x4d, 0x61, 0x78, 0x52, 0x65, 0x75, 0x73, 0x65, 0x54,
-	0x69, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x78, 0x72, 0x61,
-	0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
-	0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x52,
-	0x61, 0x6e, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e,
-	0x63, 0x4d, 0x61, 0x78, 0x52, 0x65, 0x75, 0x73, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x12, 0x5a,
-	0x0a, 0x0e, 0x63, 0x4d, 0x61, 0x78, 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x4d, 0x73,
-	0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72,
-	0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
-	0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x52, 0x61, 0x6e, 0x64, 0x52,
-	0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x63, 0x4d, 0x61, 0x78,
-	0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x42, 0x85, 0x01, 0x0a, 0x25, 0x63,
-	0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
+	0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x14, 0x73, 0x63,
+	0x4d, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x74, 0x73, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c,
+	0x4d, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x73, 0x63, 0x4d, 0x61, 0x78, 0x42, 0x75, 0x66, 0x66, 0x65,
+	0x72, 0x65, 0x64, 0x50, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12,
+	0x73, 0x63, 0x4d, 0x61, 0x78, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x65, 0x64, 0x50, 0x6f, 0x73,
+	0x74, 0x73, 0x12, 0x41, 0x0a, 0x04, 0x78, 0x6d, 0x75, 0x78, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x2d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
 	0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74,
-	0x68, 0x74, 0x74, 0x70, 0x50, 0x01, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
-	0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72,
-	0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65,
-	0x72, 0x6e, 0x65, 0x74, 0x2f, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0xaa, 0x02,
-	0x21, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e,
-	0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x48, 0x74,
-	0x74, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x68, 0x74, 0x74, 0x70, 0x2e, 0x58, 0x6d, 0x75, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
+	0x04, 0x78, 0x6d, 0x75, 0x78, 0x12, 0x51, 0x0a, 0x10, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61,
+	0x64, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74,
+	0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x10, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64,
+	0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64,
+	0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
+	0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
+	0x3a, 0x02, 0x38, 0x01, 0x42, 0x85, 0x01, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61,
+	0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
+	0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x50, 0x01,
+	0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c,
+	0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e,
+	0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x73,
+	0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0xaa, 0x02, 0x21, 0x58, 0x72, 0x61, 0x79, 0x2e,
+	0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e,
+	0x65, 0x74, 0x2e, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x48, 0x74, 0x74, 0x70, 0x62, 0x06, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -398,28 +410,29 @@ func file_transport_internet_splithttp_config_proto_rawDescGZIP() []byte {
 
 var file_transport_internet_splithttp_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
 var file_transport_internet_splithttp_config_proto_goTypes = []any{
-	(*Config)(nil),                // 0: xray.transport.internet.splithttp.Config
-	(*RandRangeConfig)(nil),       // 1: xray.transport.internet.splithttp.RandRangeConfig
-	(*Multiplexing)(nil),          // 2: xray.transport.internet.splithttp.Multiplexing
+	(*RangeConfig)(nil),           // 0: xray.transport.internet.splithttp.RangeConfig
+	(*XmuxConfig)(nil),            // 1: xray.transport.internet.splithttp.XmuxConfig
+	(*Config)(nil),                // 2: xray.transport.internet.splithttp.Config
 	nil,                           // 3: xray.transport.internet.splithttp.Config.HeadersEntry
 	(*internet.StreamConfig)(nil), // 4: xray.transport.internet.StreamConfig
 }
 var file_transport_internet_splithttp_config_proto_depIdxs = []int32{
-	3,  // 0: xray.transport.internet.splithttp.Config.headers:type_name -> xray.transport.internet.splithttp.Config.HeadersEntry
-	1,  // 1: xray.transport.internet.splithttp.Config.xPaddingBytes:type_name -> xray.transport.internet.splithttp.RandRangeConfig
-	1,  // 2: xray.transport.internet.splithttp.Config.scMaxEachPostBytes:type_name -> xray.transport.internet.splithttp.RandRangeConfig
-	1,  // 3: xray.transport.internet.splithttp.Config.scMinPostsIntervalMs:type_name -> xray.transport.internet.splithttp.RandRangeConfig
-	2,  // 4: xray.transport.internet.splithttp.Config.xmux:type_name -> xray.transport.internet.splithttp.Multiplexing
-	4,  // 5: xray.transport.internet.splithttp.Config.downloadSettings:type_name -> xray.transport.internet.StreamConfig
-	1,  // 6: xray.transport.internet.splithttp.Multiplexing.maxConcurrency:type_name -> xray.transport.internet.splithttp.RandRangeConfig
-	1,  // 7: xray.transport.internet.splithttp.Multiplexing.maxConnections:type_name -> xray.transport.internet.splithttp.RandRangeConfig
-	1,  // 8: xray.transport.internet.splithttp.Multiplexing.cMaxReuseTimes:type_name -> xray.transport.internet.splithttp.RandRangeConfig
-	1,  // 9: xray.transport.internet.splithttp.Multiplexing.cMaxLifetimeMs:type_name -> xray.transport.internet.splithttp.RandRangeConfig
-	10, // [10:10] is the sub-list for method output_type
-	10, // [10:10] is the sub-list for method input_type
-	10, // [10:10] is the sub-list for extension type_name
-	10, // [10:10] is the sub-list for extension extendee
-	0,  // [0:10] is the sub-list for field type_name
+	0,  // 0: xray.transport.internet.splithttp.XmuxConfig.maxConcurrency:type_name -> xray.transport.internet.splithttp.RangeConfig
+	0,  // 1: xray.transport.internet.splithttp.XmuxConfig.maxConnections:type_name -> xray.transport.internet.splithttp.RangeConfig
+	0,  // 2: xray.transport.internet.splithttp.XmuxConfig.cMaxReuseTimes:type_name -> xray.transport.internet.splithttp.RangeConfig
+	0,  // 3: xray.transport.internet.splithttp.XmuxConfig.cMaxLifetimeMs:type_name -> xray.transport.internet.splithttp.RangeConfig
+	0,  // 4: xray.transport.internet.splithttp.XmuxConfig.hMaxRequestTimes:type_name -> xray.transport.internet.splithttp.RangeConfig
+	3,  // 5: xray.transport.internet.splithttp.Config.headers:type_name -> xray.transport.internet.splithttp.Config.HeadersEntry
+	0,  // 6: xray.transport.internet.splithttp.Config.xPaddingBytes:type_name -> xray.transport.internet.splithttp.RangeConfig
+	0,  // 7: xray.transport.internet.splithttp.Config.scMaxEachPostBytes:type_name -> xray.transport.internet.splithttp.RangeConfig
+	0,  // 8: xray.transport.internet.splithttp.Config.scMinPostsIntervalMs:type_name -> xray.transport.internet.splithttp.RangeConfig
+	1,  // 9: xray.transport.internet.splithttp.Config.xmux:type_name -> xray.transport.internet.splithttp.XmuxConfig
+	4,  // 10: xray.transport.internet.splithttp.Config.downloadSettings:type_name -> xray.transport.internet.StreamConfig
+	11, // [11:11] is the sub-list for method output_type
+	11, // [11:11] is the sub-list for method input_type
+	11, // [11:11] is the sub-list for extension type_name
+	11, // [11:11] is the sub-list for extension extendee
+	0,  // [0:11] is the sub-list for field type_name
 }
 
 func init() { file_transport_internet_splithttp_config_proto_init() }

+ 19 - 18
transport/internet/splithttp/config.proto

@@ -8,30 +8,31 @@ option java_multiple_files = true;
 
 import "transport/internet/config.proto";
 
+message RangeConfig {
+  int32 from = 1;
+  int32 to = 2;
+}
+
+message XmuxConfig {
+  RangeConfig maxConcurrency = 1;
+  RangeConfig maxConnections = 2;
+  RangeConfig cMaxReuseTimes = 3;
+  RangeConfig cMaxLifetimeMs = 4;
+  RangeConfig hMaxRequestTimes = 5;
+  int64 hKeepAlivePeriod = 6;
+}
+
 message Config {
   string host = 1;
   string path = 2;
   string mode = 3;
   map<string, string> headers = 4;
-  RandRangeConfig xPaddingBytes = 5;
+  RangeConfig xPaddingBytes = 5;
   bool noGRPCHeader = 6;
   bool noSSEHeader = 7;
-  RandRangeConfig scMaxEachPostBytes = 8;
-  RandRangeConfig scMinPostsIntervalMs = 9;
+  RangeConfig scMaxEachPostBytes = 8;
+  RangeConfig scMinPostsIntervalMs = 9;
   int64 scMaxBufferedPosts = 10;
-  int64 keepAlivePeriod = 11;
-  Multiplexing xmux = 12;
-  xray.transport.internet.StreamConfig downloadSettings = 13;
-}
-
-message RandRangeConfig {
-    int32 from = 1;
-    int32 to = 2;
-}
-
-message Multiplexing {
-  RandRangeConfig maxConcurrency = 1;
-  RandRangeConfig maxConnections = 2;
-  RandRangeConfig cMaxReuseTimes = 3;
-  RandRangeConfig cMaxLifetimeMs = 4;
+  XmuxConfig xmux = 11;
+  xray.transport.internet.StreamConfig downloadSettings = 12;
 }

+ 46 - 27
transport/internet/splithttp/dialer.go

@@ -10,6 +10,7 @@ import (
 	"net/url"
 	"strconv"
 	"sync"
+	"sync/atomic"
 	"time"
 
 	"github.com/quic-go/quic-go"
@@ -45,11 +46,11 @@ type dialerConf struct {
 }
 
 var (
-	globalDialerMap    map[dialerConf]*muxManager
+	globalDialerMap    map[dialerConf]*XmuxManager
 	globalDialerAccess sync.Mutex
 )
 
-func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (DialerClient, *muxResource) {
+func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (DialerClient, *XmuxClient) {
 	realityConfig := reality.ConfigFromStreamSettings(streamSettings)
 
 	if browser_dialer.HasBrowserDialer() && realityConfig != nil {
@@ -60,28 +61,28 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in
 	defer globalDialerAccess.Unlock()
 
 	if globalDialerMap == nil {
-		globalDialerMap = make(map[dialerConf]*muxManager)
+		globalDialerMap = make(map[dialerConf]*XmuxManager)
 	}
 
 	key := dialerConf{dest, streamSettings}
 
-	muxManager, found := globalDialerMap[key]
+	xmuxManager, found := globalDialerMap[key]
 
 	if !found {
 		transportConfig := streamSettings.ProtocolSettings.(*Config)
-		var mux Multiplexing
+		var xmuxConfig XmuxConfig
 		if transportConfig.Xmux != nil {
-			mux = *transportConfig.Xmux
+			xmuxConfig = *transportConfig.Xmux
 		}
 
-		muxManager = NewMuxManager(mux, func() interface{} {
+		xmuxManager = NewXmuxManager(xmuxConfig, func() XmuxConn {
 			return createHTTPClient(dest, streamSettings)
 		})
-		globalDialerMap[key] = muxManager
+		globalDialerMap[key] = xmuxManager
 	}
 
-	res := muxManager.GetResource(ctx)
-	return res.Resource.(DialerClient), res
+	xmuxClient := xmuxManager.GetXmuxClient(ctx)
+	return xmuxClient.XmuxConn.(DialerClient), xmuxClient
 }
 
 func decideHTTPVersion(tlsConfig *tls.Config, realityConfig *reality.Config) string {
@@ -144,7 +145,10 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
 		return conn, nil
 	}
 
-	keepAlivePeriod := time.Duration(streamSettings.ProtocolSettings.(*Config).KeepAlivePeriod) * time.Second
+	var keepAlivePeriod time.Duration
+	if streamSettings.ProtocolSettings.(*Config).Xmux != nil {
+		keepAlivePeriod = time.Duration(streamSettings.ProtocolSettings.(*Config).Xmux.HKeepAlivePeriod) * time.Second
+	}
 
 	var transport http.RoundTripper
 
@@ -282,7 +286,7 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
 	requestURL.Path = transportConfiguration.GetNormalizedPath() + sessionIdUuid.String()
 	requestURL.RawQuery = transportConfiguration.GetNormalizedQuery()
 
-	httpClient, muxRes := getHTTPClient(ctx, dest, streamSettings)
+	httpClient, xmuxClient := getHTTPClient(ctx, dest, streamSettings)
 
 	mode := transportConfiguration.Mode
 	if mode == "" || mode == "auto" {
@@ -299,7 +303,7 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
 
 	requestURL2 := requestURL
 	httpClient2 := httpClient
-	var muxRes2 *muxResource
+	xmuxClient2 := xmuxClient
 	if transportConfiguration.DownloadSettings != nil {
 		globalDialerAccess.Lock()
 		if streamSettings.DownloadSettings == nil {
@@ -332,7 +336,7 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
 		}
 		requestURL2.Path = config2.GetNormalizedPath() + sessionIdUuid.String()
 		requestURL2.RawQuery = config2.GetNormalizedQuery()
-		httpClient2, muxRes2 = getHTTPClient(ctx, dest2, memory2)
+		httpClient2, xmuxClient2 = getHTTPClient(ctx, dest2, memory2)
 		errors.LogInfo(ctx, fmt.Sprintf("XHTTP is downloading from %s, mode %s, HTTP version %s, host %s", dest2, "stream-down", httpVersion2, requestURL2.Host))
 	}
 
@@ -343,23 +347,29 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
 
 	if mode == "stream-one" {
 		requestURL.Path = transportConfiguration.GetNormalizedPath()
+		if xmuxClient != nil {
+			xmuxClient.LeftRequests.Add(-1)
+		}
 		writer, reader = httpClient.Open(context.WithoutCancel(ctx), requestURL.String())
 		remoteAddr = &net.TCPAddr{}
 		localAddr = &net.TCPAddr{}
 	} else {
+		if xmuxClient2 != nil {
+			xmuxClient2.LeftRequests.Add(-1)
+		}
 		reader, remoteAddr, localAddr, err = httpClient2.OpenDownload(context.WithoutCancel(ctx), requestURL2.String())
 		if err != nil {
 			return nil, err
 		}
 	}
 
-	if muxRes != nil {
-		muxRes.OpenRequests.Add(1)
+	if xmuxClient != nil {
+		xmuxClient.OpenUsage.Add(1)
 	}
-	if muxRes2 != nil {
-		muxRes2.OpenRequests.Add(1)
+	if xmuxClient2 != nil && xmuxClient2 != xmuxClient {
+		xmuxClient2.OpenUsage.Add(1)
 	}
-	closed := false
+	var once atomic.Int32
 
 	conn := splitConn{
 		writer:     writer,
@@ -367,23 +377,28 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
 		remoteAddr: remoteAddr,
 		localAddr:  localAddr,
 		onClose: func() {
-			if closed {
+			if once.Add(-1) < 0 {
 				return
 			}
-			closed = true
-			if muxRes != nil {
-				muxRes.OpenRequests.Add(-1)
+			if xmuxClient != nil {
+				xmuxClient.OpenUsage.Add(-1)
 			}
-			if muxRes2 != nil {
-				muxRes2.OpenRequests.Add(-1)
+			if xmuxClient2 != nil && xmuxClient2 != xmuxClient {
+				xmuxClient2.OpenUsage.Add(-1)
 			}
 		},
 	}
 
 	if mode == "stream-one" {
+		if xmuxClient != nil {
+			xmuxClient.LeftRequests.Add(-1)
+		}
 		return stat.Connection(&conn), nil
 	}
 	if mode == "stream-up" {
+		if xmuxClient != nil {
+			xmuxClient.LeftRequests.Add(-1)
+		}
 		conn.writer = httpClient.OpenUpload(ctx, requestURL.String())
 		return stat.Connection(&conn), nil
 	}
@@ -391,7 +406,7 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
 	scMaxEachPostBytes := transportConfiguration.GetNormalizedScMaxEachPostBytes()
 	scMinPostsIntervalMs := transportConfiguration.GetNormalizedScMinPostsIntervalMs()
 
-	maxUploadSize := scMaxEachPostBytes.roll()
+	maxUploadSize := scMaxEachPostBytes.rand()
 	// WithSizeLimit(0) will still allow single bytes to pass, and a lot of
 	// code relies on this behavior. Subtract 1 so that together with
 	// uploadWriter wrapper, exact size limits can be enforced
@@ -426,7 +441,7 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
 			seq += 1
 
 			if scMinPostsIntervalMs.From > 0 {
-				time.Sleep(time.Duration(scMinPostsIntervalMs.roll())*time.Millisecond - time.Since(lastWrite))
+				time.Sleep(time.Duration(scMinPostsIntervalMs.rand())*time.Millisecond - time.Since(lastWrite))
 			}
 
 			// by offloading the uploads into a buffered pipe, multiple conn.Write
@@ -439,6 +454,10 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
 
 			lastWrite = time.Now()
 
+			if xmuxClient != nil && xmuxClient.LeftRequests.Add(-1) <= 0 {
+				httpClient, xmuxClient = getHTTPClient(ctx, dest, streamSettings)
+			}
+
 			go func() {
 				err := httpClient.SendUploadRequest(
 					context.WithoutCancel(ctx),

+ 79 - 67
transport/internet/splithttp/mux.go

@@ -2,101 +2,113 @@ package splithttp
 
 import (
 	"context"
-	"math/rand"
+	"crypto/rand"
+	"math"
+	"math/big"
 	"sync/atomic"
 	"time"
 
 	"github.com/xtls/xray-core/common/errors"
 )
 
-type muxResource struct {
-	Resource       interface{}
-	OpenRequests   atomic.Int32
+type XmuxConn interface {
+	IsClosed() bool
+}
+
+type XmuxClient struct {
+	XmuxConn       XmuxConn
+	OpenUsage      atomic.Int32
 	leftUsage      int32
 	expirationTime time.Time
+	LeftRequests   atomic.Int32
 }
 
-type muxManager struct {
-	newResourceFn func() interface{}
-	config        Multiplexing
-	concurrency   int32
-	connections   int32
-	instances     []*muxResource
+type XmuxManager struct {
+	xmuxConfig  XmuxConfig
+	concurrency int32
+	connections int32
+	newConnFunc func() XmuxConn
+	xmuxClients []*XmuxClient
 }
 
-func NewMuxManager(config Multiplexing, newResource func() interface{}) *muxManager {
-	return &muxManager{
-		config:        config,
-		concurrency:   config.GetNormalizedMaxConcurrency().roll(),
-		connections:   config.GetNormalizedMaxConnections().roll(),
-		newResourceFn: newResource,
-		instances:     make([]*muxResource, 0),
+func NewXmuxManager(xmuxConfig XmuxConfig, newConnFunc func() XmuxConn) *XmuxManager {
+	return &XmuxManager{
+		xmuxConfig:  xmuxConfig,
+		concurrency: xmuxConfig.GetNormalizedMaxConcurrency().rand(),
+		connections: xmuxConfig.GetNormalizedMaxConnections().rand(),
+		newConnFunc: newConnFunc,
+		xmuxClients: make([]*XmuxClient, 0),
 	}
 }
 
-func (m *muxManager) GetResource(ctx context.Context) *muxResource {
-	m.removeExpiredConnections(ctx)
-
-	if m.connections > 0 && len(m.instances) < int(m.connections) {
-		errors.LogDebug(ctx, "xmux: creating client, connections=", len(m.instances))
-		return m.newResource()
+func (m *XmuxManager) newXmuxClient() *XmuxClient {
+	xmuxClient := &XmuxClient{
+		XmuxConn:       m.newConnFunc(),
+		leftUsage:      -1,
+		expirationTime: time.UnixMilli(0),
 	}
-
-	if len(m.instances) == 0 {
-		errors.LogDebug(ctx, "xmux: creating client because instances is empty, connections=", len(m.instances))
-		return m.newResource()
+	if x := m.xmuxConfig.GetNormalizedCMaxReuseTimes().rand(); x > 0 {
+		xmuxClient.leftUsage = x - 1
+	}
+	if x := m.xmuxConfig.GetNormalizedCMaxLifetimeMs().rand(); x > 0 {
+		xmuxClient.expirationTime = time.Now().Add(time.Duration(x) * time.Millisecond)
 	}
+	xmuxClient.LeftRequests.Store(math.MaxInt32)
+	if x := m.xmuxConfig.GetNormalizedCMaxRequestTimes().rand(); x > 0 {
+		xmuxClient.LeftRequests.Store(x)
+	}
+	m.xmuxClients = append(m.xmuxClients, xmuxClient)
+	return xmuxClient
+}
 
-	clients := make([]*muxResource, 0)
-	if m.concurrency > 0 {
-		for _, client := range m.instances {
-			openRequests := client.OpenRequests.Load()
-			if openRequests < m.concurrency {
-				clients = append(clients, client)
-			}
+func (m *XmuxManager) GetXmuxClient(ctx context.Context) *XmuxClient { // when locking
+	for i := 0; i < len(m.xmuxClients); {
+		xmuxClient := m.xmuxClients[i]
+		if xmuxClient.XmuxConn.IsClosed() ||
+			xmuxClient.leftUsage == 0 ||
+			(xmuxClient.expirationTime != time.UnixMilli(0) && time.Now().After(xmuxClient.expirationTime)) ||
+			xmuxClient.LeftRequests.Load() <= 0 {
+			errors.LogDebug(ctx, "XMUX: removing xmuxClient, IsClosed() = ", xmuxClient.XmuxConn.IsClosed(),
+				", OpenUsage = ", xmuxClient.OpenUsage.Load(),
+				", leftUsage = ", xmuxClient.leftUsage,
+				", expirationTime = ", xmuxClient.expirationTime,
+				", LeftRequests = ", xmuxClient.LeftRequests.Load())
+			m.xmuxClients = append(m.xmuxClients[:i], m.xmuxClients[i+1:]...)
+		} else {
+			i++
 		}
-	} else {
-		clients = m.instances
 	}
 
-	if len(clients) == 0 {
-		errors.LogDebug(ctx, "xmux: creating client because concurrency was hit, total clients=", len(m.instances))
-		return m.newResource()
+	if len(m.xmuxClients) == 0 {
+		errors.LogDebug(ctx, "XMUX: creating xmuxClient because xmuxClients is empty")
+		return m.newXmuxClient()
 	}
 
-	client := clients[rand.Intn(len(clients))]
-	if client.leftUsage > 0 {
-		client.leftUsage -= 1
+	if m.connections > 0 && len(m.xmuxClients) < int(m.connections) {
+		errors.LogDebug(ctx, "XMUX: creating xmuxClient because maxConnections was not hit, xmuxClients = ", len(m.xmuxClients))
+		return m.newXmuxClient()
 	}
-	return client
-}
 
-func (m *muxManager) newResource() *muxResource {
-	leftUsage := int32(-1)
-	if x := m.config.GetNormalizedCMaxReuseTimes().roll(); x > 0 {
-		leftUsage = x - 1
-	}
-	expirationTime := time.UnixMilli(0)
-	if x := m.config.GetNormalizedCMaxLifetimeMs().roll(); x > 0 {
-		expirationTime = time.Now().Add(time.Duration(x) * time.Millisecond)
+	xmuxClients := make([]*XmuxClient, 0)
+	if m.concurrency > 0 {
+		for _, xmuxClient := range m.xmuxClients {
+			if xmuxClient.OpenUsage.Load() < m.concurrency {
+				xmuxClients = append(xmuxClients, xmuxClient)
+			}
+		}
+	} else {
+		xmuxClients = m.xmuxClients
 	}
 
-	client := &muxResource{
-		Resource:       m.newResourceFn(),
-		leftUsage:      leftUsage,
-		expirationTime: expirationTime,
+	if len(xmuxClients) == 0 {
+		errors.LogDebug(ctx, "XMUX: creating xmuxClient because maxConcurrency was hit, xmuxClients = ", len(m.xmuxClients))
+		return m.newXmuxClient()
 	}
-	m.instances = append(m.instances, client)
-	return client
-}
 
-func (m *muxManager) removeExpiredConnections(ctx context.Context) {
-	for i := 0; i < len(m.instances); i++ {
-		client := m.instances[i]
-		if client.leftUsage == 0 || (client.expirationTime != time.UnixMilli(0) && time.Now().After(client.expirationTime)) {
-			errors.LogDebug(ctx, "xmux: removing client, leftUsage = ", client.leftUsage, ", expirationTime = ", client.expirationTime)
-			m.instances = append(m.instances[:i], m.instances[i+1:]...)
-			i--
-		}
+	i, _ := rand.Int(rand.Reader, big.NewInt(int64(len(xmuxClients))))
+	xmuxClient := xmuxClients[i.Int64()]
+	if xmuxClient.leftUsage > 0 {
+		xmuxClient.leftUsage -= 1
 	}
+	return xmuxClient
 }

+ 35 - 31
transport/internet/splithttp/mux_test.go

@@ -9,80 +9,84 @@ import (
 
 type fakeRoundTripper struct{}
 
+func (f *fakeRoundTripper) IsClosed() bool {
+	return false
+}
+
 func TestMaxConnections(t *testing.T) {
-	config := Multiplexing{
-		MaxConnections: &RandRangeConfig{From: 4, To: 4},
+	xmuxConfig := XmuxConfig{
+		MaxConnections: &RangeConfig{From: 4, To: 4},
 	}
 
-	mux := NewMuxManager(config, func() interface{} {
+	xmuxManager := NewXmuxManager(xmuxConfig, func() XmuxConn {
 		return &fakeRoundTripper{}
 	})
 
-	clients := make(map[interface{}]struct{})
+	xmuxClients := make(map[interface{}]struct{})
 	for i := 0; i < 8; i++ {
-		clients[mux.GetResource(context.Background())] = struct{}{}
+		xmuxClients[xmuxManager.GetXmuxClient(context.Background())] = struct{}{}
 	}
 
-	if len(clients) != 4 {
-		t.Error("did not get 4 distinct clients, got ", len(clients))
+	if len(xmuxClients) != 4 {
+		t.Error("did not get 4 distinct clients, got ", len(xmuxClients))
 	}
 }
 
 func TestCMaxReuseTimes(t *testing.T) {
-	config := Multiplexing{
-		CMaxReuseTimes: &RandRangeConfig{From: 2, To: 2},
+	xmuxConfig := XmuxConfig{
+		CMaxReuseTimes: &RangeConfig{From: 2, To: 2},
 	}
 
-	mux := NewMuxManager(config, func() interface{} {
+	xmuxManager := NewXmuxManager(xmuxConfig, func() XmuxConn {
 		return &fakeRoundTripper{}
 	})
 
-	clients := make(map[interface{}]struct{})
+	xmuxClients := make(map[interface{}]struct{})
 	for i := 0; i < 64; i++ {
-		clients[mux.GetResource(context.Background())] = struct{}{}
+		xmuxClients[xmuxManager.GetXmuxClient(context.Background())] = struct{}{}
 	}
 
-	if len(clients) != 32 {
-		t.Error("did not get 32 distinct clients, got ", len(clients))
+	if len(xmuxClients) != 32 {
+		t.Error("did not get 32 distinct clients, got ", len(xmuxClients))
 	}
 }
 
 func TestMaxConcurrency(t *testing.T) {
-	config := Multiplexing{
-		MaxConcurrency: &RandRangeConfig{From: 2, To: 2},
+	xmuxConfig := XmuxConfig{
+		MaxConcurrency: &RangeConfig{From: 2, To: 2},
 	}
 
-	mux := NewMuxManager(config, func() interface{} {
+	xmuxManager := NewXmuxManager(xmuxConfig, func() XmuxConn {
 		return &fakeRoundTripper{}
 	})
 
-	clients := make(map[interface{}]struct{})
+	xmuxClients := make(map[interface{}]struct{})
 	for i := 0; i < 64; i++ {
-		client := mux.GetResource(context.Background())
-		client.OpenRequests.Add(1)
-		clients[client] = struct{}{}
+		xmuxClient := xmuxManager.GetXmuxClient(context.Background())
+		xmuxClient.OpenUsage.Add(1)
+		xmuxClients[xmuxClient] = struct{}{}
 	}
 
-	if len(clients) != 32 {
-		t.Error("did not get 32 distinct clients, got ", len(clients))
+	if len(xmuxClients) != 32 {
+		t.Error("did not get 32 distinct clients, got ", len(xmuxClients))
 	}
 }
 
 func TestDefault(t *testing.T) {
-	config := Multiplexing{}
+	xmuxConfig := XmuxConfig{}
 
-	mux := NewMuxManager(config, func() interface{} {
+	xmuxManager := NewXmuxManager(xmuxConfig, func() XmuxConn {
 		return &fakeRoundTripper{}
 	})
 
-	clients := make(map[interface{}]struct{})
+	xmuxClients := make(map[interface{}]struct{})
 	for i := 0; i < 64; i++ {
-		client := mux.GetResource(context.Background())
-		client.OpenRequests.Add(1)
-		clients[client] = struct{}{}
+		xmuxClient := xmuxManager.GetXmuxClient(context.Background())
+		xmuxClient.OpenUsage.Add(1)
+		xmuxClients[xmuxClient] = struct{}{}
 	}
 
-	if len(clients) != 1 {
-		t.Error("did not get 1 distinct clients, got ", len(clients))
+	if len(xmuxClients) != 1 {
+		t.Error("did not get 1 distinct clients, got ", len(xmuxClients))
 	}
 }

+ 1 - 1
transport/internet/splithttp/splithttp_test.go

@@ -423,7 +423,7 @@ func Test_maxUpload(t *testing.T) {
 		ProtocolName: "splithttp",
 		ProtocolSettings: &Config{
 			Path: "/sh",
-			ScMaxEachPostBytes: &RandRangeConfig{
+			ScMaxEachPostBytes: &RangeConfig{
 				From: 10000,
 				To:   10000,
 			},