feat: add configurable congestion modes and BBR profiles

This commit is contained in:
Toby 2026-03-28 13:11:17 -07:00
parent a00368f9e3
commit b3f2193f10
14 changed files with 475 additions and 36 deletions

View file

@ -65,6 +65,7 @@ type clientConfig struct {
Obfs clientConfigObfs `mapstructure:"obfs"`
TLS clientConfigTLS `mapstructure:"tls"`
QUIC clientConfigQUIC `mapstructure:"quic"`
Congestion clientConfigCongestion `mapstructure:"congestion"`
Bandwidth clientConfigBandwidth `mapstructure:"bandwidth"`
FastOpen bool `mapstructure:"fastOpen"`
Lazy bool `mapstructure:"lazy"`
@ -127,6 +128,11 @@ type clientConfigBandwidth struct {
Down string `mapstructure:"down"`
}
type clientConfigCongestion struct {
Type string `mapstructure:"type"`
BBRProfile string `mapstructure:"bbrProfile"`
}
type socks5Config struct {
Listen string `mapstructure:"listen"`
Username string `mapstructure:"username"`
@ -355,6 +361,22 @@ func (c *clientConfig) fillBandwidthConfig(hyConfig *client.Config) error {
return nil
}
func (c *clientConfig) fillCongestionConfig(hyConfig *client.Config) error {
normalizedType, err := normalizeCongestionType(c.Congestion.Type)
if err != nil {
return configError{Field: "congestion.type", Err: err}
}
hyConfig.CongestionConfig.Type = normalizedType
if normalizedType == congestionTypeBBR {
normalizedProfile, err := normalizeBBRProfile(c.Congestion.BBRProfile)
if err != nil {
return configError{Field: "congestion.bbrProfile", Err: err}
}
hyConfig.CongestionConfig.BBRProfile = normalizedProfile
}
return nil
}
func (c *clientConfig) fillFastOpen(hyConfig *client.Config) error {
hyConfig.FastOpen = c.FastOpen
return nil
@ -457,6 +479,7 @@ func (c *clientConfig) Config() (*client.Config, error) {
c.fillAuth,
c.fillTLSConfig,
c.fillQUICConfig,
c.fillCongestionConfig,
c.fillBandwidthConfig,
c.fillFastOpen,
}

View file

@ -4,6 +4,7 @@ import (
"testing"
"time"
"github.com/apernet/hysteria/core/v2/client"
"github.com/stretchr/testify/assert"
"github.com/spf13/viper"
@ -54,6 +55,10 @@ func TestClientConfig(t *testing.T) {
FdControlUnixSocket: stringRef("test.sock"),
},
},
Congestion: clientConfigCongestion{
Type: "bbr",
BBRProfile: "aggressive",
},
Bandwidth: clientConfigBandwidth{
Up: "200 mbps",
Down: "1 gbps",
@ -197,6 +202,46 @@ func TestClientConfigURI(t *testing.T) {
}
}
func TestClientFillCongestionConfig(t *testing.T) {
t.Run("defaults to bbr standard", func(t *testing.T) {
hyConfig := &client.Config{}
err := (&clientConfig{}).fillCongestionConfig(hyConfig)
assert.NoError(t, err)
assert.Equal(t, "bbr", hyConfig.CongestionConfig.Type)
assert.Equal(t, "standard", hyConfig.CongestionConfig.BBRProfile)
})
t.Run("reno ignores bbr profile", func(t *testing.T) {
hyConfig := &client.Config{}
err := (&clientConfig{
Congestion: clientConfigCongestion{
Type: "reno",
BBRProfile: "definitely-invalid",
},
}).fillCongestionConfig(hyConfig)
assert.NoError(t, err)
assert.Equal(t, "reno", hyConfig.CongestionConfig.Type)
assert.Empty(t, hyConfig.CongestionConfig.BBRProfile)
})
t.Run("rejects invalid type", func(t *testing.T) {
err := (&clientConfig{
Congestion: clientConfigCongestion{Type: "cubic"},
}).fillCongestionConfig(&client.Config{})
assert.EqualError(t, err, `invalid config: congestion.type: unsupported congestion type "cubic"`)
})
t.Run("rejects invalid bbr profile", func(t *testing.T) {
err := (&clientConfig{
Congestion: clientConfigCongestion{
Type: "bbr",
BBRProfile: "turbo",
},
}).fillCongestionConfig(&client.Config{})
assert.EqualError(t, err, `invalid config: congestion.bbrProfile: unsupported BBR profile "turbo"`)
})
}
func stringRef(s string) *string {
return &s
}

View file

@ -33,6 +33,10 @@ quic:
fwmark: 1234
fdControlUnixSocket: test.sock
congestion:
type: bbr
bbrProfile: aggressive
bandwidth:
up: 200 mbps
down: 1 gbps

33
app/cmd/congestion.go Normal file
View file

@ -0,0 +1,33 @@
package cmd
import (
"fmt"
"strings"
)
const (
congestionTypeBBR = "bbr"
congestionTypeReno = "reno"
)
func normalizeCongestionType(congestionType string) (string, error) {
switch normalized := strings.ToLower(congestionType); normalized {
case "", congestionTypeBBR:
return congestionTypeBBR, nil
case congestionTypeReno:
return congestionTypeReno, nil
default:
return "", fmt.Errorf("unsupported congestion type %q", congestionType)
}
}
func normalizeBBRProfile(profile string) (string, error) {
switch normalized := strings.ToLower(profile); normalized {
case "", "standard":
return "standard", nil
case "conservative", "aggressive":
return normalized, nil
default:
return "", fmt.Errorf("unsupported BBR profile %q", profile)
}
}

View file

@ -60,6 +60,7 @@ type serverConfig struct {
TLS *serverConfigTLS `mapstructure:"tls"`
ACME *serverConfigACME `mapstructure:"acme"`
QUIC serverConfigQUIC `mapstructure:"quic"`
Congestion serverConfigCongestion `mapstructure:"congestion"`
Bandwidth serverConfigBandwidth `mapstructure:"bandwidth"`
IgnoreClientBandwidth bool `mapstructure:"ignoreClientBandwidth"`
SpeedTest bool `mapstructure:"speedTest"`
@ -140,6 +141,11 @@ type serverConfigBandwidth struct {
Down string `mapstructure:"down"`
}
type serverConfigCongestion struct {
Type string `mapstructure:"type"`
BBRProfile string `mapstructure:"bbrProfile"`
}
type serverConfigAuthHTTP struct {
URL string `mapstructure:"url"`
Insecure bool `mapstructure:"insecure"`
@ -740,6 +746,22 @@ func (c *serverConfig) fillBandwidthConfig(hyConfig *server.Config) error {
return nil
}
func (c *serverConfig) fillCongestionConfig(hyConfig *server.Config) error {
normalizedType, err := normalizeCongestionType(c.Congestion.Type)
if err != nil {
return configError{Field: "congestion.type", Err: err}
}
hyConfig.CongestionConfig.Type = normalizedType
if normalizedType == congestionTypeBBR {
normalizedProfile, err := normalizeBBRProfile(c.Congestion.BBRProfile)
if err != nil {
return configError{Field: "congestion.bbrProfile", Err: err}
}
hyConfig.CongestionConfig.BBRProfile = normalizedProfile
}
return nil
}
func (c *serverConfig) fillIgnoreClientBandwidth(hyConfig *server.Config) error {
hyConfig.IgnoreClientBandwidth = c.IgnoreClientBandwidth
return nil
@ -918,6 +940,7 @@ func (c *serverConfig) Config() (*server.Config, error) {
c.fillQUICConfig,
c.fillRequestHook,
c.fillOutboundConfig,
c.fillCongestionConfig,
c.fillBandwidthConfig,
c.fillIgnoreClientBandwidth,
c.fillDisableUDP,

View file

@ -4,6 +4,7 @@ import (
"testing"
"time"
"github.com/apernet/hysteria/core/v2/server"
"github.com/stretchr/testify/assert"
"github.com/spf13/viper"
@ -68,6 +69,10 @@ func TestServerConfig(t *testing.T) {
MaxIncomingStreams: 256,
DisablePathMTUDiscovery: true,
},
Congestion: serverConfigCongestion{
Type: "reno",
BBRProfile: "aggressive",
},
Bandwidth: serverConfigBandwidth{
Up: "500 mbps",
Down: "100 mbps",
@ -189,3 +194,43 @@ func TestServerConfig(t *testing.T) {
},
})
}
func TestServerFillCongestionConfig(t *testing.T) {
t.Run("defaults to bbr standard", func(t *testing.T) {
hyConfig := &server.Config{}
err := (&serverConfig{}).fillCongestionConfig(hyConfig)
assert.NoError(t, err)
assert.Equal(t, "bbr", hyConfig.CongestionConfig.Type)
assert.Equal(t, "standard", hyConfig.CongestionConfig.BBRProfile)
})
t.Run("reno ignores bbr profile", func(t *testing.T) {
hyConfig := &server.Config{}
err := (&serverConfig{
Congestion: serverConfigCongestion{
Type: "reno",
BBRProfile: "invalid",
},
}).fillCongestionConfig(hyConfig)
assert.NoError(t, err)
assert.Equal(t, "reno", hyConfig.CongestionConfig.Type)
assert.Empty(t, hyConfig.CongestionConfig.BBRProfile)
})
t.Run("rejects invalid type", func(t *testing.T) {
err := (&serverConfig{
Congestion: serverConfigCongestion{Type: "cubic"},
}).fillCongestionConfig(&server.Config{})
assert.EqualError(t, err, `invalid config: congestion.type: unsupported congestion type "cubic"`)
})
t.Run("rejects invalid bbr profile", func(t *testing.T) {
err := (&serverConfig{
Congestion: serverConfigCongestion{
Type: "bbr",
BBRProfile: "turbo",
},
}).fillCongestionConfig(&server.Config{})
assert.EqualError(t, err, `invalid config: congestion.bbrProfile: unsupported BBR profile "turbo"`)
})
}

View file

@ -43,6 +43,10 @@ quic:
maxIncomingStreams: 256
disablePathMTUDiscovery: true
congestion:
type: reno
bbrProfile: aggressive
bandwidth:
up: 500 mbps
down: 100 mbps

View file

@ -134,8 +134,8 @@ func (c *clientImpl) connect() (*HandshakeInfo, error) {
var actualTx uint64
if authResp.RxAuto {
// Server asks client to use bandwidth detection,
// ignore local bandwidth config and use BBR
congestion.UseBBR(conn)
// ignore local bandwidth config and use the configured congestion controller.
congestion.UseConfigured(conn, c.config.CongestionConfig.Type, c.config.CongestionConfig.BBRProfile)
} else {
// actualTx = min(serverRx, clientTx)
actualTx = authResp.Rx
@ -146,8 +146,8 @@ func (c *clientImpl) connect() (*HandshakeInfo, error) {
if actualTx > 0 {
congestion.UseBrutal(conn, actualTx)
} else {
// We don't know our own bandwidth either, use BBR
congestion.UseBBR(conn)
// We don't know our own bandwidth either, use the configured congestion controller.
congestion.UseConfigured(conn, c.config.CongestionConfig.Type, c.config.CongestionConfig.BBRProfile)
}
}
_ = resp.Body.Close()

View file

@ -7,6 +7,7 @@ import (
"time"
"github.com/apernet/hysteria/core/v2/errors"
"github.com/apernet/hysteria/core/v2/internal/congestion"
"github.com/apernet/hysteria/core/v2/internal/pmtud"
)
@ -23,6 +24,7 @@ type Config struct {
Auth string
TLSConfig TLSConfig
QUICConfig QUICConfig
CongestionConfig CongestionConfig
BandwidthConfig BandwidthConfig
FastOpen bool
@ -72,6 +74,17 @@ func (c *Config) verifyAndFill() error {
return errors.ConfigError{Field: "QUICConfig.KeepAlivePeriod", Reason: "must be between 2s and 60s"}
}
c.QUICConfig.DisablePathMTUDiscovery = c.QUICConfig.DisablePathMTUDiscovery || pmtud.DisablePathMTUDiscovery
var err error
c.CongestionConfig.Type, err = congestion.NormalizeType(c.CongestionConfig.Type)
if err != nil {
return errors.ConfigError{Field: "CongestionConfig.Type", Reason: err.Error()}
}
if c.CongestionConfig.Type == congestion.TypeBBR {
c.CongestionConfig.BBRProfile, err = congestion.NormalizeBBRProfile(c.CongestionConfig.BBRProfile)
if err != nil {
return errors.ConfigError{Field: "CongestionConfig.BBRProfile", Reason: err.Error()}
}
}
c.filled = true
return nil
@ -107,6 +120,11 @@ type QUICConfig struct {
DisablePathMTUDiscovery bool // The server may still override this to true on unsupported platforms.
}
type CongestionConfig struct {
Type string
BBRProfile string
}
// BandwidthConfig describes the maximum bandwidth that the server can use, in bytes per second.
type BandwidthConfig struct {
MaxTx uint64

View file

@ -6,6 +6,7 @@ import (
"net"
"os"
"strconv"
"strings"
"time"
"github.com/apernet/quic-go/congestion"
@ -93,6 +94,76 @@ const (
bbrRecoveryStateGrowth
)
type Profile string
const (
ProfileConservative Profile = "conservative"
ProfileStandard Profile = "standard"
ProfileAggressive Profile = "aggressive"
)
type profileConfig struct {
highGain float64
highCwndGain float64
congestionWindowGainConstant float64
numStartupRtts int64
drainToTarget bool
detectOvershooting bool
bytesLostMultiplier uint8
enableAckAggregationStartup bool
expireAckAggregationStartup bool
enableOverestimateAvoidance bool
reduceExtraAckedOnBandwidthIncrease bool
}
func ParseProfile(profile string) (Profile, error) {
switch normalized := strings.ToLower(profile); normalized {
case "", string(ProfileStandard):
return ProfileStandard, nil
case string(ProfileConservative):
return ProfileConservative, nil
case string(ProfileAggressive):
return ProfileAggressive, nil
default:
return "", fmt.Errorf("unsupported BBR profile %q", profile)
}
}
func configForProfile(profile Profile) profileConfig {
switch profile {
case ProfileConservative:
return profileConfig{
highGain: 2.25,
highCwndGain: 1.75,
congestionWindowGainConstant: 1.75,
numStartupRtts: 2,
drainToTarget: true,
detectOvershooting: true,
bytesLostMultiplier: 1,
enableOverestimateAvoidance: true,
reduceExtraAckedOnBandwidthIncrease: true,
}
case ProfileAggressive:
return profileConfig{
highGain: 3.0,
highCwndGain: 2.25,
congestionWindowGainConstant: 2.5,
numStartupRtts: 4,
bytesLostMultiplier: 2,
enableAckAggregationStartup: true,
expireAckAggregationStartup: true,
}
default:
return profileConfig{
highGain: defaultHighGain,
highCwndGain: derivedHighCWNDGain,
congestionWindowGainConstant: 2.0,
numStartupRtts: roundTripsWithoutGrowthBeforeExitingStartup,
bytesLostMultiplier: 2,
}
}
}
type bbrSender struct {
rttStats congestion.RTTStatsProvider
clock Clock
@ -141,6 +212,9 @@ type bbrSender struct {
// The smallest value the |congestion_window_| can achieve.
minCongestionWindow congestion.ByteCount
// The BBR profile used by the sender.
profile Profile
// The pacing gain applied during the STARTUP phase.
highGain float64
@ -247,12 +321,14 @@ var _ congestion.CongestionControl = &bbrSender{}
func NewBbrSender(
clock Clock,
initialMaxDatagramSize congestion.ByteCount,
profile Profile,
) *bbrSender {
return newBbrSender(
clock,
initialMaxDatagramSize,
initialCongestionWindowPackets*initialMaxDatagramSize,
congestion.MaxCongestionWindowPackets*initialMaxDatagramSize,
profile,
)
}
@ -261,6 +337,7 @@ func newBbrSender(
initialMaxDatagramSize,
initialCongestionWindow,
initialMaxCongestionWindow congestion.ByteCount,
profile Profile,
) *bbrSender {
debug, _ := strconv.ParseBool(os.Getenv(debugEnv))
b := &bbrSender{
@ -274,8 +351,9 @@ func newBbrSender(
initialCongestionWindow: initialCongestionWindow,
maxCongestionWindow: initialMaxCongestionWindow,
minCongestionWindow: minCongestionWindowForMaxDatagramSize(initialMaxDatagramSize),
profile: ProfileStandard,
highGain: defaultHighGain,
highCwndGain: defaultHighGain,
highCwndGain: derivedHighCWNDGain,
drainGain: 1.0 / defaultHighGain,
pacingGain: 1.0,
congestionWindowGain: 1.0,
@ -291,13 +369,38 @@ func newBbrSender(
debug: debug,
}
b.pacer = common.NewPacer(b.bandwidthForPacer)
b.applyProfile(profile)
if b.debug {
b.debugPrint("Profile: %s", b.profile)
}
b.enterStartupMode(b.clock.Now())
b.setHighCwndGain(derivedHighCWNDGain)
return b
}
func (b *bbrSender) applyProfile(profile Profile) {
if profile == "" {
profile = ProfileStandard
}
cfg := configForProfile(profile)
b.profile = profile
b.highGain = cfg.highGain
b.highCwndGain = cfg.highCwndGain
b.drainGain = 1.0 / cfg.highGain
b.congestionWindowGainConstant = cfg.congestionWindowGainConstant
b.numStartupRtts = cfg.numStartupRtts
b.drainToTarget = cfg.drainToTarget
b.detectOvershooting = cfg.detectOvershooting
b.bytesLostMultiplierWhileDetectingOvershooting = cfg.bytesLostMultiplier
b.enableAckAggregationDuringStartup = cfg.enableAckAggregationStartup
b.expireAckAggregationInStartup = cfg.expireAckAggregationStartup
if cfg.enableOverestimateAvoidance {
b.sampler.EnableOverestimateAvoidance()
}
b.sampler.SetReduceExtraAckedOnBandwidthIncrease(cfg.reduceExtraAckedOnBandwidthIncrease)
}
func minCongestionWindowForMaxDatagramSize(maxDatagramSize congestion.ByteCount) congestion.ByteCount {
return minCongestionWindowPackets * maxDatagramSize
}

View file

@ -18,6 +18,7 @@ func TestSetMaxDatagramSizeRescalesPacketSizedWindows(t *testing.T) {
oldMaxDatagramSize,
initialCongestionWindowPackets*oldMaxDatagramSize,
maxCongestionWindowPackets*oldMaxDatagramSize,
ProfileStandard,
)
b.congestionWindow = b.initialCongestionWindow
@ -33,7 +34,7 @@ func TestSetMaxDatagramSizeClampsCongestionWindow(t *testing.T) {
const oldMaxDatagramSize = congestion.ByteCount(1000)
const newMaxDatagramSize = congestion.ByteCount(1400)
b := NewBbrSender(DefaultClock{}, oldMaxDatagramSize)
b := NewBbrSender(DefaultClock{}, oldMaxDatagramSize, ProfileStandard)
b.congestionWindow = b.minCongestionWindow + oldMaxDatagramSize
b.recoveryWindow = b.minCongestionWindow + oldMaxDatagramSize
@ -42,3 +43,88 @@ func TestSetMaxDatagramSizeClampsCongestionWindow(t *testing.T) {
require.Equal(t, b.minCongestionWindow, b.congestionWindow)
require.Equal(t, b.minCongestionWindow, b.recoveryWindow)
}
func TestNewBbrSenderAppliesProfiles(t *testing.T) {
testCases := []struct {
name string
profile Profile
highGain float64
highCwndGain float64
congestionWindowGainConstant float64
numStartupRtts int64
drainToTarget bool
detectOvershooting bool
bytesLostMultiplier uint8
enableAckAggregationDuringStartup bool
expireAckAggregationInStartup bool
enableOverestimateAvoidance bool
reduceExtraAckedOnBandwidthIncrease bool
}{
{
name: "standard",
profile: ProfileStandard,
highGain: defaultHighGain,
highCwndGain: derivedHighCWNDGain,
congestionWindowGainConstant: 2.0,
numStartupRtts: roundTripsWithoutGrowthBeforeExitingStartup,
bytesLostMultiplier: 2,
},
{
name: "conservative",
profile: ProfileConservative,
highGain: 2.25,
highCwndGain: 1.75,
congestionWindowGainConstant: 1.75,
numStartupRtts: 2,
drainToTarget: true,
detectOvershooting: true,
bytesLostMultiplier: 1,
enableOverestimateAvoidance: true,
reduceExtraAckedOnBandwidthIncrease: true,
},
{
name: "aggressive",
profile: ProfileAggressive,
highGain: 3.0,
highCwndGain: 2.25,
congestionWindowGainConstant: 2.5,
numStartupRtts: 4,
bytesLostMultiplier: 2,
enableAckAggregationDuringStartup: true,
expireAckAggregationInStartup: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
b := NewBbrSender(DefaultClock{}, congestion.InitialPacketSize, tc.profile)
require.Equal(t, tc.profile, b.profile)
require.Equal(t, tc.highGain, b.highGain)
require.Equal(t, tc.highCwndGain, b.highCwndGain)
require.Equal(t, tc.congestionWindowGainConstant, b.congestionWindowGainConstant)
require.Equal(t, tc.numStartupRtts, b.numStartupRtts)
require.Equal(t, tc.drainToTarget, b.drainToTarget)
require.Equal(t, tc.detectOvershooting, b.detectOvershooting)
require.Equal(t, tc.bytesLostMultiplier, b.bytesLostMultiplierWhileDetectingOvershooting)
require.Equal(t, tc.enableAckAggregationDuringStartup, b.enableAckAggregationDuringStartup)
require.Equal(t, tc.expireAckAggregationInStartup, b.expireAckAggregationInStartup)
require.Equal(t, tc.enableOverestimateAvoidance, b.sampler.IsOverestimateAvoidanceEnabled())
require.Equal(t, tc.reduceExtraAckedOnBandwidthIncrease, b.sampler.maxAckHeightTracker.reduceExtraAckedOnBandwidthIncrease)
require.Equal(t, b.highGain, b.pacingGain)
require.Equal(t, b.highCwndGain, b.congestionWindowGain)
})
}
}
func TestParseProfile(t *testing.T) {
profile, err := ParseProfile("")
require.NoError(t, err)
require.Equal(t, ProfileStandard, profile)
profile, err = ParseProfile("Aggressive")
require.NoError(t, err)
require.Equal(t, ProfileAggressive, profile)
_, err = ParseProfile("turbo")
require.EqualError(t, err, `unsupported BBR profile "turbo"`)
}

View file

@ -1,18 +1,55 @@
package congestion
import (
"fmt"
"strings"
"github.com/apernet/hysteria/core/v2/internal/congestion/bbr"
"github.com/apernet/hysteria/core/v2/internal/congestion/brutal"
"github.com/apernet/quic-go"
)
func UseBBR(conn *quic.Conn) {
const (
TypeBBR = "bbr"
TypeReno = "reno"
)
func NormalizeType(congestionType string) (string, error) {
switch normalized := strings.ToLower(congestionType); normalized {
case "", TypeBBR:
return TypeBBR, nil
case TypeReno:
return TypeReno, nil
default:
return "", fmt.Errorf("unsupported congestion type %q", congestionType)
}
}
func NormalizeBBRProfile(profile string) (string, error) {
normalized, err := bbr.ParseProfile(profile)
if err != nil {
return "", err
}
return string(normalized), nil
}
func UseBBR(conn *quic.Conn, profile bbr.Profile) {
conn.SetCongestionControl(bbr.NewBbrSender(
bbr.DefaultClock{},
bbr.GetInitialPacketSize(conn.RemoteAddr()),
profile,
))
}
func UseBrutal(conn *quic.Conn, tx uint64) {
conn.SetCongestionControl(brutal.NewBrutalSender(tx))
}
func UseConfigured(conn *quic.Conn, congestionType, bbrProfile string) {
switch congestionType {
case TypeReno:
return
default:
UseBBR(conn, bbr.Profile(bbrProfile))
}
}

View file

@ -9,6 +9,7 @@ import (
"time"
"github.com/apernet/hysteria/core/v2/errors"
"github.com/apernet/hysteria/core/v2/internal/congestion"
"github.com/apernet/hysteria/core/v2/internal/pmtud"
"github.com/apernet/hysteria/core/v2/internal/utils"
"github.com/apernet/quic-go"
@ -28,6 +29,7 @@ type Config struct {
Conn net.PacketConn
RequestHook RequestHook
Outbound Outbound
CongestionConfig CongestionConfig
BandwidthConfig BandwidthConfig
IgnoreClientBandwidth bool
DisableUDP bool
@ -75,6 +77,17 @@ func (c *Config) fill() error {
return errors.ConfigError{Field: "QUICConfig.MaxIncomingStreams", Reason: "must be at least 8"}
}
c.QUICConfig.DisablePathMTUDiscovery = c.QUICConfig.DisablePathMTUDiscovery || pmtud.DisablePathMTUDiscovery
var err error
c.CongestionConfig.Type, err = congestion.NormalizeType(c.CongestionConfig.Type)
if err != nil {
return errors.ConfigError{Field: "CongestionConfig.Type", Reason: err.Error()}
}
if c.CongestionConfig.Type == congestion.TypeBBR {
c.CongestionConfig.BBRProfile, err = congestion.NormalizeBBRProfile(c.CongestionConfig.BBRProfile)
if err != nil {
return errors.ConfigError{Field: "CongestionConfig.BBRProfile", Reason: err.Error()}
}
}
if c.Conn == nil {
return errors.ConfigError{Field: "Conn", Reason: "must be set"}
}
@ -116,6 +129,11 @@ type QUICConfig struct {
DisablePathMTUDiscovery bool // The server may still override this to true on unsupported platforms.
}
type CongestionConfig struct {
Type string
BBRProfile string
}
// RequestHook allows filtering and modifying requests before the server connects to the remote.
// A request will only be hooked if Check returns true.
// The returned byte slice, if not empty, will be sent to the remote before proxying - this is

View file

@ -152,8 +152,8 @@ func (h *h3sHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.authenticated = true
h.authID = id
if h.config.IgnoreClientBandwidth {
// Ignore client bandwidth, always use BBR
congestion.UseBBR(h.conn)
// Ignore client bandwidth and use the configured congestion controller.
congestion.UseConfigured(h.conn, h.config.CongestionConfig.Type, h.config.CongestionConfig.BBRProfile)
actualTx = 0
} else {
// actualTx = min(serverTx, clientRx)
@ -165,8 +165,8 @@ func (h *h3sHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if actualTx > 0 {
congestion.UseBrutal(h.conn, actualTx)
} else {
// Client doesn't know its own bandwidth, use BBR
congestion.UseBBR(h.conn)
// Client doesn't know its own bandwidth, use the configured congestion controller.
congestion.UseConfigured(h.conn, h.config.CongestionConfig.Type, h.config.CongestionConfig.BBRProfile)
}
}
// Auth OK, send response