mirror of
https://github.com/XTLS/REALITY.git
synced 2025-08-22 14:38:35 +00:00
feat: Add rate limiting to fallback handling via token bucket
This commit is contained in:
parent
90e738a94c
commit
6810f8a623
@ -45,7 +45,12 @@ TODO List: TODO
|
|||||||
"shortIds": [ // Required, the acceptable shortId list, which can be used to distinguish different clients
|
"shortIds": [ // Required, the acceptable shortId list, which can be used to distinguish different clients
|
||||||
"", // If there is this item, the client shortId can be empty
|
"", // If there is this item, the client shortId can be empty
|
||||||
"0123456789abcdef" // 0 to f, the length is a multiple of 2, the maximum length is 16
|
"0123456789abcdef" // 0 to f, the length is a multiple of 2, the maximum length is 16
|
||||||
]
|
],
|
||||||
|
// The following four Limits are optional and limit the rate of falling REALITY connections in bytes/second. Default is 0, i.e. not enabled
|
||||||
|
"limitUploadRate": 0, // Uplink base rate
|
||||||
|
"limitUploadBrust": 0, // Uplink burst rate
|
||||||
|
"limitDownloadRate": 0, // Downlink base rate
|
||||||
|
"limitDownloadBrust": 0 // Downlink burst rate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,12 @@ TODO List: TODO
|
|||||||
"shortIds": [ // 必填,客户端可用的 shortId 列表,可用于区分不同的客户端
|
"shortIds": [ // 必填,客户端可用的 shortId 列表,可用于区分不同的客户端
|
||||||
"", // 若有此项,客户端 shortId 可为空
|
"", // 若有此项,客户端 shortId 可为空
|
||||||
"0123456789abcdef" // 0 到 f,长度为 2 的倍数,长度上限为 16
|
"0123456789abcdef" // 0 到 f,长度为 2 的倍数,长度上限为 16
|
||||||
]
|
],
|
||||||
|
// 下列四个 Limit 为选填,可对回落的 REALITY 连接限速,单位是字节/秒。默认为 0 即不启用
|
||||||
|
"limitUploadRate": 0, // 上行基准速率
|
||||||
|
"limitUploadBrust": 0, // 上行突发速率
|
||||||
|
"limitDownloadRate": 0, // 下行基准速率
|
||||||
|
"limitDownloadBrust": 0 // 下行突发速率
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -556,6 +556,11 @@ type Config struct {
|
|||||||
MaxTimeDiff time.Duration
|
MaxTimeDiff time.Duration
|
||||||
ShortIds map[[8]byte]bool
|
ShortIds map[[8]byte]bool
|
||||||
|
|
||||||
|
LimitUploadRate float64
|
||||||
|
LimitUploadBrust int64
|
||||||
|
LimitDownloadRate float64
|
||||||
|
LimitDownloadBrust int64
|
||||||
|
|
||||||
// Rand provides the source of entropy for nonces and RSA blinding.
|
// Rand provides the source of entropy for nonces and RSA blinding.
|
||||||
// If Rand is nil, TLS uses the cryptographic random reader in package
|
// If Rand is nil, TLS uses the cryptographic random reader in package
|
||||||
// crypto/rand.
|
// crypto/rand.
|
||||||
@ -971,6 +976,10 @@ func (c *Config) Clone() *Config {
|
|||||||
MaxClientVer: c.MaxClientVer,
|
MaxClientVer: c.MaxClientVer,
|
||||||
MaxTimeDiff: c.MaxTimeDiff,
|
MaxTimeDiff: c.MaxTimeDiff,
|
||||||
ShortIds: c.ShortIds,
|
ShortIds: c.ShortIds,
|
||||||
|
LimitUploadRate: c.LimitUploadRate,
|
||||||
|
LimitUploadBrust: c.LimitUploadBrust,
|
||||||
|
LimitDownloadRate: c.LimitDownloadRate,
|
||||||
|
LimitDownloadBrust: c.LimitDownloadBrust,
|
||||||
Rand: c.Rand,
|
Rand: c.Rand,
|
||||||
Time: c.Time,
|
Time: c.Time,
|
||||||
Certificates: c.Certificates,
|
Certificates: c.Certificates,
|
||||||
|
1
go.mod
1
go.mod
@ -3,6 +3,7 @@ module github.com/xtls/reality
|
|||||||
go 1.24
|
go 1.24
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/juju/ratelimit v1.0.2
|
||||||
github.com/pires/go-proxyproto v0.8.1
|
github.com/pires/go-proxyproto v0.8.1
|
||||||
github.com/refraction-networking/utls v1.7.3
|
github.com/refraction-networking/utls v1.7.3
|
||||||
golang.org/x/crypto v0.39.0
|
golang.org/x/crypto v0.39.0
|
||||||
|
2
go.sum
2
go.sum
@ -1,3 +1,5 @@
|
|||||||
|
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
|
||||||
|
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
||||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||||
github.com/refraction-networking/utls v1.7.3 h1:L0WRhHY7Oq1T0zkdzVZMR6zWZv+sXbHB9zcuvsAEqCo=
|
github.com/refraction-networking/utls v1.7.3 h1:L0WRhHY7Oq1T0zkdzVZMR6zWZv+sXbHB9zcuvsAEqCo=
|
||||||
|
44
tls.go
44
tls.go
@ -50,6 +50,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/juju/ratelimit"
|
||||||
"github.com/pires/go-proxyproto"
|
"github.com/pires/go-proxyproto"
|
||||||
"golang.org/x/crypto/curve25519"
|
"golang.org/x/crypto/curve25519"
|
||||||
"golang.org/x/crypto/hkdf"
|
"golang.org/x/crypto/hkdf"
|
||||||
@ -100,6 +101,19 @@ func (c *MirrorConn) SetWriteDeadline(t time.Time) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RatelimitedConn struct {
|
||||||
|
net.Conn
|
||||||
|
Bucket *ratelimit.Bucket
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RatelimitedConn) Read(b []byte) (int, error) {
|
||||||
|
n, err := c.Conn.Read(b)
|
||||||
|
if n != 0 {
|
||||||
|
c.Bucket.Wait(int64(n))
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
size = 8192
|
size = 8192
|
||||||
empty = make([]byte, size)
|
empty = make([]byte, size)
|
||||||
@ -228,7 +242,15 @@ func Server(ctx context.Context, conn net.Conn, config *Config) (*Conn, error) {
|
|||||||
if config.Show && hs.clientHello != nil {
|
if config.Show && hs.clientHello != nil {
|
||||||
fmt.Printf("REALITY remoteAddr: %v\tforwarded SNI: %v\n", remoteAddr, hs.clientHello.serverName)
|
fmt.Printf("REALITY remoteAddr: %v\tforwarded SNI: %v\n", remoteAddr, hs.clientHello.serverName)
|
||||||
}
|
}
|
||||||
io.Copy(target, underlying)
|
if config.LimitUploadRate == 0 || config.LimitUploadBrust == 0 {
|
||||||
|
io.Copy(target, underlying)
|
||||||
|
} else {
|
||||||
|
// Limit upload speed for fallback connection
|
||||||
|
io.Copy(target, &RatelimitedConn{
|
||||||
|
Conn: underlying,
|
||||||
|
Bucket: ratelimit.NewBucketWithRate(config.LimitUploadRate, config.LimitUploadBrust),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
waitGroup.Done()
|
waitGroup.Done()
|
||||||
}()
|
}()
|
||||||
@ -359,12 +381,28 @@ func Server(ctx context.Context, conn net.Conn, config *Config) (*Conn, error) {
|
|||||||
if hs.c.conn == conn { // if we processed the Client Hello successfully but the target did not
|
if hs.c.conn == conn { // if we processed the Client Hello successfully but the target did not
|
||||||
waitGroup.Add(1)
|
waitGroup.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
io.Copy(target, underlying)
|
if config.LimitUploadRate == 0 || config.LimitUploadBrust == 0 {
|
||||||
|
io.Copy(target, underlying)
|
||||||
|
} else {
|
||||||
|
// Limit upload speed for fallback connection (handshake ok but hello failed)
|
||||||
|
io.Copy(target, &RatelimitedConn{
|
||||||
|
Conn: underlying,
|
||||||
|
Bucket: ratelimit.NewBucketWithRate(config.LimitUploadRate, config.LimitUploadBrust),
|
||||||
|
})
|
||||||
|
}
|
||||||
waitGroup.Done()
|
waitGroup.Done()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
conn.Write(s2cSaved)
|
conn.Write(s2cSaved)
|
||||||
io.Copy(underlying, target)
|
if config.LimitDownloadRate == 0 || config.LimitDownloadBrust == 0 {
|
||||||
|
io.Copy(underlying, target)
|
||||||
|
} else {
|
||||||
|
// Limit download speed for fallback connection
|
||||||
|
io.Copy(underlying, &RatelimitedConn{
|
||||||
|
Conn: target,
|
||||||
|
Bucket: ratelimit.NewBucketWithRate(config.LimitDownloadRate, config.LimitDownloadBrust),
|
||||||
|
})
|
||||||
|
}
|
||||||
// Here is bidirectional direct forwarding:
|
// Here is bidirectional direct forwarding:
|
||||||
// client ---underlying--- server ---target--- dest
|
// client ---underlying--- server ---target--- dest
|
||||||
// Call `underlying.CloseWrite()` once `io.Copy()` returned
|
// Call `underlying.CloseWrite()` once `io.Copy()` returned
|
||||||
|
Loading…
Reference in New Issue
Block a user