SplitHTTP: Replace responseOkPadding with xPaddingBytes (#3643)

This commit is contained in:
mmmray 2024-08-10 23:47:42 +02:00 committed by GitHub
parent f650d87083
commit a3b306aaa4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 119 additions and 106 deletions

View file

@ -233,7 +233,7 @@ type SplitHTTPConfig struct {
ScMaxEachPostBytes *Int32Range `json:"scMaxEachPostBytes"` ScMaxEachPostBytes *Int32Range `json:"scMaxEachPostBytes"`
ScMinPostsIntervalMs *Int32Range `json:"scMinPostsIntervalMs"` ScMinPostsIntervalMs *Int32Range `json:"scMinPostsIntervalMs"`
NoSSEHeader bool `json:"noSSEHeader"` NoSSEHeader bool `json:"noSSEHeader"`
ResponseOkPadding *Int32Range `json:"responseOkPadding"` XPaddingBytes *Int32Range `json:"xPaddingBytes"`
} }
func splithttpNewRandRangeConfig(input *Int32Range) *splithttp.RandRangeConfig { func splithttpNewRandRangeConfig(input *Int32Range) *splithttp.RandRangeConfig {
@ -265,7 +265,7 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
ScMaxEachPostBytes: splithttpNewRandRangeConfig(c.ScMaxEachPostBytes), ScMaxEachPostBytes: splithttpNewRandRangeConfig(c.ScMaxEachPostBytes),
ScMinPostsIntervalMs: splithttpNewRandRangeConfig(c.ScMinPostsIntervalMs), ScMinPostsIntervalMs: splithttpNewRandRangeConfig(c.ScMinPostsIntervalMs),
NoSSEHeader: c.NoSSEHeader, NoSSEHeader: c.NoSSEHeader,
ResponseOkPadding: splithttpNewRandRangeConfig(c.ResponseOkPadding), XPaddingBytes: splithttpNewRandRangeConfig(c.XPaddingBytes),
} }
return config, nil return config, nil
} }

View file

@ -10,22 +10,39 @@ import (
"github.com/xtls/xray-core/transport/internet" "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) pathAndQuery := strings.SplitN(c.Path, "?", 2)
path := pathAndQuery[0] path := pathAndQuery[0]
query := ""
if len(pathAndQuery) > 1 && addQuery {
query = "?" + pathAndQuery[1]
}
if path == "" || path[0] != '/' { if path == "" || path[0] != '/' {
path = "/" + path path = "/" + path
} }
if path[len(path)-1] != '/' { if path[len(path)-1] != '/' {
path = path + "/" 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 { func (c *Config) GetRequestHeader() http.Header {
@ -33,9 +50,17 @@ func (c *Config) GetRequestHeader() http.Header {
for k, v := range c.Header { for k, v := range c.Header {
header.Add(k, v) header.Add(k, v)
} }
return header 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 { func (c *Config) GetNormalizedScMaxConcurrentPosts() RandRangeConfig {
if c.ScMaxConcurrentPosts == nil || c.ScMaxConcurrentPosts.To == 0 { if c.ScMaxConcurrentPosts == nil || c.ScMaxConcurrentPosts.To == 0 {
return RandRangeConfig{ return RandRangeConfig{
@ -69,15 +94,15 @@ func (c *Config) GetNormalizedScMinPostsIntervalMs() RandRangeConfig {
return *c.ScMinPostsIntervalMs return *c.ScMinPostsIntervalMs
} }
func (c *Config) GetNormalizedResponseOkPadding() RandRangeConfig { func (c *Config) GetNormalizedXPaddingBytes() RandRangeConfig {
if c.ResponseOkPadding == nil || c.ResponseOkPadding.To == 0 { if c.XPaddingBytes == nil || c.XPaddingBytes.To == 0 {
return RandRangeConfig{ return RandRangeConfig{
From: 100, From: 100,
To: 1000, To: 1000,
} }
} }
return *c.ResponseOkPadding return *c.XPaddingBytes
} }
func init() { func init() {

View file

@ -32,7 +32,7 @@ type Config struct {
ScMaxEachPostBytes *RandRangeConfig `protobuf:"bytes,5,opt,name=scMaxEachPostBytes,proto3" json:"scMaxEachPostBytes,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"` ScMinPostsIntervalMs *RandRangeConfig `protobuf:"bytes,6,opt,name=scMinPostsIntervalMs,proto3" json:"scMinPostsIntervalMs,omitempty"`
NoSSEHeader bool `protobuf:"varint,7,opt,name=noSSEHeader,proto3" json:"noSSEHeader,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() { func (x *Config) Reset() {
@ -116,9 +116,9 @@ func (x *Config) GetNoSSEHeader() bool {
return false return false
} }
func (x *Config) GetResponseOkPadding() *RandRangeConfig { func (x *Config) GetXPaddingBytes() *RandRangeConfig {
if x != nil { if x != nil {
return x.ResponseOkPadding return x.XPaddingBytes
} }
return nil 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x5a, 0x0a, 0x0e, 0x78, 0x50, 0x61, 0x64, 0x64, 0x69,
0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x50, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x08, 0x20, 0x6e, 0x67, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32,
0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e,
0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74,
0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x52, 0x61, 0x6e, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x74, 0x70, 0x2e, 0x52, 0x61, 0x6e, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66,
0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x13, 0x67, 0x65, 0x74, 0x48, 0x61, 0x6e, 0x64, 0x69, 0x67, 0x52, 0x0e, 0x78, 0x50, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x65, 0x61, 0x64,
0x73, 0x68, 0x61, 0x6b, 0x65, 0x50, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x1a, 0x39, 0x0a, 0x0b, 0x65, 0x72, 0x1a, 0x39, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72,
0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x35, 0x0a,
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x35, 0x0a, 0x0f, 0x52, 0x61, 0x6e, 0x64, 0x52, 0x0f, 0x52, 0x61, 0x6e, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04,
0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05,
0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x74, 0x6f, 0x42, 0x85, 0x52, 0x02, 0x74, 0x6f, 0x42, 0x85, 0x01, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61,
0x01, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x50, 0x01,
0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x50, 0x01, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e,
0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x73,
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0xaa, 0x02, 0x21, 0x58, 0x72, 0x61, 0x79, 0x2e,
0x74, 0x70, 0xaa, 0x02, 0x21, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e,
0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x70, 0x6c, 0x65, 0x74, 0x2e, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x48, 0x74, 0x74, 0x70, 0x62, 0x06, 0x70, 0x72,
0x69, 0x74, 0x48, 0x74, 0x74, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( 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, // 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, // 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, // 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 output_type
5, // [5:5] is the sub-list for method input_type 5, // [5:5] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name 5, // [5:5] is the sub-list for extension type_name

View file

@ -14,7 +14,7 @@ message Config {
RandRangeConfig scMaxEachPostBytes = 5; RandRangeConfig scMaxEachPostBytes = 5;
RandRangeConfig scMinPostsIntervalMs = 6; RandRangeConfig scMinPostsIntervalMs = 6;
bool noSSEHeader = 7; bool noSSEHeader = 7;
RandRangeConfig responseOkPadding = 8; RandRangeConfig xPaddingBytes = 8;
} }
message RandRangeConfig { message RandRangeConfig {

View file

@ -11,41 +11,8 @@ func Test_GetNormalizedPath(t *testing.T) {
Path: "/?world", Path: "/?world",
} }
path := c.GetNormalizedPath("hello", true) path := c.GetNormalizedPath()
if path != "/hello?world" { if path != "/" {
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/" {
t.Error("Unexpected: ", path) t.Error("Unexpected: ", path)
} }
} }

View file

@ -1,6 +1,7 @@
package splithttp package splithttp
import ( import (
"bytes"
"context" "context"
gotls "crypto/tls" gotls "crypto/tls"
"io" "io"
@ -217,8 +218,8 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
} }
sessionIdUuid := uuid.New() sessionIdUuid := uuid.New()
requestURL.Path = transportConfiguration.GetNormalizedPath(sessionIdUuid.String(), true) requestURL.Path = transportConfiguration.GetNormalizedPath() + sessionIdUuid.String()
baseURL := requestURL.String() requestURL.RawQuery = transportConfiguration.GetNormalizedQuery()
httpClient := getHTTPClient(ctx, dest, streamSettings) httpClient := getHTTPClient(ctx, dest, streamSettings)
@ -247,9 +248,16 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
go func() { go func() {
defer requestsLimiter.Signal() 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( err := httpClient.SendUploadRequest(
context.WithoutCancel(ctx), context.WithoutCancel(ctx),
baseURL+"/"+strconv.FormatInt(seq, 10), url.String(),
&buf.MultiBufferContainer{MultiBuffer: chunk}, &buf.MultiBufferContainer{MultiBuffer: chunk},
int64(chunk.Len()), 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 { if err != nil {
return nil, err return nil, err
} }
lazyDownload := &LazyReader{ lazyDownload := &LazyReader{
CreateReader: func() (io.ReadCloser, error) { CreateReader: func() (io.ReadCloser, error) {
// skip "ooooooooook" response // skip "ok" response
trashHeader := []byte{0} trashHeader := []byte{0, 0}
for { _, err := io.ReadFull(lazyRawDownload, trashHeader)
_, err := io.ReadFull(lazyRawDownload, trashHeader) if err != nil {
if err != nil { return nil, errors.New("failed to read initial response").Base(err)
return nil, errors.New("failed to read initial response").Base(err)
}
if trashHeader[0] == 'k' {
break
}
} }
return lazyRawDownload, nil if bytes.Equal(trashHeader, []byte("ok")) {
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
}, },
} }

View file

@ -124,7 +124,6 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
currentSession := h.upsertSession(sessionId) currentSession := h.upsertSession(sessionId)
scMaxEachPostBytes := int(h.ln.config.GetNormalizedScMaxEachPostBytes().To) scMaxEachPostBytes := int(h.ln.config.GetNormalizedScMaxEachPostBytes().To)
responseOkPadding := h.ln.config.GetNormalizedResponseOkPadding()
if request.Method == "POST" { if request.Method == "POST" {
seq := "" seq := ""
@ -170,6 +169,7 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
return return
} }
h.config.WriteResponseHeader(writer)
writer.WriteHeader(http.StatusOK) writer.WriteHeader(http.StatusOK)
} else if request.Method == "GET" { } else if request.Method == "GET" {
responseFlusher, ok := writer.(http.Flusher) 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") writer.Header().Set("Content-Type", "text/event-stream")
} }
h.config.WriteResponseHeader(writer)
writer.WriteHeader(http.StatusOK) writer.WriteHeader(http.StatusOK)
// send a chunk immediately to enable CDN streaming. // in earlier versions, this initial body data was used to immediately
// many CDN buffer the response headers until the origin starts sending // start a 200 OK on all CDN. but xray client since 1.8.16 does not
// the body, with no way to turn it off. // actually require an immediate 200 OK, but now requires these
padding := int(responseOkPadding.roll()) // additional bytes "ok". xray client 1.8.24+ doesn't require "ok"
for i := 0; i < padding; i++ { // anymore, and so this line should be removed in later versions.
writer.Write([]byte("o"))
}
writer.Write([]byte("ok")) writer.Write([]byte("ok"))
responseFlusher.Flush() responseFlusher.Flush()
@ -277,7 +277,7 @@ func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSet
handler := &requestHandler{ handler := &requestHandler{
config: shSettings, config: shSettings,
host: shSettings.Host, host: shSettings.Host,
path: shSettings.GetNormalizedPath("", false), path: shSettings.GetNormalizedPath(),
ln: l, ln: l,
sessionMu: &sync.Mutex{}, sessionMu: &sync.Mutex{},
sessions: sync.Map{}, sessions: sync.Map{},

View file

@ -5,6 +5,7 @@ import (
"crypto/rand" "crypto/rand"
gotls "crypto/tls" gotls "crypto/tls"
"fmt" "fmt"
"io"
gonet "net" gonet "net"
"net/http" "net/http"
"runtime" "runtime"
@ -60,7 +61,7 @@ func Test_listenSHAndDial(t *testing.T) {
var b [1024]byte var b [1024]byte
fmt.Println("test2") fmt.Println("test2")
n, _ := conn.Read(b[:]) n, _ := io.ReadFull(conn, b[:])
fmt.Println("string is", n) fmt.Println("string is", n)
if string(b[:n]) != "Response" { if string(b[:n]) != "Response" {
t.Error("response: ", string(b[:n])) t.Error("response: ", string(b[:n]))
@ -72,7 +73,7 @@ func Test_listenSHAndDial(t *testing.T) {
common.Must(err) common.Must(err)
_, err = conn.Write([]byte("Test connection 2")) _, err = conn.Write([]byte("Test connection 2"))
common.Must(err) common.Must(err)
n, _ = conn.Read(b[:]) n, _ = io.ReadFull(conn, b[:])
common.Must(err) common.Must(err)
if string(b[:n]) != "Response" { if string(b[:n]) != "Response" {
t.Error("response: ", string(b[:n])) t.Error("response: ", string(b[:n]))
@ -116,7 +117,7 @@ func TestDialWithRemoteAddr(t *testing.T) {
common.Must(err) common.Must(err)
var b [1024]byte var b [1024]byte
n, _ := conn.Read(b[:]) n, _ := io.ReadFull(conn, b[:])
if string(b[:n]) != "1.1.1.1:0" { if string(b[:n]) != "1.1.1.1:0" {
t.Error("response: ", string(b[:n])) t.Error("response: ", string(b[:n]))
} }
@ -168,7 +169,7 @@ func Test_listenSHAndDial_TLS(t *testing.T) {
common.Must(err) common.Must(err)
var b [1024]byte var b [1024]byte
n, _ := conn.Read(b[:]) n, _ := io.ReadFull(conn, b[:])
if string(b[:n]) != "Response" { if string(b[:n]) != "Response" {
t.Error("response: ", string(b[:n])) t.Error("response: ", string(b[:n]))
} }
@ -339,7 +340,7 @@ func Test_listenSHAndDial_Unix(t *testing.T) {
var b [1024]byte var b [1024]byte
fmt.Println("test2") fmt.Println("test2")
n, _ := conn.Read(b[:]) n, _ := io.ReadFull(conn, b[:])
fmt.Println("string is", n) fmt.Println("string is", n)
if string(b[:n]) != "Response" { if string(b[:n]) != "Response" {
t.Error("response: ", string(b[:n])) t.Error("response: ", string(b[:n]))
@ -351,7 +352,7 @@ func Test_listenSHAndDial_Unix(t *testing.T) {
common.Must(err) common.Must(err)
_, err = conn.Write([]byte("Test connection 2")) _, err = conn.Write([]byte("Test connection 2"))
common.Must(err) common.Must(err)
n, _ = conn.Read(b[:]) n, _ = io.ReadFull(conn, b[:])
common.Must(err) common.Must(err)
if string(b[:n]) != "Response" { if string(b[:n]) != "Response" {
t.Error("response: ", string(b[:n])) t.Error("response: ", string(b[:n]))
@ -397,7 +398,7 @@ func Test_queryString(t *testing.T) {
var b [1024]byte var b [1024]byte
fmt.Println("test2") fmt.Println("test2")
n, _ := conn.Read(b[:]) n, _ := io.ReadFull(conn, b[:])
fmt.Println("string is", n) fmt.Println("string is", n)
if string(b[:n]) != "Response" { if string(b[:n]) != "Response" {
t.Error("response: ", string(b[:n])) t.Error("response: ", string(b[:n]))