Skip to content

Commit

Permalink
SplitHTTP: More range options, change defaults, enforce maxUploadSize…
Browse files Browse the repository at this point in the history
…, 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
  • Loading branch information
mmmray authored Jul 29, 2024
1 parent 4cb2a12 commit 59f6685
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 82 deletions.
20 changes: 13 additions & 7 deletions infra/conf/transport_internet.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}

Expand All @@ -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,
Expand Down
61 changes: 42 additions & 19 deletions transport/internet/splithttp/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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() {
Expand Down
94 changes: 51 additions & 43 deletions transport/internet/splithttp/config.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions transport/internet/splithttp/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ message Config {
string host = 1;
string path = 2;
map<string, string> header = 3;
int32 maxConcurrentUploads = 4;
int32 maxUploadSize = 5;
RandRangeConfig maxConcurrentUploads = 4;
RandRangeConfig maxUploadSize = 5;
RandRangeConfig minUploadIntervalMs = 6;
}

Expand Down
51 changes: 51 additions & 0 deletions transport/internet/splithttp/config_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
17 changes: 8 additions & 9 deletions transport/internet/splithttp/dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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()

httpClient := getHTTPClient(ctx, dest, streamSettings)

sessionIdUuid := uuid.New()
sessionId := sessionIdUuid.String()
baseURL := requestURL.String() + sessionId
requestURL.Path = transportConfiguration.GetNormalizedPath(sessionIdUuid.String(), true)
baseURL := requestURL.String()

httpClient := getHTTPClient(ctx, dest, streamSettings)

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()
Expand Down
Loading

0 comments on commit 59f6685

Please sign in to comment.