From 59f668577499349b64371a10203a6202db0a8f58 Mon Sep 17 00:00:00 2001 From: mmmray <142015632+mmmray@users.noreply.github.com> Date: Mon, 29 Jul 2024 06:35:17 +0200 Subject: [PATCH] SplitHTTP: More range options, change defaults, enforce maxUploadSize, fix querystring behavior (#3603) * maxUploadSize and maxConcurrentUploads can now be ranges on the client * maxUploadSize is now enforced on the server * the default of maxUploadSize is 2MB on the server, and 1MB on the client * the default of maxConcurrentUploads is 200 on the server, and 100 on the client * ranges on the server are treated as a single number. if server is configured as `"1-2"`, server will enforce `2` * querystrings in `path` are now handled correctly --- infra/conf/transport_internet.go | 20 ++-- transport/internet/splithttp/config.go | 61 ++++++++---- transport/internet/splithttp/config.pb.go | 94 ++++++++++--------- transport/internet/splithttp/config.proto | 4 +- transport/internet/splithttp/config_test.go | 51 ++++++++++ transport/internet/splithttp/dialer.go | 17 ++-- transport/internet/splithttp/hub.go | 12 ++- .../internet/splithttp/splithttp_test.go | 46 +++++++++ 8 files changed, 223 insertions(+), 82 deletions(-) create mode 100644 transport/internet/splithttp/config_test.go diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index bfaa7481..91dc86be 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -229,8 +229,8 @@ type SplitHTTPConfig struct { Host string `json:"host"` Path string `json:"path"` Headers map[string]string `json:"headers"` - MaxConcurrentUploads int32 `json:"maxConcurrentUploads"` - MaxUploadSize int32 `json:"maxUploadSize"` + MaxConcurrentUploads Int32Range `json:"maxConcurrentUploads"` + MaxUploadSize Int32Range `json:"maxUploadSize"` MinUploadIntervalMs Int32Range `json:"minUploadIntervalMs"` } @@ -245,11 +245,17 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) { c.Host = c.Headers["Host"] } config := &splithttp.Config{ - Path: c.Path, - Host: c.Host, - Header: c.Headers, - MaxConcurrentUploads: c.MaxConcurrentUploads, - MaxUploadSize: c.MaxUploadSize, + Path: c.Path, + Host: c.Host, + Header: c.Headers, + MaxConcurrentUploads: &splithttp.RandRangeConfig{ + From: c.MaxConcurrentUploads.From, + To: c.MaxConcurrentUploads.To, + }, + MaxUploadSize: &splithttp.RandRangeConfig{ + From: c.MaxUploadSize.From, + To: c.MaxUploadSize.To, + }, MinUploadIntervalMs: &splithttp.RandRangeConfig{ From: c.MinUploadIntervalMs.From, To: c.MinUploadIntervalMs.To, diff --git a/transport/internet/splithttp/config.go b/transport/internet/splithttp/config.go index 3aa318c8..8bb61746 100644 --- a/transport/internet/splithttp/config.go +++ b/transport/internet/splithttp/config.go @@ -4,23 +4,28 @@ import ( "crypto/rand" "math/big" "net/http" + "strings" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/transport/internet" ) -func (c *Config) GetNormalizedPath() string { - path := c.Path - if path == "" { - path = "/" +func (c *Config) GetNormalizedPath(addPath string, addQuery bool) string { + pathAndQuery := strings.SplitN(c.Path, "?", 2) + path := pathAndQuery[0] + query := "" + if len(pathAndQuery) > 1 && addQuery { + query = "?" + pathAndQuery[1] } - if path[0] != '/' { + + if path == "" || path[0] != '/' { path = "/" + path } if path[len(path)-1] != '/' { path = path + "/" } - return path + + return path + addPath + query } func (c *Config) GetRequestHeader() http.Header { @@ -31,33 +36,51 @@ func (c *Config) GetRequestHeader() http.Header { return header } -func (c *Config) GetNormalizedMaxConcurrentUploads() int32 { - if c.MaxConcurrentUploads == 0 { - return 10 +func (c *Config) GetNormalizedMaxConcurrentUploads(isServer bool) RandRangeConfig { + if c.MaxConcurrentUploads == nil { + if isServer { + return RandRangeConfig{ + From: 200, + To: 200, + } + } else { + return RandRangeConfig{ + From: 100, + To: 100, + } + } } - return c.MaxConcurrentUploads + return *c.MaxConcurrentUploads } -func (c *Config) GetNormalizedMaxUploadSize() int32 { - if c.MaxUploadSize == 0 { - return 1000000 +func (c *Config) GetNormalizedMaxUploadSize(isServer bool) RandRangeConfig { + if c.MaxUploadSize == nil { + if isServer { + return RandRangeConfig{ + From: 2000000, + To: 2000000, + } + } else { + return RandRangeConfig{ + From: 1000000, + To: 1000000, + } + } } - return c.MaxUploadSize + return *c.MaxUploadSize } func (c *Config) GetNormalizedMinUploadInterval() RandRangeConfig { - r := c.MinUploadIntervalMs - - if r == nil { - r = &RandRangeConfig{ + if c.MinUploadIntervalMs == nil { + return RandRangeConfig{ From: 30, To: 30, } } - return *r + return *c.MinUploadIntervalMs } func init() { diff --git a/transport/internet/splithttp/config.pb.go b/transport/internet/splithttp/config.pb.go index 43f57e2f..adde7162 100644 --- a/transport/internet/splithttp/config.pb.go +++ b/transport/internet/splithttp/config.pb.go @@ -28,8 +28,8 @@ 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"` - MaxConcurrentUploads int32 `protobuf:"varint,4,opt,name=maxConcurrentUploads,proto3" json:"maxConcurrentUploads,omitempty"` - MaxUploadSize int32 `protobuf:"varint,5,opt,name=maxUploadSize,proto3" json:"maxUploadSize,omitempty"` + MaxConcurrentUploads *RandRangeConfig `protobuf:"bytes,4,opt,name=maxConcurrentUploads,proto3" json:"maxConcurrentUploads,omitempty"` + MaxUploadSize *RandRangeConfig `protobuf:"bytes,5,opt,name=maxUploadSize,proto3" json:"maxUploadSize,omitempty"` MinUploadIntervalMs *RandRangeConfig `protobuf:"bytes,6,opt,name=minUploadIntervalMs,proto3" json:"minUploadIntervalMs,omitempty"` } @@ -86,18 +86,18 @@ func (x *Config) GetHeader() map[string]string { return nil } -func (x *Config) GetMaxConcurrentUploads() int32 { +func (x *Config) GetMaxConcurrentUploads() *RandRangeConfig { if x != nil { return x.MaxConcurrentUploads } - return 0 + return nil } -func (x *Config) GetMaxUploadSize() int32 { +func (x *Config) GetMaxUploadSize() *RandRangeConfig { if x != nil { return x.MaxUploadSize } - return 0 + return nil } func (x *Config) GetMinUploadIntervalMs() *RandRangeConfig { @@ -169,8 +169,8 @@ 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, 0xfa, - 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, + 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x22, 0xe2, + 0x03, 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, @@ -178,35 +178,41 @@ var file_transport_internet_splithttp_config_proto_rawDesc = []byte{ 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, 0x32, 0x0a, 0x14, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, - 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x6c, - 0x6f, 0x61, 0x64, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x55, 0x70, 0x6c, 0x6f, 0x61, - 0x64, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6d, 0x61, 0x78, - 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x64, 0x0a, 0x13, 0x6d, 0x69, - 0x6e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 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, 0x13, 0x6d, 0x69, 0x6e, - 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x73, - 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, + 0x12, 0x66, 0x0a, 0x14, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 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, 0x14, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x12, 0x58, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x55, + 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x69, 0x7a, 0x65, 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, 0x6d, 0x61, 0x78, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x69, + 0x7a, 0x65, 0x12, 0x64, 0x0a, 0x13, 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 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, 0x13, 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x73, 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 ( @@ -229,12 +235,14 @@ var file_transport_internet_splithttp_config_proto_goTypes = []interface{}{ } var file_transport_internet_splithttp_config_proto_depIdxs = []int32{ 2, // 0: xray.transport.internet.splithttp.Config.header:type_name -> xray.transport.internet.splithttp.Config.HeaderEntry - 1, // 1: xray.transport.internet.splithttp.Config.minUploadIntervalMs:type_name -> xray.transport.internet.splithttp.RandRangeConfig - 2, // [2:2] is the sub-list for method output_type - 2, // [2:2] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name + 1, // 1: xray.transport.internet.splithttp.Config.maxConcurrentUploads:type_name -> xray.transport.internet.splithttp.RandRangeConfig + 1, // 2: xray.transport.internet.splithttp.Config.maxUploadSize:type_name -> xray.transport.internet.splithttp.RandRangeConfig + 1, // 3: xray.transport.internet.splithttp.Config.minUploadIntervalMs:type_name -> xray.transport.internet.splithttp.RandRangeConfig + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] 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 c87ab459..95381feb 100644 --- a/transport/internet/splithttp/config.proto +++ b/transport/internet/splithttp/config.proto @@ -10,8 +10,8 @@ message Config { string host = 1; string path = 2; map header = 3; - int32 maxConcurrentUploads = 4; - int32 maxUploadSize = 5; + RandRangeConfig maxConcurrentUploads = 4; + RandRangeConfig maxUploadSize = 5; RandRangeConfig minUploadIntervalMs = 6; } diff --git a/transport/internet/splithttp/config_test.go b/transport/internet/splithttp/config_test.go new file mode 100644 index 00000000..b2891df9 --- /dev/null +++ b/transport/internet/splithttp/config_test.go @@ -0,0 +1,51 @@ +package splithttp_test + +import ( + "testing" + + . "github.com/xtls/xray-core/transport/internet/splithttp" +) + +func Test_GetNormalizedPath(t *testing.T) { + c := Config{ + 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/" { + t.Error("Unexpected: ", path) + } +} diff --git a/transport/internet/splithttp/dialer.go b/transport/internet/splithttp/dialer.go index 7c677bf9..0448b2f7 100644 --- a/transport/internet/splithttp/dialer.go +++ b/transport/internet/splithttp/dialer.go @@ -181,8 +181,8 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me transportConfiguration := streamSettings.ProtocolSettings.(*Config) tlsConfig := tls.ConfigFromStreamSettings(streamSettings) - maxConcurrentUploads := transportConfiguration.GetNormalizedMaxConcurrentUploads() - maxUploadSize := transportConfiguration.GetNormalizedMaxUploadSize() + maxConcurrentUploads := transportConfiguration.GetNormalizedMaxConcurrentUploads(false) + maxUploadSize := transportConfiguration.GetNormalizedMaxUploadSize(false) minUploadInterval := transportConfiguration.GetNormalizedMinUploadInterval() if tlsConfig != nil { @@ -194,18 +194,17 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me if requestURL.Host == "" { requestURL.Host = dest.NetAddr() } - requestURL.Path = transportConfiguration.GetNormalizedPath() + + sessionIdUuid := uuid.New() + requestURL.Path = transportConfiguration.GetNormalizedPath(sessionIdUuid.String(), true) + baseURL := requestURL.String() httpClient := getHTTPClient(ctx, dest, streamSettings) - sessionIdUuid := uuid.New() - sessionId := sessionIdUuid.String() - baseURL := requestURL.String() + sessionId - - uploadPipeReader, uploadPipeWriter := pipe.New(pipe.WithSizeLimit(maxUploadSize)) + uploadPipeReader, uploadPipeWriter := pipe.New(pipe.WithSizeLimit(maxUploadSize.roll())) go func() { - requestsLimiter := semaphore.New(int(maxConcurrentUploads)) + requestsLimiter := semaphore.New(int(maxConcurrentUploads.roll())) var requestCounter int64 lastWrite := time.Now() diff --git a/transport/internet/splithttp/hub.go b/transport/internet/splithttp/hub.go index 373e1613..86125504 100644 --- a/transport/internet/splithttp/hub.go +++ b/transport/internet/splithttp/hub.go @@ -75,7 +75,7 @@ func (h *requestHandler) upsertSession(sessionId string) *httpSession { } s := &httpSession{ - uploadQueue: NewUploadQueue(int(2 * h.ln.config.GetNormalizedMaxConcurrentUploads())), + uploadQueue: NewUploadQueue(int(h.ln.config.GetNormalizedMaxConcurrentUploads(true).To)), isFullyConnected: done.New(), } @@ -122,6 +122,7 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req } currentSession := h.upsertSession(sessionId) + maxUploadSize := int(h.ln.config.GetNormalizedMaxUploadSize(true).To) if request.Method == "POST" { seq := "" @@ -136,6 +137,13 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req } payload, err := io.ReadAll(request.Body) + + if len(payload) > maxUploadSize { + errors.LogInfo(context.Background(), "Too large upload. maxUploadSize is set to", maxUploadSize, "but request had size", len(payload), ". Adjust maxUploadSize on the server to be at least as large as client.") + writer.WriteHeader(http.StatusRequestEntityTooLarge) + return + } + if err != nil { errors.LogInfoInner(context.Background(), err, "failed to upload") writer.WriteHeader(http.StatusInternalServerError) @@ -260,7 +268,7 @@ func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSet var localAddr = gonet.TCPAddr{} handler := &requestHandler{ host: shSettings.Host, - path: shSettings.GetNormalizedPath(), + path: shSettings.GetNormalizedPath("", false), 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 5002e1a5..1ac27b79 100644 --- a/transport/internet/splithttp/splithttp_test.go +++ b/transport/internet/splithttp/splithttp_test.go @@ -360,3 +360,49 @@ func Test_listenSHAndDial_Unix(t *testing.T) { common.Must(listen.Close()) } + +func Test_queryString(t *testing.T) { + listenPort := tcp.PickPort() + listen, err := ListenSH(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{ + ProtocolName: "splithttp", + ProtocolSettings: &Config{ + // this querystring does not have any effect, but sometimes people blindly copy it from websocket config. make sure the outbound doesn't break + Path: "/sh?ed=2048", + }, + }, func(conn stat.Connection) { + go func(c stat.Connection) { + defer c.Close() + + var b [1024]byte + c.SetReadDeadline(time.Now().Add(2 * time.Second)) + _, err := c.Read(b[:]) + if err != nil { + return + } + + common.Must2(c.Write([]byte("Response"))) + }(conn) + }) + common.Must(err) + ctx := context.Background() + streamSettings := &internet.MemoryStreamConfig{ + ProtocolName: "splithttp", + ProtocolSettings: &Config{Path: "sh"}, + } + conn, err := Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), listenPort), streamSettings) + + common.Must(err) + _, err = conn.Write([]byte("Test connection 1")) + common.Must(err) + + var b [1024]byte + fmt.Println("test2") + n, _ := conn.Read(b[:]) + fmt.Println("string is", n) + if string(b[:n]) != "Response" { + t.Error("response: ", string(b[:n])) + } + + common.Must(conn.Close()) + common.Must(listen.Close()) +}