From a3b306aaa429392507078f8f1e1ab7871d3032e3 Mon Sep 17 00:00:00 2001 From: mmmray <142015632+mmmray@users.noreply.github.com> Date: Sat, 10 Aug 2024 23:47:42 +0200 Subject: [PATCH] SplitHTTP: Replace responseOkPadding with xPaddingBytes (#3643) --- infra/conf/transport_internet.go | 4 +- transport/internet/splithttp/config.go | 43 +++++++++++--- transport/internet/splithttp/config.pb.go | 56 +++++++++---------- transport/internet/splithttp/config.proto | 2 +- transport/internet/splithttp/config_test.go | 37 +----------- transport/internet/splithttp/dialer.go | 50 ++++++++++++----- transport/internet/splithttp/hub.go | 18 +++--- .../internet/splithttp/splithttp_test.go | 15 ++--- 8 files changed, 119 insertions(+), 106 deletions(-) diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 0fa6a381..9e13f246 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -233,7 +233,7 @@ type SplitHTTPConfig struct { ScMaxEachPostBytes *Int32Range `json:"scMaxEachPostBytes"` ScMinPostsIntervalMs *Int32Range `json:"scMinPostsIntervalMs"` NoSSEHeader bool `json:"noSSEHeader"` - ResponseOkPadding *Int32Range `json:"responseOkPadding"` + XPaddingBytes *Int32Range `json:"xPaddingBytes"` } func splithttpNewRandRangeConfig(input *Int32Range) *splithttp.RandRangeConfig { @@ -265,7 +265,7 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) { ScMaxEachPostBytes: splithttpNewRandRangeConfig(c.ScMaxEachPostBytes), ScMinPostsIntervalMs: splithttpNewRandRangeConfig(c.ScMinPostsIntervalMs), NoSSEHeader: c.NoSSEHeader, - ResponseOkPadding: splithttpNewRandRangeConfig(c.ResponseOkPadding), + XPaddingBytes: splithttpNewRandRangeConfig(c.XPaddingBytes), } return config, nil } diff --git a/transport/internet/splithttp/config.go b/transport/internet/splithttp/config.go index efdb3f65..6b5a2005 100644 --- a/transport/internet/splithttp/config.go +++ b/transport/internet/splithttp/config.go @@ -10,22 +10,39 @@ import ( "github.com/xtls/xray-core/transport/internet" ) -func (c *Config) GetNormalizedPath(addPath string, addQuery bool) string { +func (c *Config) GetNormalizedPath() string { pathAndQuery := strings.SplitN(c.Path, "?", 2) path := pathAndQuery[0] - query := "" - if len(pathAndQuery) > 1 && addQuery { - query = "?" + pathAndQuery[1] - } if path == "" || path[0] != '/' { path = "/" + path } + if path[len(path)-1] != '/' { path = path + "/" } - return path + addPath + query + return path +} + +func (c *Config) GetNormalizedQuery() string { + pathAndQuery := strings.SplitN(c.Path, "?", 2) + query := "" + + if len(pathAndQuery) > 1 { + query = pathAndQuery[1] + } + + if query != "" { + query += "&" + } + + paddingLen := c.GetNormalizedXPaddingBytes().roll() + if paddingLen > 0 { + query += "x_padding=" + strings.Repeat("0", int(paddingLen)) + } + + return query } func (c *Config) GetRequestHeader() http.Header { @@ -33,9 +50,17 @@ func (c *Config) GetRequestHeader() http.Header { for k, v := range c.Header { header.Add(k, v) } + return header } +func (c *Config) WriteResponseHeader(writer http.ResponseWriter) { + paddingLen := c.GetNormalizedXPaddingBytes().roll() + if paddingLen > 0 { + writer.Header().Set("X-Padding", strings.Repeat("0", int(paddingLen))) + } +} + func (c *Config) GetNormalizedScMaxConcurrentPosts() RandRangeConfig { if c.ScMaxConcurrentPosts == nil || c.ScMaxConcurrentPosts.To == 0 { return RandRangeConfig{ @@ -69,15 +94,15 @@ func (c *Config) GetNormalizedScMinPostsIntervalMs() RandRangeConfig { return *c.ScMinPostsIntervalMs } -func (c *Config) GetNormalizedResponseOkPadding() RandRangeConfig { - if c.ResponseOkPadding == nil || c.ResponseOkPadding.To == 0 { +func (c *Config) GetNormalizedXPaddingBytes() RandRangeConfig { + if c.XPaddingBytes == nil || c.XPaddingBytes.To == 0 { return RandRangeConfig{ From: 100, To: 1000, } } - return *c.ResponseOkPadding + return *c.XPaddingBytes } func init() { diff --git a/transport/internet/splithttp/config.pb.go b/transport/internet/splithttp/config.pb.go index b5cd7ce1..26dbb0b7 100644 --- a/transport/internet/splithttp/config.pb.go +++ b/transport/internet/splithttp/config.pb.go @@ -32,7 +32,7 @@ type Config struct { ScMaxEachPostBytes *RandRangeConfig `protobuf:"bytes,5,opt,name=scMaxEachPostBytes,proto3" json:"scMaxEachPostBytes,omitempty"` ScMinPostsIntervalMs *RandRangeConfig `protobuf:"bytes,6,opt,name=scMinPostsIntervalMs,proto3" json:"scMinPostsIntervalMs,omitempty"` NoSSEHeader bool `protobuf:"varint,7,opt,name=noSSEHeader,proto3" json:"noSSEHeader,omitempty"` - ResponseOkPadding *RandRangeConfig `protobuf:"bytes,8,opt,name=responseOkPadding,proto3" json:"responseOkPadding,omitempty"` + XPaddingBytes *RandRangeConfig `protobuf:"bytes,8,opt,name=xPaddingBytes,proto3" json:"xPaddingBytes,omitempty"` } func (x *Config) Reset() { @@ -116,9 +116,9 @@ func (x *Config) GetNoSSEHeader() bool { return false } -func (x *Config) GetResponseOkPadding() *RandRangeConfig { +func (x *Config) GetXPaddingBytes() *RandRangeConfig { if x != nil { - return x.ResponseOkPadding + return x.XPaddingBytes } return nil } @@ -185,7 +185,7 @@ var file_transport_internet_splithttp_config_proto_rawDesc = []byte{ 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x21, 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, 0x22, 0xf6, + 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x22, 0xec, 0x04, 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, @@ -215,29 +215,29 @@ var file_transport_internet_splithttp_config_proto_rawDesc = []byte{ 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, 0x64, 0x0a, 0x13, 0x67, 0x65, 0x74, 0x48, 0x61, 0x6e, - 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x50, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 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, 0x13, 0x67, 0x65, 0x74, 0x48, 0x61, 0x6e, 0x64, - 0x73, 0x68, 0x61, 0x6b, 0x65, 0x50, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 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, 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, + 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x5a, 0x0a, 0x0e, 0x78, 0x50, 0x61, 0x64, 0x64, 0x69, + 0x6e, 0x67, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 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, 0x0e, 0x78, 0x50, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 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, 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 ( @@ -263,7 +263,7 @@ var file_transport_internet_splithttp_config_proto_depIdxs = []int32{ 1, // 1: xray.transport.internet.splithttp.Config.scMaxConcurrentPosts: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.responseOkPadding:type_name -> xray.transport.internet.splithttp.RandRangeConfig + 1, // 4: xray.transport.internet.splithttp.Config.xPaddingBytes:type_name -> xray.transport.internet.splithttp.RandRangeConfig 5, // [5:5] is the sub-list for method output_type 5, // [5:5] is the sub-list for method input_type 5, // [5:5] is the sub-list for extension type_name diff --git a/transport/internet/splithttp/config.proto b/transport/internet/splithttp/config.proto index ee8a3909..3f24cfd3 100644 --- a/transport/internet/splithttp/config.proto +++ b/transport/internet/splithttp/config.proto @@ -14,7 +14,7 @@ message Config { RandRangeConfig scMaxEachPostBytes = 5; RandRangeConfig scMinPostsIntervalMs = 6; bool noSSEHeader = 7; - RandRangeConfig responseOkPadding = 8; + RandRangeConfig xPaddingBytes = 8; } message RandRangeConfig { diff --git a/transport/internet/splithttp/config_test.go b/transport/internet/splithttp/config_test.go index b2891df9..39c3fd95 100644 --- a/transport/internet/splithttp/config_test.go +++ b/transport/internet/splithttp/config_test.go @@ -11,41 +11,8 @@ func Test_GetNormalizedPath(t *testing.T) { Path: "/?world", } - path := c.GetNormalizedPath("hello", true) - if path != "/hello?world" { - t.Error("Unexpected: ", path) - } -} - -func Test_GetNormalizedPath2(t *testing.T) { - c := Config{ - Path: "?world", - } - - path := c.GetNormalizedPath("hello", true) - if path != "/hello?world" { - t.Error("Unexpected: ", path) - } -} - -func Test_GetNormalizedPath3(t *testing.T) { - c := Config{ - Path: "hello?world", - } - - path := c.GetNormalizedPath("", true) - if path != "/hello/?world" { - t.Error("Unexpected: ", path) - } -} - -func Test_GetNormalizedPath4(t *testing.T) { - c := Config{ - Path: "hello?world", - } - - path := c.GetNormalizedPath("", false) - if path != "/hello/" { + path := c.GetNormalizedPath() + if path != "/" { t.Error("Unexpected: ", path) } } diff --git a/transport/internet/splithttp/dialer.go b/transport/internet/splithttp/dialer.go index d50514e4..45bdc645 100644 --- a/transport/internet/splithttp/dialer.go +++ b/transport/internet/splithttp/dialer.go @@ -1,6 +1,7 @@ package splithttp import ( + "bytes" "context" gotls "crypto/tls" "io" @@ -217,8 +218,8 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me } sessionIdUuid := uuid.New() - requestURL.Path = transportConfiguration.GetNormalizedPath(sessionIdUuid.String(), true) - baseURL := requestURL.String() + requestURL.Path = transportConfiguration.GetNormalizedPath() + sessionIdUuid.String() + requestURL.RawQuery = transportConfiguration.GetNormalizedQuery() httpClient := getHTTPClient(ctx, dest, streamSettings) @@ -247,9 +248,16 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me 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), - baseURL+"/"+strconv.FormatInt(seq, 10), + url.String(), &buf.MultiBufferContainer{MultiBuffer: chunk}, int64(chunk.Len()), ) @@ -271,26 +279,38 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me } }() - lazyRawDownload, remoteAddr, localAddr, err := httpClient.OpenDownload(context.WithoutCancel(ctx), baseURL) + lazyRawDownload, remoteAddr, localAddr, err := httpClient.OpenDownload(context.WithoutCancel(ctx), requestURL.String()) if err != nil { return nil, err } lazyDownload := &LazyReader{ CreateReader: func() (io.ReadCloser, error) { - // skip "ooooooooook" response - trashHeader := []byte{0} - for { - _, err := io.ReadFull(lazyRawDownload, trashHeader) - if err != nil { - return nil, errors.New("failed to read initial response").Base(err) - } - if trashHeader[0] == 'k' { - break - } + // skip "ok" response + trashHeader := []byte{0, 0} + _, err := io.ReadFull(lazyRawDownload, trashHeader) + if err != nil { + return nil, errors.New("failed to read initial response").Base(err) + } + + if bytes.Equal(trashHeader, []byte("ok")) { + return lazyRawDownload, nil } - return lazyRawDownload, nil + // we read some garbage byte that may not have been "ok" at + // all. return a reader that replays what we have read so far + reader := io.MultiReader( + bytes.NewReader(trashHeader), + lazyRawDownload, + ) + readCloser := struct { + io.Reader + io.Closer + }{ + Reader: reader, + Closer: lazyRawDownload, + } + return readCloser, nil }, } diff --git a/transport/internet/splithttp/hub.go b/transport/internet/splithttp/hub.go index 3ba429ea..701c265e 100644 --- a/transport/internet/splithttp/hub.go +++ b/transport/internet/splithttp/hub.go @@ -124,7 +124,6 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req currentSession := h.upsertSession(sessionId) scMaxEachPostBytes := int(h.ln.config.GetNormalizedScMaxEachPostBytes().To) - responseOkPadding := h.ln.config.GetNormalizedResponseOkPadding() if request.Method == "POST" { seq := "" @@ -170,6 +169,7 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req return } + h.config.WriteResponseHeader(writer) writer.WriteHeader(http.StatusOK) } else if request.Method == "GET" { responseFlusher, ok := writer.(http.Flusher) @@ -189,14 +189,14 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req writer.Header().Set("Content-Type", "text/event-stream") } + h.config.WriteResponseHeader(writer) + writer.WriteHeader(http.StatusOK) - // send a chunk immediately to enable CDN streaming. - // many CDN buffer the response headers until the origin starts sending - // the body, with no way to turn it off. - padding := int(responseOkPadding.roll()) - for i := 0; i < padding; i++ { - writer.Write([]byte("o")) - } + // in earlier versions, this initial body data was used to immediately + // start a 200 OK on all CDN. but xray client since 1.8.16 does not + // actually require an immediate 200 OK, but now requires these + // additional bytes "ok". xray client 1.8.24+ doesn't require "ok" + // anymore, and so this line should be removed in later versions. writer.Write([]byte("ok")) responseFlusher.Flush() @@ -277,7 +277,7 @@ func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSet handler := &requestHandler{ config: shSettings, host: shSettings.Host, - path: shSettings.GetNormalizedPath("", false), + path: shSettings.GetNormalizedPath(), ln: l, sessionMu: &sync.Mutex{}, sessions: sync.Map{}, diff --git a/transport/internet/splithttp/splithttp_test.go b/transport/internet/splithttp/splithttp_test.go index 1ac27b79..acb4addc 100644 --- a/transport/internet/splithttp/splithttp_test.go +++ b/transport/internet/splithttp/splithttp_test.go @@ -5,6 +5,7 @@ import ( "crypto/rand" gotls "crypto/tls" "fmt" + "io" gonet "net" "net/http" "runtime" @@ -60,7 +61,7 @@ func Test_listenSHAndDial(t *testing.T) { var b [1024]byte fmt.Println("test2") - n, _ := conn.Read(b[:]) + n, _ := io.ReadFull(conn, b[:]) fmt.Println("string is", n) if string(b[:n]) != "Response" { t.Error("response: ", string(b[:n])) @@ -72,7 +73,7 @@ func Test_listenSHAndDial(t *testing.T) { common.Must(err) _, err = conn.Write([]byte("Test connection 2")) common.Must(err) - n, _ = conn.Read(b[:]) + n, _ = io.ReadFull(conn, b[:]) common.Must(err) if string(b[:n]) != "Response" { t.Error("response: ", string(b[:n])) @@ -116,7 +117,7 @@ func TestDialWithRemoteAddr(t *testing.T) { common.Must(err) var b [1024]byte - n, _ := conn.Read(b[:]) + n, _ := io.ReadFull(conn, b[:]) if string(b[:n]) != "1.1.1.1:0" { t.Error("response: ", string(b[:n])) } @@ -168,7 +169,7 @@ func Test_listenSHAndDial_TLS(t *testing.T) { common.Must(err) var b [1024]byte - n, _ := conn.Read(b[:]) + n, _ := io.ReadFull(conn, b[:]) if string(b[:n]) != "Response" { t.Error("response: ", string(b[:n])) } @@ -339,7 +340,7 @@ func Test_listenSHAndDial_Unix(t *testing.T) { var b [1024]byte fmt.Println("test2") - n, _ := conn.Read(b[:]) + n, _ := io.ReadFull(conn, b[:]) fmt.Println("string is", n) if string(b[:n]) != "Response" { t.Error("response: ", string(b[:n])) @@ -351,7 +352,7 @@ func Test_listenSHAndDial_Unix(t *testing.T) { common.Must(err) _, err = conn.Write([]byte("Test connection 2")) common.Must(err) - n, _ = conn.Read(b[:]) + n, _ = io.ReadFull(conn, b[:]) common.Must(err) if string(b[:n]) != "Response" { t.Error("response: ", string(b[:n])) @@ -397,7 +398,7 @@ func Test_queryString(t *testing.T) { var b [1024]byte fmt.Println("test2") - n, _ := conn.Read(b[:]) + n, _ := io.ReadFull(conn, b[:]) fmt.Println("string is", n) if string(b[:n]) != "Response" { t.Error("response: ", string(b[:n]))