Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions infra/conf/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package conf

import (
"encoding/json"
"strconv"
"strings"

"github.com/xtls/xray-core/common/errors"
Expand Down Expand Up @@ -242,3 +243,33 @@ func (v *User) Build() *protocol.User {
Level: uint32(v.LevelByte),
}
}

// Int32Range deserializes from "1-2" or 1, so can deserialize from both int and number.
// Negative integers can be passed as sentinel values, but do not parse as ranges.
type Int32Range struct {
From int32
To int32
}

func (v *Int32Range) UnmarshalJSON(data []byte) error {
var stringrange string
var rawint int32
if err := json.Unmarshal(data, &stringrange); err == nil {
pair := strings.SplitN(stringrange, "-", 2)
if len(pair) == 2 {
from, err := strconv.Atoi(pair[0])
to, err2 := strconv.Atoi(pair[1])
if err == nil && err2 == nil {
v.From = int32(from)
v.To = int32(to)
return nil
}
}
} else if err := json.Unmarshal(data, &rawint); err == nil {
v.From = rawint
v.To = rawint
return nil
}

return errors.New("Invalid integer range, expected either string of form \"1-2\" or plain integer.")
}
5 changes: 5 additions & 0 deletions infra/conf/transport_internet.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ type SplitHTTPConfig struct {
Headers map[string]string `json:"headers"`
MaxConcurrentUploads int32 `json:"maxConcurrentUploads"`
MaxUploadSize int32 `json:"maxUploadSize"`
MinUploadIntervalMs Int32Range `json:"minUploadIntervalMs"`
}

// Build implements Buildable.
Expand All @@ -249,6 +250,10 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
Header: c.Headers,
MaxConcurrentUploads: c.MaxConcurrentUploads,
MaxUploadSize: c.MaxUploadSize,
MinUploadIntervalMs: &splithttp.RandRangeConfig{
From: c.MinUploadIntervalMs.From,
To: c.MinUploadIntervalMs.To,
},
}
return config, nil
}
Expand Down
23 changes: 23 additions & 0 deletions transport/internet/splithttp/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package splithttp

import (
"crypto/rand"
"math/big"
"net/http"

"github.com/xtls/xray-core/common"
Expand Down Expand Up @@ -45,8 +47,29 @@ func (c *Config) GetNormalizedMaxUploadSize() int32 {
return c.MaxUploadSize
}

func (c *Config) GetNormalizedMinUploadInterval() RandRangeConfig {
r := c.MinUploadIntervalMs

if r == nil {
r = &RandRangeConfig{
From: 30,
To: 30,
}
}

return *r
}

func init() {
common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} {
return new(Config)
}))
}

func (c RandRangeConfig) roll() int32 {
if c.From == c.To {
return c.From
}
bigInt, _ := rand.Int(rand.Reader, big.NewInt(int64(c.To-c.From)))
return c.From + int32(bigInt.Int64())
}
137 changes: 112 additions & 25 deletions transport/internet/splithttp/config.pb.go

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

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

message RandRangeConfig {
int32 from = 1;
int32 to = 2;
}
11 changes: 11 additions & 0 deletions transport/internet/splithttp/dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me

maxConcurrentUploads := transportConfiguration.GetNormalizedMaxConcurrentUploads()
maxUploadSize := transportConfiguration.GetNormalizedMaxUploadSize()
minUploadInterval := transportConfiguration.GetNormalizedMinUploadInterval()

if tlsConfig != nil {
requestURL.Scheme = "https"
Expand All @@ -207,6 +208,8 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
requestsLimiter := semaphore.New(int(maxConcurrentUploads))
var requestCounter int64

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.
Expand Down Expand Up @@ -237,6 +240,14 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
}
}()

if minUploadInterval.From > 0 {
roll := time.Duration(minUploadInterval.roll()) * time.Millisecond
if time.Since(lastWrite) < roll {
time.Sleep(roll)
}

lastWrite = time.Now()
}
}
}()

Expand Down