From 8cd9a743764a9ee13f745edd0a17aedcc33a9c63 Mon Sep 17 00:00:00 2001 From: RPRX <63339210+RPRX@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:05:39 +0000 Subject: [PATCH] XHTTP client: Refactor "packet-up" mode, chasing "stream-up" (#4150) * Add wroteRequest (waiting for new quic-go) * Use XTLS/quic-go instead * Client doesn't need `scMaxConcurrentPosts` anymore * GotConn is available in H3 * `scMaxConcurrentPosts` -> `scMaxBufferedPosts` (server only, 30 by default) Fixes https://github.com/XTLS/Xray-core/issues/4100 --- go.mod | 2 + go.sum | 4 +- infra/conf/transport_internet.go | 105 ++++--- transport/internet/splithttp/client.go | 11 +- transport/internet/splithttp/config.go | 13 +- transport/internet/splithttp/config.pb.go | 262 +++++++++--------- transport/internet/splithttp/config.proto | 20 +- transport/internet/splithttp/dialer.go | 64 +++-- transport/internet/splithttp/hub.go | 2 +- .../internet/splithttp/splithttp_test.go | 2 +- 10 files changed, 235 insertions(+), 250 deletions(-) diff --git a/go.mod b/go.mod index 1d8631a7..1ef6c840 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/xtls/xray-core go 1.21.4 +replace github.com/quic-go/quic-go v0.46.0 => github.com/xtls/quic-go v0.46.2 + require ( github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 github.com/cloudflare/circl v1.4.0 diff --git a/go.sum b/go.sum index 28d4a47d..3740d8ef 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/quic-go v0.46.0 h1:uuwLClEEyk1DNvchH8uCByQVjo3yKL9opKulExNDs7Y= -github.com/quic-go/quic-go v0.46.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI= github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM= github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= @@ -70,6 +68,8 @@ github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQ github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/xtls/quic-go v0.46.2 h1:bzUnZIQIH8SyqYGR6fAXzEvZauA+32J/2w2AtnEFa1o= +github.com/xtls/quic-go v0.46.2/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI= github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d h1:+B97uD9uHLgAAulhigmys4BVwZZypzK7gPN3WtpgRJg= github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index c4937ba5..7669c016 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -221,35 +221,28 @@ func (c *HttpUpgradeConfig) Build() (proto.Message, error) { type SplitHTTPConfig struct { Host string `json:"host"` Path string `json:"path"` + Mode string `json:"mode"` Headers map[string]string `json:"headers"` - ScMaxConcurrentPosts *Int32Range `json:"scMaxConcurrentPosts"` - ScMaxEachPostBytes *Int32Range `json:"scMaxEachPostBytes"` - ScMinPostsIntervalMs *Int32Range `json:"scMinPostsIntervalMs"` + XPaddingBytes Int32Range `json:"xPaddingBytes"` + NoGRPCHeader bool `json:"noGRPCHeader"` NoSSEHeader bool `json:"noSSEHeader"` - XPaddingBytes *Int32Range `json:"xPaddingBytes"` + ScMaxEachPostBytes Int32Range `json:"scMaxEachPostBytes"` + ScMinPostsIntervalMs Int32Range `json:"scMinPostsIntervalMs"` + ScMaxBufferedPosts int64 `json:"scMaxConcurrentPosts"` + KeepAlivePeriod int64 `json:"keepAlivePeriod"` Xmux Xmux `json:"xmux"` DownloadSettings *StreamConfig `json:"downloadSettings"` - Mode string `json:"mode"` Extra json.RawMessage `json:"extra"` - NoGRPCHeader bool `json:"noGRPCHeader"` - KeepAlivePeriod int64 `json:"keepAlivePeriod"` } type Xmux struct { - MaxConcurrency *Int32Range `json:"maxConcurrency"` - MaxConnections *Int32Range `json:"maxConnections"` - CMaxReuseTimes *Int32Range `json:"cMaxReuseTimes"` - CMaxLifetimeMs *Int32Range `json:"cMaxLifetimeMs"` + MaxConcurrency Int32Range `json:"maxConcurrency"` + MaxConnections Int32Range `json:"maxConnections"` + CMaxReuseTimes Int32Range `json:"cMaxReuseTimes"` + CMaxLifetimeMs Int32Range `json:"cMaxLifetimeMs"` } -func splithttpNewRandRangeConfig(input *Int32Range) *splithttp.RandRangeConfig { - if input == nil { - return &splithttp.RandRangeConfig{ - From: 0, - To: 0, - } - } - +func splithttpNewRandRangeConfig(input Int32Range) *splithttp.RandRangeConfig { return &splithttp.RandRangeConfig{ From: input.From, To: input.To, @@ -270,35 +263,6 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) { c = &extra } - // Priority (client): host > serverName > address - for k := range c.Headers { - if strings.ToLower(k) == "host" { - return nil, errors.New(`"headers" can't contain "host"`) - } - } - - if c.Xmux.MaxConnections != nil && c.Xmux.MaxConnections.To > 0 && c.Xmux.MaxConcurrency != nil && c.Xmux.MaxConcurrency.To > 0 { - return nil, errors.New("maxConnections cannot be specified together with maxConcurrency") - } - - // Multiplexing config - muxProtobuf := splithttp.Multiplexing{ - MaxConcurrency: splithttpNewRandRangeConfig(c.Xmux.MaxConcurrency), - MaxConnections: splithttpNewRandRangeConfig(c.Xmux.MaxConnections), - CMaxReuseTimes: splithttpNewRandRangeConfig(c.Xmux.CMaxReuseTimes), - CMaxLifetimeMs: splithttpNewRandRangeConfig(c.Xmux.CMaxLifetimeMs), - } - - if muxProtobuf.MaxConcurrency.To == 0 && - muxProtobuf.MaxConnections.To == 0 && - muxProtobuf.CMaxReuseTimes.To == 0 && - muxProtobuf.CMaxLifetimeMs.To == 0 { - muxProtobuf.MaxConcurrency.From = 16 - muxProtobuf.MaxConcurrency.To = 32 - muxProtobuf.CMaxReuseTimes.From = 64 - muxProtobuf.CMaxReuseTimes.To = 128 - } - switch c.Mode { case "": c.Mode = "auto" @@ -307,21 +271,46 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) { return nil, errors.New("unsupported mode: " + c.Mode) } + // Priority (client): host > serverName > address + for k := range c.Headers { + if strings.ToLower(k) == "host" { + return nil, errors.New(`"headers" can't contain "host"`) + } + } + + 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 { + c.Xmux.MaxConcurrency.From = 16 + c.Xmux.MaxConcurrency.To = 32 + c.Xmux.CMaxReuseTimes.From = 64 + c.Xmux.CMaxReuseTimes.To = 128 + } + config := &splithttp.Config{ - Path: c.Path, Host: c.Host, - Header: c.Headers, - ScMaxConcurrentPosts: splithttpNewRandRangeConfig(c.ScMaxConcurrentPosts), + Path: c.Path, + Mode: c.Mode, + Headers: c.Headers, + XPaddingBytes: splithttpNewRandRangeConfig(c.XPaddingBytes), + NoGRPCHeader: c.NoGRPCHeader, + NoSSEHeader: c.NoSSEHeader, ScMaxEachPostBytes: splithttpNewRandRangeConfig(c.ScMaxEachPostBytes), ScMinPostsIntervalMs: splithttpNewRandRangeConfig(c.ScMinPostsIntervalMs), - NoSSEHeader: c.NoSSEHeader, - XPaddingBytes: splithttpNewRandRangeConfig(c.XPaddingBytes), - Xmux: &muxProtobuf, - Mode: c.Mode, - NoGRPCHeader: c.NoGRPCHeader, + 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), + }, } - var err error + if c.DownloadSettings != nil { if c.Mode == "stream-one" { return nil, errors.New(`Can not use "downloadSettings" in "stream-one" mode.`) @@ -329,10 +318,12 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) { if c.Extra != nil { c.DownloadSettings.SocketSettings = nil } + var err error if config.DownloadSettings, err = c.DownloadSettings.Build(); err != nil { return nil, errors.New(`Failed to build "downloadSettings".`).Base(err) } } + return config, nil } diff --git a/transport/internet/splithttp/client.go b/transport/internet/splithttp/client.go index baeac3ca..35aad1ec 100644 --- a/transport/internet/splithttp/client.go +++ b/transport/internet/splithttp/client.go @@ -141,14 +141,7 @@ func (c *DefaultDialerClient) OpenDownload(ctx context.Context, baseURL string) gotDownResponse.Close() }() - if !c.isH3 { - // in quic-go, sometimes gotConn is never closed for the lifetime of - // the entire connection, and the download locks up - // https://github.com/quic-go/quic-go/issues/3342 - // for other HTTP versions, we want to block Dial until we know the - // remote address of the server, for logging purposes - <-gotConn.Wait() - } + <-gotConn.Wait() lazyDownload := &LazyReader{ CreateReader: func() (io.Reader, error) { @@ -172,7 +165,7 @@ func (c *DefaultDialerClient) OpenDownload(ctx context.Context, baseURL string) } func (c *DefaultDialerClient) SendUploadRequest(ctx context.Context, url string, payload io.ReadWriteCloser, contentLength int64) error { - req, err := http.NewRequest("POST", url, payload) + req, err := http.NewRequestWithContext(ctx, "POST", url, payload) if err != nil { return err } diff --git a/transport/internet/splithttp/config.go b/transport/internet/splithttp/config.go index eb32adf7..ca3f25af 100644 --- a/transport/internet/splithttp/config.go +++ b/transport/internet/splithttp/config.go @@ -47,7 +47,7 @@ func (c *Config) GetNormalizedQuery() string { func (c *Config) GetRequestHeader() http.Header { header := http.Header{} - for k, v := range c.Header { + for k, v := range c.Headers { header.Add(k, v) } @@ -64,15 +64,12 @@ func (c *Config) WriteResponseHeader(writer http.ResponseWriter) { } } -func (c *Config) GetNormalizedScMaxConcurrentPosts() RandRangeConfig { - if c.ScMaxConcurrentPosts == nil || c.ScMaxConcurrentPosts.To == 0 { - return RandRangeConfig{ - From: 100, - To: 100, - } +func (c *Config) GetNormalizedScMaxBufferedPosts() int { + if c.ScMaxBufferedPosts == 0 { + return 30 } - return *c.ScMaxConcurrentPosts + return int(c.ScMaxBufferedPosts) } func (c *Config) GetNormalizedScMaxEachPostBytes() RandRangeConfig { diff --git a/transport/internet/splithttp/config.pb.go b/transport/internet/splithttp/config.pb.go index ad1d3ca9..7d745d53 100644 --- a/transport/internet/splithttp/config.pb.go +++ b/transport/internet/splithttp/config.pb.go @@ -28,17 +28,17 @@ type Config struct { Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` - Header map[string]string `protobuf:"bytes,3,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - ScMaxConcurrentPosts *RandRangeConfig `protobuf:"bytes,4,opt,name=scMaxConcurrentPosts,proto3" json:"scMaxConcurrentPosts,omitempty"` - ScMaxEachPostBytes *RandRangeConfig `protobuf:"bytes,5,opt,name=scMaxEachPostBytes,proto3" json:"scMaxEachPostBytes,omitempty"` - ScMinPostsIntervalMs *RandRangeConfig `protobuf:"bytes,6,opt,name=scMinPostsIntervalMs,proto3" json:"scMinPostsIntervalMs,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"` - XPaddingBytes *RandRangeConfig `protobuf:"bytes,8,opt,name=xPaddingBytes,proto3" json:"xPaddingBytes,omitempty"` - Xmux *Multiplexing `protobuf:"bytes,9,opt,name=xmux,proto3" json:"xmux,omitempty"` - DownloadSettings *internet.StreamConfig `protobuf:"bytes,10,opt,name=downloadSettings,proto3" json:"downloadSettings,omitempty"` - Mode string `protobuf:"bytes,11,opt,name=mode,proto3" json:"mode,omitempty"` - NoGRPCHeader bool `protobuf:"varint,12,opt,name=noGRPCHeader,proto3" json:"noGRPCHeader,omitempty"` - KeepAlivePeriod int64 `protobuf:"varint,13,opt,name=keepAlivePeriod,proto3" json:"keepAlivePeriod,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"` } func (x *Config) Reset() { @@ -85,20 +85,41 @@ func (x *Config) GetPath() string { return "" } -func (x *Config) GetHeader() map[string]string { +func (x *Config) GetMode() string { if x != nil { - return x.Header + return x.Mode + } + return "" +} + +func (x *Config) GetHeaders() map[string]string { + if x != nil { + return x.Headers } return nil } -func (x *Config) GetScMaxConcurrentPosts() *RandRangeConfig { +func (x *Config) GetXPaddingBytes() *RandRangeConfig { if x != nil { - return x.ScMaxConcurrentPosts + return x.XPaddingBytes } return nil } +func (x *Config) GetNoGRPCHeader() bool { + if x != nil { + return x.NoGRPCHeader + } + return false +} + +func (x *Config) GetNoSSEHeader() bool { + if x != nil { + return x.NoSSEHeader + } + return false +} + func (x *Config) GetScMaxEachPostBytes() *RandRangeConfig { if x != nil { return x.ScMaxEachPostBytes @@ -113,18 +134,18 @@ func (x *Config) GetScMinPostsIntervalMs() *RandRangeConfig { return nil } -func (x *Config) GetNoSSEHeader() bool { +func (x *Config) GetScMaxBufferedPosts() int64 { if x != nil { - return x.NoSSEHeader + return x.ScMaxBufferedPosts } - return false + return 0 } -func (x *Config) GetXPaddingBytes() *RandRangeConfig { +func (x *Config) GetKeepAlivePeriod() int64 { if x != nil { - return x.XPaddingBytes + return x.KeepAlivePeriod } - return nil + return 0 } func (x *Config) GetXmux() *Multiplexing { @@ -141,27 +162,6 @@ func (x *Config) GetDownloadSettings() *internet.StreamConfig { return nil } -func (x *Config) GetMode() string { - if x != nil { - return x.Mode - } - return "" -} - -func (x *Config) GetNoGRPCHeader() bool { - if x != nil { - return x.NoGRPCHeader - } - return false -} - -func (x *Config) GetKeepAlivePeriod() int64 { - if x != nil { - return x.KeepAlivePeriod - } - return 0 -} - type RandRangeConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -294,97 +294,94 @@ 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, - 0xe4, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, + 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, 0x4d, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x35, 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, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, - 0x72, 0x12, 0x66, 0x0a, 0x14, 0x73, 0x63, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x74, 0x50, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 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, 0x14, 0x73, 0x63, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x74, 0x50, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x62, 0x0a, 0x12, 0x73, 0x63, 0x4d, - 0x61, 0x78, 0x45, 0x61, 0x63, 0x68, 0x50, 0x6f, 0x73, 0x74, 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, 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, 0x06, 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, - 0x14, 0x73, 0x63, 0x4d, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x74, 0x73, 0x49, 0x6e, 0x74, 0x65, 0x72, - 0x76, 0x61, 0x6c, 0x4d, 0x73, 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, 0x58, 0x0a, 0x0d, 0x78, 0x50, 0x61, 0x64, 0x64, - 0x69, 0x6e, 0x67, 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, 0x0d, 0x78, 0x50, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x79, 0x74, 0x65, - 0x73, 0x12, 0x43, 0x0a, 0x04, 0x78, 0x6d, 0x75, 0x78, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x2f, 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, 0x0a, 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, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, - 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x22, 0x0a, - 0x0c, 0x6e, 0x6f, 0x47, 0x52, 0x50, 0x43, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x0c, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6e, 0x6f, 0x47, 0x52, 0x50, 0x43, 0x48, 0x65, 0x61, 0x64, 0x65, - 0x72, 0x12, 0x28, 0x0a, 0x0f, 0x6b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x50, 0x65, - 0x72, 0x69, 0x6f, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6b, 0x65, 0x65, 0x70, - 0x41, 0x6c, 0x69, 0x76, 0x65, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x1a, 0x39, 0x0a, 0x0b, 0x48, - 0x65, 0x61, 0x64, 0x65, 0x72, 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, + 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, + 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, + 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, 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, + 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, 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, + 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, 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, 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, + 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, + 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 ( @@ -404,26 +401,25 @@ 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 - nil, // 3: xray.transport.internet.splithttp.Config.HeaderEntry + 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.header:type_name -> xray.transport.internet.splithttp.Config.HeaderEntry - 1, // 1: xray.transport.internet.splithttp.Config.scMaxConcurrentPosts:type_name -> xray.transport.internet.splithttp.RandRangeConfig + 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 - 1, // 4: xray.transport.internet.splithttp.Config.xPaddingBytes:type_name -> xray.transport.internet.splithttp.RandRangeConfig - 2, // 5: xray.transport.internet.splithttp.Config.xmux:type_name -> xray.transport.internet.splithttp.Multiplexing - 4, // 6: xray.transport.internet.splithttp.Config.downloadSettings:type_name -> xray.transport.internet.StreamConfig - 1, // 7: xray.transport.internet.splithttp.Multiplexing.maxConcurrency:type_name -> xray.transport.internet.splithttp.RandRangeConfig - 1, // 8: xray.transport.internet.splithttp.Multiplexing.maxConnections:type_name -> xray.transport.internet.splithttp.RandRangeConfig - 1, // 9: xray.transport.internet.splithttp.Multiplexing.cMaxReuseTimes:type_name -> xray.transport.internet.splithttp.RandRangeConfig - 1, // 10: xray.transport.internet.splithttp.Multiplexing.cMaxLifetimeMs:type_name -> xray.transport.internet.splithttp.RandRangeConfig - 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 + 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 } func init() { file_transport_internet_splithttp_config_proto_init() } diff --git a/transport/internet/splithttp/config.proto b/transport/internet/splithttp/config.proto index 78b77b49..655fdf3f 100644 --- a/transport/internet/splithttp/config.proto +++ b/transport/internet/splithttp/config.proto @@ -11,17 +11,17 @@ import "transport/internet/config.proto"; message Config { string host = 1; string path = 2; - map header = 3; - RandRangeConfig scMaxConcurrentPosts = 4; - RandRangeConfig scMaxEachPostBytes = 5; - RandRangeConfig scMinPostsIntervalMs = 6; + string mode = 3; + map headers = 4; + RandRangeConfig xPaddingBytes = 5; + bool noGRPCHeader = 6; bool noSSEHeader = 7; - RandRangeConfig xPaddingBytes = 8; - Multiplexing xmux = 9; - xray.transport.internet.StreamConfig downloadSettings = 10; - string mode = 11; - bool noGRPCHeader = 12; - int64 keepAlivePeriod = 13; + RandRangeConfig scMaxEachPostBytes = 8; + RandRangeConfig scMinPostsIntervalMs = 9; + int64 scMaxBufferedPosts = 10; + int64 keepAlivePeriod = 11; + Multiplexing xmux = 12; + xray.transport.internet.StreamConfig downloadSettings = 13; } message RandRangeConfig { diff --git a/transport/internet/splithttp/dialer.go b/transport/internet/splithttp/dialer.go index 8fa87501..4f9ee01e 100644 --- a/transport/internet/splithttp/dialer.go +++ b/transport/internet/splithttp/dialer.go @@ -5,6 +5,7 @@ import ( gotls "crypto/tls" "io" "net/http" + "net/http/httptrace" "net/url" "strconv" "sync" @@ -16,7 +17,7 @@ import ( "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" - "github.com/xtls/xray-core/common/signal/semaphore" + "github.com/xtls/xray-core/common/signal/done" "github.com/xtls/xray-core/common/uuid" "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/browser_dialer" @@ -249,7 +250,6 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me tlsConfig := tls.ConfigFromStreamSettings(streamSettings) realityConfig := reality.ConfigFromStreamSettings(streamSettings) - scMaxConcurrentPosts := transportConfiguration.GetNormalizedScMaxConcurrentPosts() scMaxEachPostBytes := transportConfiguration.GetNormalizedScMaxEachPostBytes() scMinPostsIntervalMs := transportConfiguration.GetNormalizedScMinPostsIntervalMs() @@ -386,54 +386,60 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me } go func() { - requestsLimiter := semaphore.New(int(scMaxConcurrentPosts.roll())) - var requestCounter int64 + var seq int64 + var lastWrite time.Time - lastWrite := time.Now() - - // by offloading the uploads into a buffered pipe, multiple conn.Write - // calls get automatically batched together into larger POST requests. - // without batching, bandwidth is extremely limited. for { + wroteRequest := done.New() + + ctx := httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{ + WroteRequest: func(httptrace.WroteRequestInfo) { + wroteRequest.Close() + }, + }) + + // this intentionally makes a shallow-copy of the struct so we + // can reassign Path (potentially concurrently) + url := requestURL + url.Path += "/" + strconv.FormatInt(seq, 10) + // reassign query to get different padding + url.RawQuery = transportConfiguration.GetNormalizedQuery() + + seq += 1 + + if scMinPostsIntervalMs.From > 0 { + sleep := time.Duration(scMinPostsIntervalMs.roll())*time.Millisecond - time.Since(lastWrite) + if sleep > 0 { + time.Sleep(sleep) + } + } + + // by offloading the uploads into a buffered pipe, multiple conn.Write + // calls get automatically batched together into larger POST requests. + // without batching, bandwidth is extremely limited. chunk, err := uploadPipeReader.ReadMultiBuffer() if err != nil { break } - <-requestsLimiter.Wait() - - seq := requestCounter - requestCounter += 1 + lastWrite = time.Now() go func() { - defer requestsLimiter.Signal() - - // this intentionally makes a shallow-copy of the struct so we - // can reassign Path (potentially concurrently) - url := requestURL - url.Path += "/" + strconv.FormatInt(seq, 10) - // reassign query to get different padding - url.RawQuery = transportConfiguration.GetNormalizedQuery() - err := httpClient.SendUploadRequest( context.WithoutCancel(ctx), url.String(), &buf.MultiBufferContainer{MultiBuffer: chunk}, int64(chunk.Len()), ) + wroteRequest.Close() if err != nil { errors.LogInfoInner(ctx, err, "failed to send upload") uploadPipeReader.Interrupt() } }() - if scMinPostsIntervalMs.From > 0 { - roll := time.Duration(scMinPostsIntervalMs.roll()) * time.Millisecond - if time.Since(lastWrite) < roll { - time.Sleep(roll) - } - - lastWrite = time.Now() + if _, ok := httpClient.(*DefaultDialerClient); ok { + <-wroteRequest.Wait() } } }() diff --git a/transport/internet/splithttp/hub.go b/transport/internet/splithttp/hub.go index 17e1deef..5fef37d9 100644 --- a/transport/internet/splithttp/hub.go +++ b/transport/internet/splithttp/hub.go @@ -78,7 +78,7 @@ func (h *requestHandler) upsertSession(sessionId string) *httpSession { } s := &httpSession{ - uploadQueue: NewUploadQueue(int(h.ln.config.GetNormalizedScMaxConcurrentPosts().To)), + uploadQueue: NewUploadQueue(h.ln.config.GetNormalizedScMaxBufferedPosts()), isFullyConnected: done.New(), } diff --git a/transport/internet/splithttp/splithttp_test.go b/transport/internet/splithttp/splithttp_test.go index c349f8f5..8f32f7d3 100644 --- a/transport/internet/splithttp/splithttp_test.go +++ b/transport/internet/splithttp/splithttp_test.go @@ -109,7 +109,7 @@ func TestDialWithRemoteAddr(t *testing.T) { conn, err := Dial(context.Background(), net.TCPDestination(net.DomainAddress("localhost"), listenPort), &internet.MemoryStreamConfig{ ProtocolName: "splithttp", - ProtocolSettings: &Config{Path: "sh", Header: map[string]string{"X-Forwarded-For": "1.1.1.1"}}, + ProtocolSettings: &Config{Path: "sh", Headers: map[string]string{"X-Forwarded-For": "1.1.1.1"}}, }) common.Must(err)