Преглед изворни кода

Hysteria & XHTTP/3: Unified Finalmask's `quicParams` to set `congestion`, `brutalUp`, `brutalDown`, `udpHop` (`ports` & `interval`), etc. (#5772)

https://github.com/XTLS/Xray-core/pull/5772#issuecomment-4023006179
LjhAUMEM пре 3 месеци
родитељ
комит
0321cdd0d2

+ 96 - 112
infra/conf/transport_internet.go

@@ -7,6 +7,7 @@ import (
 	"encoding/json"
 	"math"
 	"net/url"
+	"os"
 	"runtime"
 	"strconv"
 	"strings"
@@ -500,7 +501,7 @@ func (b Bandwidth) Bps() (uint64, error) {
 }
 
 type UdpHop struct {
-	PortList json.RawMessage `json:"port"`
+	PortList json.RawMessage `json:"ports"`
 	Interval *Int32Range     `json:"interval"`
 }
 
@@ -519,21 +520,13 @@ type Masquerade struct {
 }
 
 type HysteriaConfig struct {
-	Version    int32     `json:"version"`
-	Auth       string    `json:"auth"`
-	Congestion string    `json:"congestion"`
-	Up         Bandwidth `json:"up"`
-	Down       Bandwidth `json:"down"`
-	UdpHop     UdpHop    `json:"udphop"`
-
-	InitStreamReceiveWindow     uint64 `json:"initStreamReceiveWindow"`
-	MaxStreamReceiveWindow      uint64 `json:"maxStreamReceiveWindow"`
-	InitConnectionReceiveWindow uint64 `json:"initConnectionReceiveWindow"`
-	MaxConnectionReceiveWindow  uint64 `json:"maxConnectionReceiveWindow"`
-	MaxIdleTimeout              int64  `json:"maxIdleTimeout"`
-	KeepAlivePeriod             int64  `json:"keepAlivePeriod"`
-	DisablePathMTUDiscovery     bool   `json:"disablePathMTUDiscovery"`
-	MaxIncomingStreams          int64  `json:"maxIncomingStreams"`
+	Version int32  `json:"version"`
+	Auth    string `json:"auth"`
+
+	Congestion *string    `json:"congestion"`
+	Up         *Bandwidth `json:"up"`
+	Down       *Bandwidth `json:"down"`
+	UdpHop     *UdpHop    `json:"udphop"`
 
 	UdpIdleTimeout int64      `json:"udpIdleTimeout"`
 	Masquerade     Masquerade `json:"masquerade"`
@@ -544,62 +537,10 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
 		return nil, errors.New("version != 2")
 	}
 
-	up, err := c.Up.Bps()
-	if err != nil {
-		return nil, err
-	}
-	down, err := c.Down.Bps()
-	if err != nil {
-		return nil, err
-	}
-
-	c.Congestion = strings.ToLower(c.Congestion)
-	if c.Congestion == "force-brutal" && up == 0 {
-		return nil, errors.New("force-brutal require up")
-	}
-
-	var hop *PortList
-	if err := json.Unmarshal(c.UdpHop.PortList, &hop); err != nil {
-		hop = &PortList{}
-	}
-
-	var inertvalMin, inertvalMax int64
-	if c.UdpHop.Interval != nil {
-		inertvalMin = int64(c.UdpHop.Interval.From)
-		inertvalMax = int64(c.UdpHop.Interval.To)
-	}
-
-	if up > 0 && up < 65536 {
-		return nil, errors.New("Up must be at least 65536 bytes per second")
-	}
-	if down > 0 && down < 65536 {
-		return nil, errors.New("Down must be at least 65536 bytes per second")
-	}
-	if (inertvalMin != 0 && inertvalMin < 5) || (inertvalMax != 0 && inertvalMax < 5) {
-		return nil, errors.New("Interval must be at least 5")
+	if c.Congestion != nil || c.Up != nil || c.Down != nil || c.UdpHop != nil {
+		errors.LogWarning(context.Background(), "congestion & up & down & udphop move to finalmask/quicParams")
 	}
 
-	if c.InitStreamReceiveWindow > 0 && c.InitStreamReceiveWindow < 16384 {
-		return nil, errors.New("InitStreamReceiveWindow must be at least 16384")
-	}
-	if c.MaxStreamReceiveWindow > 0 && c.MaxStreamReceiveWindow < 16384 {
-		return nil, errors.New("MaxStreamReceiveWindow must be at least 16384")
-	}
-	if c.InitConnectionReceiveWindow > 0 && c.InitConnectionReceiveWindow < 16384 {
-		return nil, errors.New("InitConnectionReceiveWindow must be at least 16384")
-	}
-	if c.MaxConnectionReceiveWindow > 0 && c.MaxConnectionReceiveWindow < 16384 {
-		return nil, errors.New("MaxConnectionReceiveWindow must be at least 16384")
-	}
-	if c.MaxIdleTimeout != 0 && (c.MaxIdleTimeout < 4 || c.MaxIdleTimeout > 120) {
-		return nil, errors.New("MaxIdleTimeout must be between 4 and 120")
-	}
-	if c.KeepAlivePeriod != 0 && (c.KeepAlivePeriod < 2 || c.KeepAlivePeriod > 60) {
-		return nil, errors.New("KeepAlivePeriod must be between 2 and 60")
-	}
-	if c.MaxIncomingStreams != 0 && c.MaxIncomingStreams < 8 {
-		return nil, errors.New("MaxIncomingStreams must be at least 8")
-	}
 	if c.UdpIdleTimeout != 0 && (c.UdpIdleTimeout < 2 || c.UdpIdleTimeout > 600) {
 		return nil, errors.New("UdpIdleTimeout must be between 2 and 600")
 	}
@@ -607,20 +548,6 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
 	config := &hysteria.Config{}
 	config.Version = c.Version
 	config.Auth = c.Auth
-	config.Congestion = c.Congestion
-	config.Up = up
-	config.Down = down
-	config.Ports = hop.Build().Ports()
-	config.IntervalMin = inertvalMin
-	config.IntervalMax = inertvalMax
-	config.InitStreamReceiveWindow = c.InitStreamReceiveWindow
-	config.MaxStreamReceiveWindow = c.MaxStreamReceiveWindow
-	config.InitConnReceiveWindow = c.InitConnectionReceiveWindow
-	config.MaxConnReceiveWindow = c.MaxConnectionReceiveWindow
-	config.MaxIdleTimeout = c.MaxIdleTimeout
-	config.KeepAlivePeriod = c.KeepAlivePeriod
-	config.DisablePathMtuDiscovery = c.DisablePathMTUDiscovery
-	config.MaxIncomingStreams = c.MaxIncomingStreams
 	config.UdpIdleTimeout = c.UdpIdleTimeout
 	config.MasqType = c.Masquerade.Type
 	config.MasqFile = c.Masquerade.Dir
@@ -631,27 +558,6 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
 	config.MasqStringHeaders = c.Masquerade.Headers
 	config.MasqStringStatusCode = c.Masquerade.StatusCode
 
-	if config.InitStreamReceiveWindow == 0 {
-		config.InitStreamReceiveWindow = 8388608
-	}
-	if config.MaxStreamReceiveWindow == 0 {
-		config.MaxStreamReceiveWindow = 8388608
-	}
-	if config.InitConnReceiveWindow == 0 {
-		config.InitConnReceiveWindow = 8388608 * 5 / 2
-	}
-	if config.MaxConnReceiveWindow == 0 {
-		config.MaxConnReceiveWindow = 8388608 * 5 / 2
-	}
-	if config.MaxIdleTimeout == 0 {
-		config.MaxIdleTimeout = 30
-	}
-	// if config.KeepAlivePeriod == 0 {
-	// 	config.KeepAlivePeriod = 10
-	// }
-	if config.MaxIncomingStreams == 0 {
-		config.MaxIncomingStreams = 1024
-	}
 	if config.UdpIdleTimeout == 0 {
 		config.UdpIdleTimeout = 60
 	}
@@ -722,8 +628,19 @@ func (c *TLSCertConfig) Build() (*tls.Certificate, error) {
 }
 
 type QuicParamsConfig struct {
-	Congestion string    `json:"congestion"`
-	Up         Bandwidth `json:"up"`
+	Congestion                  string    `json:"congestion"`
+	Debug                       bool      `json:"debug"`
+	BrutalUp                    Bandwidth `json:"brutalUp"`
+	BrutalDown                  Bandwidth `json:"brutalDown"`
+	UdpHop                      UdpHop    `json:"udpHop"`
+	InitStreamReceiveWindow     uint64    `json:"initStreamReceiveWindow"`
+	MaxStreamReceiveWindow      uint64    `json:"maxStreamReceiveWindow"`
+	InitConnectionReceiveWindow uint64    `json:"initConnectionReceiveWindow"`
+	MaxConnectionReceiveWindow  uint64    `json:"maxConnectionReceiveWindow"`
+	MaxIdleTimeout              int64     `json:"maxIdleTimeout"`
+	KeepAlivePeriod             int64     `json:"keepAlivePeriod"`
+	DisablePathMTUDiscovery     bool      `json:"disablePathMTUDiscovery"`
+	MaxIncomingStreams          int64     `json:"maxIncomingStreams"`
 }
 
 type TLSConfig struct {
@@ -1918,25 +1835,92 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
 			config.Udpmasks = append(config.Udpmasks, serial.ToTypedMessage(u))
 		}
 		if c.FinalMask.QuicParams != nil {
-			up, err := c.FinalMask.QuicParams.Up.Bps()
+			up, err := c.FinalMask.QuicParams.BrutalUp.Bps()
 			if err != nil {
 				return nil, err
 			}
+			down, err := c.FinalMask.QuicParams.BrutalDown.Bps()
+			if err != nil {
+				return nil, err
+			}
+
 			if up > 0 && up < 65536 {
-				return nil, errors.New("Up must be at least 65536 bytes per second")
+				return nil, errors.New("BrutalUp must be at least 65536 bytes per second")
 			}
+			if down > 0 && down < 65536 {
+				return nil, errors.New("BrutalDown must be at least 65536 bytes per second")
+			}
+
+			c.FinalMask.QuicParams.Congestion = strings.ToLower(c.FinalMask.QuicParams.Congestion)
 			switch c.FinalMask.QuicParams.Congestion {
-			case "", "bbr", "reno":
+			case "", "brutal", "reno", "bbr":
 			case "force-brutal":
 				if up == 0 {
 					return nil, errors.New("force-brutal requires up")
 				}
 			default:
-				return nil, errors.New("unknown congestion control: ", c.FinalMask.QuicParams.Congestion, ", valid values: bbr, reno, force-brutal")
+				return nil, errors.New("unknown congestion control: ", c.FinalMask.QuicParams.Congestion, ", valid values: reno, bbr, brutal, force-brutal")
+			}
+
+			var hop *PortList
+			if err := json.Unmarshal(c.FinalMask.QuicParams.UdpHop.PortList, &hop); err != nil {
+				hop = &PortList{}
+			}
+
+			var inertvalMin, inertvalMax int64
+			if c.FinalMask.QuicParams.UdpHop.Interval != nil {
+				inertvalMin = int64(c.FinalMask.QuicParams.UdpHop.Interval.From)
+				inertvalMax = int64(c.FinalMask.QuicParams.UdpHop.Interval.To)
 			}
+
+			if (inertvalMin != 0 && inertvalMin < 5) || (inertvalMax != 0 && inertvalMax < 5) {
+				return nil, errors.New("Interval must be at least 5")
+			}
+
+			if c.FinalMask.QuicParams.InitStreamReceiveWindow > 0 && c.FinalMask.QuicParams.InitStreamReceiveWindow < 16384 {
+				return nil, errors.New("InitStreamReceiveWindow must be at least 16384")
+			}
+			if c.FinalMask.QuicParams.MaxStreamReceiveWindow > 0 && c.FinalMask.QuicParams.MaxStreamReceiveWindow < 16384 {
+				return nil, errors.New("MaxStreamReceiveWindow must be at least 16384")
+			}
+			if c.FinalMask.QuicParams.InitConnectionReceiveWindow > 0 && c.FinalMask.QuicParams.InitConnectionReceiveWindow < 16384 {
+				return nil, errors.New("InitConnectionReceiveWindow must be at least 16384")
+			}
+			if c.FinalMask.QuicParams.MaxConnectionReceiveWindow > 0 && c.FinalMask.QuicParams.MaxConnectionReceiveWindow < 16384 {
+				return nil, errors.New("MaxConnectionReceiveWindow must be at least 16384")
+			}
+			if c.FinalMask.QuicParams.MaxIdleTimeout != 0 && (c.FinalMask.QuicParams.MaxIdleTimeout < 4 || c.FinalMask.QuicParams.MaxIdleTimeout > 120) {
+				return nil, errors.New("MaxIdleTimeout must be between 4 and 120")
+			}
+			if c.FinalMask.QuicParams.KeepAlivePeriod != 0 && (c.FinalMask.QuicParams.KeepAlivePeriod < 2 || c.FinalMask.QuicParams.KeepAlivePeriod > 60) {
+				return nil, errors.New("KeepAlivePeriod must be between 2 and 60")
+			}
+			if c.FinalMask.QuicParams.MaxIncomingStreams != 0 && c.FinalMask.QuicParams.MaxIncomingStreams < 8 {
+				return nil, errors.New("MaxIncomingStreams must be at least 8")
+			}
+
+			if c.FinalMask.QuicParams.Debug {
+				os.Setenv("HYSTERIA_BBR_DEBUG", "true")
+				os.Setenv("HYSTERIA_BRUTAL_DEBUG", "true")
+			}
+
 			config.QuicParams = &internet.QuicParams{
 				Congestion: c.FinalMask.QuicParams.Congestion,
-				Up:         up,
+				BrutalUp:   up,
+				BrutalDown: down,
+				UdpHop: &internet.UdpHop{
+					Ports:       hop.Build().Ports(),
+					IntervalMin: inertvalMin,
+					IntervalMax: inertvalMax,
+				},
+				InitStreamReceiveWindow: c.FinalMask.QuicParams.InitStreamReceiveWindow,
+				MaxStreamReceiveWindow:  c.FinalMask.QuicParams.MaxStreamReceiveWindow,
+				InitConnReceiveWindow:   c.FinalMask.QuicParams.InitConnectionReceiveWindow,
+				MaxConnReceiveWindow:    c.FinalMask.QuicParams.MaxConnectionReceiveWindow,
+				MaxIdleTimeout:          c.FinalMask.QuicParams.MaxIdleTimeout,
+				KeepAlivePeriod:         c.FinalMask.QuicParams.KeepAlivePeriod,
+				DisablePathMtuDiscovery: c.FinalMask.QuicParams.DisablePathMTUDiscovery,
+				MaxIncomingStreams:      c.FinalMask.QuicParams.MaxIncomingStreams,
 			}
 		}
 	}

+ 208 - 50
transport/internet/config.pb.go

@@ -206,7 +206,7 @@ func (x SocketConfig_TProxyMode) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use SocketConfig_TProxyMode.Descriptor instead.
 func (SocketConfig_TProxyMode) EnumDescriptor() ([]byte, []int) {
-	return file_transport_internet_config_proto_rawDescGZIP(), []int{5, 0}
+	return file_transport_internet_config_proto_rawDescGZIP(), []int{6, 0}
 }
 
 type TransportConfig struct {
@@ -382,17 +382,87 @@ func (x *StreamConfig) GetSocketSettings() *SocketConfig {
 	return nil
 }
 
-type QuicParams struct {
+type UdpHop struct {
 	state         protoimpl.MessageState `protogen:"open.v1"`
-	Congestion    string                 `protobuf:"bytes,1,opt,name=congestion,proto3" json:"congestion,omitempty"`
-	Up            uint64                 `protobuf:"varint,2,opt,name=up,proto3" json:"up,omitempty"`
+	Ports         []uint32               `protobuf:"varint,1,rep,packed,name=ports,proto3" json:"ports,omitempty"`
+	IntervalMin   int64                  `protobuf:"varint,2,opt,name=interval_min,json=intervalMin,proto3" json:"interval_min,omitempty"`
+	IntervalMax   int64                  `protobuf:"varint,3,opt,name=interval_max,json=intervalMax,proto3" json:"interval_max,omitempty"`
 	unknownFields protoimpl.UnknownFields
 	sizeCache     protoimpl.SizeCache
 }
 
+func (x *UdpHop) Reset() {
+	*x = UdpHop{}
+	mi := &file_transport_internet_config_proto_msgTypes[2]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *UdpHop) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UdpHop) ProtoMessage() {}
+
+func (x *UdpHop) ProtoReflect() protoreflect.Message {
+	mi := &file_transport_internet_config_proto_msgTypes[2]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use UdpHop.ProtoReflect.Descriptor instead.
+func (*UdpHop) Descriptor() ([]byte, []int) {
+	return file_transport_internet_config_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *UdpHop) GetPorts() []uint32 {
+	if x != nil {
+		return x.Ports
+	}
+	return nil
+}
+
+func (x *UdpHop) GetIntervalMin() int64 {
+	if x != nil {
+		return x.IntervalMin
+	}
+	return 0
+}
+
+func (x *UdpHop) GetIntervalMax() int64 {
+	if x != nil {
+		return x.IntervalMax
+	}
+	return 0
+}
+
+type QuicParams struct {
+	state                   protoimpl.MessageState `protogen:"open.v1"`
+	Congestion              string                 `protobuf:"bytes,1,opt,name=congestion,proto3" json:"congestion,omitempty"`
+	BrutalUp                uint64                 `protobuf:"varint,2,opt,name=brutal_up,json=brutalUp,proto3" json:"brutal_up,omitempty"`
+	BrutalDown              uint64                 `protobuf:"varint,3,opt,name=brutal_down,json=brutalDown,proto3" json:"brutal_down,omitempty"`
+	UdpHop                  *UdpHop                `protobuf:"bytes,4,opt,name=udp_hop,json=udpHop,proto3" json:"udp_hop,omitempty"`
+	InitStreamReceiveWindow uint64                 `protobuf:"varint,5,opt,name=init_stream_receive_window,json=initStreamReceiveWindow,proto3" json:"init_stream_receive_window,omitempty"`
+	MaxStreamReceiveWindow  uint64                 `protobuf:"varint,6,opt,name=max_stream_receive_window,json=maxStreamReceiveWindow,proto3" json:"max_stream_receive_window,omitempty"`
+	InitConnReceiveWindow   uint64                 `protobuf:"varint,7,opt,name=init_conn_receive_window,json=initConnReceiveWindow,proto3" json:"init_conn_receive_window,omitempty"`
+	MaxConnReceiveWindow    uint64                 `protobuf:"varint,8,opt,name=max_conn_receive_window,json=maxConnReceiveWindow,proto3" json:"max_conn_receive_window,omitempty"`
+	MaxIdleTimeout          int64                  `protobuf:"varint,9,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3" json:"max_idle_timeout,omitempty"`
+	KeepAlivePeriod         int64                  `protobuf:"varint,10,opt,name=keep_alive_period,json=keepAlivePeriod,proto3" json:"keep_alive_period,omitempty"`
+	DisablePathMtuDiscovery bool                   `protobuf:"varint,11,opt,name=disable_path_mtu_discovery,json=disablePathMtuDiscovery,proto3" json:"disable_path_mtu_discovery,omitempty"`
+	MaxIncomingStreams      int64                  `protobuf:"varint,12,opt,name=max_incoming_streams,json=maxIncomingStreams,proto3" json:"max_incoming_streams,omitempty"`
+	unknownFields           protoimpl.UnknownFields
+	sizeCache               protoimpl.SizeCache
+}
+
 func (x *QuicParams) Reset() {
 	*x = QuicParams{}
-	mi := &file_transport_internet_config_proto_msgTypes[2]
+	mi := &file_transport_internet_config_proto_msgTypes[3]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -404,7 +474,7 @@ func (x *QuicParams) String() string {
 func (*QuicParams) ProtoMessage() {}
 
 func (x *QuicParams) ProtoReflect() protoreflect.Message {
-	mi := &file_transport_internet_config_proto_msgTypes[2]
+	mi := &file_transport_internet_config_proto_msgTypes[3]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -417,7 +487,7 @@ func (x *QuicParams) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use QuicParams.ProtoReflect.Descriptor instead.
 func (*QuicParams) Descriptor() ([]byte, []int) {
-	return file_transport_internet_config_proto_rawDescGZIP(), []int{2}
+	return file_transport_internet_config_proto_rawDescGZIP(), []int{3}
 }
 
 func (x *QuicParams) GetCongestion() string {
@@ -427,9 +497,79 @@ func (x *QuicParams) GetCongestion() string {
 	return ""
 }
 
-func (x *QuicParams) GetUp() uint64 {
+func (x *QuicParams) GetBrutalUp() uint64 {
+	if x != nil {
+		return x.BrutalUp
+	}
+	return 0
+}
+
+func (x *QuicParams) GetBrutalDown() uint64 {
 	if x != nil {
-		return x.Up
+		return x.BrutalDown
+	}
+	return 0
+}
+
+func (x *QuicParams) GetUdpHop() *UdpHop {
+	if x != nil {
+		return x.UdpHop
+	}
+	return nil
+}
+
+func (x *QuicParams) GetInitStreamReceiveWindow() uint64 {
+	if x != nil {
+		return x.InitStreamReceiveWindow
+	}
+	return 0
+}
+
+func (x *QuicParams) GetMaxStreamReceiveWindow() uint64 {
+	if x != nil {
+		return x.MaxStreamReceiveWindow
+	}
+	return 0
+}
+
+func (x *QuicParams) GetInitConnReceiveWindow() uint64 {
+	if x != nil {
+		return x.InitConnReceiveWindow
+	}
+	return 0
+}
+
+func (x *QuicParams) GetMaxConnReceiveWindow() uint64 {
+	if x != nil {
+		return x.MaxConnReceiveWindow
+	}
+	return 0
+}
+
+func (x *QuicParams) GetMaxIdleTimeout() int64 {
+	if x != nil {
+		return x.MaxIdleTimeout
+	}
+	return 0
+}
+
+func (x *QuicParams) GetKeepAlivePeriod() int64 {
+	if x != nil {
+		return x.KeepAlivePeriod
+	}
+	return 0
+}
+
+func (x *QuicParams) GetDisablePathMtuDiscovery() bool {
+	if x != nil {
+		return x.DisablePathMtuDiscovery
+	}
+	return false
+}
+
+func (x *QuicParams) GetMaxIncomingStreams() int64 {
+	if x != nil {
+		return x.MaxIncomingStreams
 	}
 	return 0
 }
@@ -444,7 +584,7 @@ type ProxyConfig struct {
 
 func (x *ProxyConfig) Reset() {
 	*x = ProxyConfig{}
-	mi := &file_transport_internet_config_proto_msgTypes[3]
+	mi := &file_transport_internet_config_proto_msgTypes[4]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -456,7 +596,7 @@ func (x *ProxyConfig) String() string {
 func (*ProxyConfig) ProtoMessage() {}
 
 func (x *ProxyConfig) ProtoReflect() protoreflect.Message {
-	mi := &file_transport_internet_config_proto_msgTypes[3]
+	mi := &file_transport_internet_config_proto_msgTypes[4]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -469,7 +609,7 @@ func (x *ProxyConfig) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use ProxyConfig.ProtoReflect.Descriptor instead.
 func (*ProxyConfig) Descriptor() ([]byte, []int) {
-	return file_transport_internet_config_proto_rawDescGZIP(), []int{3}
+	return file_transport_internet_config_proto_rawDescGZIP(), []int{4}
 }
 
 func (x *ProxyConfig) GetTag() string {
@@ -500,7 +640,7 @@ type CustomSockopt struct {
 
 func (x *CustomSockopt) Reset() {
 	*x = CustomSockopt{}
-	mi := &file_transport_internet_config_proto_msgTypes[4]
+	mi := &file_transport_internet_config_proto_msgTypes[5]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -512,7 +652,7 @@ func (x *CustomSockopt) String() string {
 func (*CustomSockopt) ProtoMessage() {}
 
 func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
-	mi := &file_transport_internet_config_proto_msgTypes[4]
+	mi := &file_transport_internet_config_proto_msgTypes[5]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -525,7 +665,7 @@ func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use CustomSockopt.ProtoReflect.Descriptor instead.
 func (*CustomSockopt) Descriptor() ([]byte, []int) {
-	return file_transport_internet_config_proto_rawDescGZIP(), []int{4}
+	return file_transport_internet_config_proto_rawDescGZIP(), []int{5}
 }
 
 func (x *CustomSockopt) GetSystem() string {
@@ -607,7 +747,7 @@ type SocketConfig struct {
 
 func (x *SocketConfig) Reset() {
 	*x = SocketConfig{}
-	mi := &file_transport_internet_config_proto_msgTypes[5]
+	mi := &file_transport_internet_config_proto_msgTypes[6]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -619,7 +759,7 @@ func (x *SocketConfig) String() string {
 func (*SocketConfig) ProtoMessage() {}
 
 func (x *SocketConfig) ProtoReflect() protoreflect.Message {
-	mi := &file_transport_internet_config_proto_msgTypes[5]
+	mi := &file_transport_internet_config_proto_msgTypes[6]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -632,7 +772,7 @@ func (x *SocketConfig) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use SocketConfig.ProtoReflect.Descriptor instead.
 func (*SocketConfig) Descriptor() ([]byte, []int) {
-	return file_transport_internet_config_proto_rawDescGZIP(), []int{5}
+	return file_transport_internet_config_proto_rawDescGZIP(), []int{6}
 }
 
 func (x *SocketConfig) GetMark() int32 {
@@ -808,7 +948,7 @@ type HappyEyeballsConfig struct {
 
 func (x *HappyEyeballsConfig) Reset() {
 	*x = HappyEyeballsConfig{}
-	mi := &file_transport_internet_config_proto_msgTypes[6]
+	mi := &file_transport_internet_config_proto_msgTypes[7]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -820,7 +960,7 @@ func (x *HappyEyeballsConfig) String() string {
 func (*HappyEyeballsConfig) ProtoMessage() {}
 
 func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {
-	mi := &file_transport_internet_config_proto_msgTypes[6]
+	mi := &file_transport_internet_config_proto_msgTypes[7]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -833,7 +973,7 @@ func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use HappyEyeballsConfig.ProtoReflect.Descriptor instead.
 func (*HappyEyeballsConfig) Descriptor() ([]byte, []int) {
-	return file_transport_internet_config_proto_rawDescGZIP(), []int{6}
+	return file_transport_internet_config_proto_rawDescGZIP(), []int{7}
 }
 
 func (x *HappyEyeballsConfig) GetPrioritizeIpv6() bool {
@@ -884,13 +1024,29 @@ const file_transport_internet_config_proto_rawDesc = "" +
 	"\btcpmasks\x18\v \x03(\v2 .xray.common.serial.TypedMessageR\btcpmasks\x12D\n" +
 	"\vquic_params\x18\f \x01(\v2#.xray.transport.internet.QuicParamsR\n" +
 	"quicParams\x12N\n" +
-	"\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"<\n" +
+	"\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"d\n" +
+	"\x06UdpHop\x12\x14\n" +
+	"\x05ports\x18\x01 \x03(\rR\x05ports\x12!\n" +
+	"\finterval_min\x18\x02 \x01(\x03R\vintervalMin\x12!\n" +
+	"\finterval_max\x18\x03 \x01(\x03R\vintervalMax\"\xd1\x04\n" +
 	"\n" +
 	"QuicParams\x12\x1e\n" +
 	"\n" +
 	"congestion\x18\x01 \x01(\tR\n" +
-	"congestion\x12\x0e\n" +
-	"\x02up\x18\x02 \x01(\x04R\x02up\"Q\n" +
+	"congestion\x12\x1b\n" +
+	"\tbrutal_up\x18\x02 \x01(\x04R\bbrutalUp\x12\x1f\n" +
+	"\vbrutal_down\x18\x03 \x01(\x04R\n" +
+	"brutalDown\x128\n" +
+	"\audp_hop\x18\x04 \x01(\v2\x1f.xray.transport.internet.UdpHopR\x06udpHop\x12;\n" +
+	"\x1ainit_stream_receive_window\x18\x05 \x01(\x04R\x17initStreamReceiveWindow\x129\n" +
+	"\x19max_stream_receive_window\x18\x06 \x01(\x04R\x16maxStreamReceiveWindow\x127\n" +
+	"\x18init_conn_receive_window\x18\a \x01(\x04R\x15initConnReceiveWindow\x125\n" +
+	"\x17max_conn_receive_window\x18\b \x01(\x04R\x14maxConnReceiveWindow\x12(\n" +
+	"\x10max_idle_timeout\x18\t \x01(\x03R\x0emaxIdleTimeout\x12*\n" +
+	"\x11keep_alive_period\x18\n" +
+	" \x01(\x03R\x0fkeepAlivePeriod\x12;\n" +
+	"\x1adisable_path_mtu_discovery\x18\v \x01(\bR\x17disablePathMtuDiscovery\x120\n" +
+	"\x14max_incoming_streams\x18\f \x01(\x03R\x12maxIncomingStreams\"Q\n" +
 	"\vProxyConfig\x12\x10\n" +
 	"\x03tag\x18\x01 \x01(\tR\x03tag\x120\n" +
 	"\x13transportLayerProxy\x18\x02 \x01(\bR\x13transportLayerProxy\"\x93\x01\n" +
@@ -979,40 +1135,42 @@ func file_transport_internet_config_proto_rawDescGZIP() []byte {
 }
 
 var file_transport_internet_config_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
-var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
+var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
 var file_transport_internet_config_proto_goTypes = []any{
 	(DomainStrategy)(0),          // 0: xray.transport.internet.DomainStrategy
 	(AddressPortStrategy)(0),     // 1: xray.transport.internet.AddressPortStrategy
 	(SocketConfig_TProxyMode)(0), // 2: xray.transport.internet.SocketConfig.TProxyMode
 	(*TransportConfig)(nil),      // 3: xray.transport.internet.TransportConfig
 	(*StreamConfig)(nil),         // 4: xray.transport.internet.StreamConfig
-	(*QuicParams)(nil),           // 5: xray.transport.internet.QuicParams
-	(*ProxyConfig)(nil),          // 6: xray.transport.internet.ProxyConfig
-	(*CustomSockopt)(nil),        // 7: xray.transport.internet.CustomSockopt
-	(*SocketConfig)(nil),         // 8: xray.transport.internet.SocketConfig
-	(*HappyEyeballsConfig)(nil),  // 9: xray.transport.internet.HappyEyeballsConfig
-	(*serial.TypedMessage)(nil),  // 10: xray.common.serial.TypedMessage
-	(*net.IPOrDomain)(nil),       // 11: xray.common.net.IPOrDomain
+	(*UdpHop)(nil),               // 5: xray.transport.internet.UdpHop
+	(*QuicParams)(nil),           // 6: xray.transport.internet.QuicParams
+	(*ProxyConfig)(nil),          // 7: xray.transport.internet.ProxyConfig
+	(*CustomSockopt)(nil),        // 8: xray.transport.internet.CustomSockopt
+	(*SocketConfig)(nil),         // 9: xray.transport.internet.SocketConfig
+	(*HappyEyeballsConfig)(nil),  // 10: xray.transport.internet.HappyEyeballsConfig
+	(*serial.TypedMessage)(nil),  // 11: xray.common.serial.TypedMessage
+	(*net.IPOrDomain)(nil),       // 12: xray.common.net.IPOrDomain
 }
 var file_transport_internet_config_proto_depIdxs = []int32{
-	10, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
-	11, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain
+	11, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
+	12, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain
 	3,  // 2: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig
-	10, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
-	10, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage
-	10, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage
-	5,  // 6: xray.transport.internet.StreamConfig.quic_params:type_name -> xray.transport.internet.QuicParams
-	8,  // 7: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
-	2,  // 8: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
-	0,  // 9: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
-	7,  // 10: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
-	1,  // 11: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy
-	9,  // 12: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig
-	13, // [13:13] is the sub-list for method output_type
-	13, // [13:13] is the sub-list for method input_type
-	13, // [13:13] is the sub-list for extension type_name
-	13, // [13:13] is the sub-list for extension extendee
-	0,  // [0:13] is the sub-list for field type_name
+	11, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
+	11, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage
+	11, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage
+	6,  // 6: xray.transport.internet.StreamConfig.quic_params:type_name -> xray.transport.internet.QuicParams
+	9,  // 7: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
+	5,  // 8: xray.transport.internet.QuicParams.udp_hop:type_name -> xray.transport.internet.UdpHop
+	2,  // 9: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
+	0,  // 10: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
+	8,  // 11: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
+	1,  // 12: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy
+	10, // 13: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig
+	14, // [14:14] is the sub-list for method output_type
+	14, // [14:14] is the sub-list for method input_type
+	14, // [14:14] is the sub-list for extension type_name
+	14, // [14:14] is the sub-list for extension extendee
+	0,  // [0:14] is the sub-list for field type_name
 }
 
 func init() { file_transport_internet_config_proto_init() }
@@ -1026,7 +1184,7 @@ func file_transport_internet_config_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_config_proto_rawDesc), len(file_transport_internet_config_proto_rawDesc)),
 			NumEnums:      3,
-			NumMessages:   7,
+			NumMessages:   8,
 			NumExtensions: 0,
 			NumServices:   0,
 		},

+ 17 - 1
transport/internet/config.proto

@@ -64,9 +64,25 @@ message StreamConfig {
   SocketConfig socket_settings = 6;
 }
 
+message UdpHop {
+  repeated uint32 ports = 1;
+  int64 interval_min = 2;
+  int64 interval_max = 3;
+}
+
 message QuicParams {
   string congestion = 1;
-  uint64 up = 2;
+  uint64 brutal_up = 2;
+  uint64 brutal_down = 3;
+  UdpHop udp_hop = 4;
+  uint64 init_stream_receive_window = 5;
+  uint64 max_stream_receive_window = 6;
+  uint64 init_conn_receive_window = 7;
+  uint64 max_conn_receive_window = 8;
+  int64 max_idle_timeout = 9;
+  int64 keep_alive_period = 10;
+  bool disable_path_mtu_discovery = 11;
+  int64 max_incoming_streams = 12;
 }
 
 message ProxyConfig {

+ 0 - 6
transport/internet/finalmask/sudoku/sudoku_test.go

@@ -372,9 +372,6 @@ func runHysteria2Case(t *testing.T, bin string, mode trafficMode, payloadSize in
 								Settings: serial.ToTypedMessage(&hytransport.Config{
 									Version:        2,
 									Auth:           auth,
-									Congestion:     "bbr",
-									Up:             10 * 1024 * 1024,
-									Down:           10 * 1024 * 1024,
 									UdpIdleTimeout: 60,
 								}),
 							},
@@ -438,9 +435,6 @@ func runHysteria2Case(t *testing.T, bin string, mode trafficMode, payloadSize in
 								Settings: serial.ToTypedMessage(&hytransport.Config{
 									Version:        2,
 									Auth:           auth,
-									Congestion:     "bbr",
-									Up:             10 * 1024 * 1024,
-									Down:           10 * 1024 * 1024,
 									UdpIdleTimeout: 60,
 								}),
 							},

+ 26 - 154
transport/internet/hysteria/config.pb.go

@@ -22,34 +22,20 @@ const (
 )
 
 type Config struct {
-	state                   protoimpl.MessageState `protogen:"open.v1"`
-	Version                 int32                  `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
-	Auth                    string                 `protobuf:"bytes,2,opt,name=auth,proto3" json:"auth,omitempty"`
-	Congestion              string                 `protobuf:"bytes,3,opt,name=congestion,proto3" json:"congestion,omitempty"`
-	Up                      uint64                 `protobuf:"varint,4,opt,name=up,proto3" json:"up,omitempty"`
-	Down                    uint64                 `protobuf:"varint,5,opt,name=down,proto3" json:"down,omitempty"`
-	Ports                   []uint32               `protobuf:"varint,6,rep,packed,name=ports,proto3" json:"ports,omitempty"`
-	IntervalMin             int64                  `protobuf:"varint,7,opt,name=interval_min,json=intervalMin,proto3" json:"interval_min,omitempty"`
-	IntervalMax             int64                  `protobuf:"varint,8,opt,name=interval_max,json=intervalMax,proto3" json:"interval_max,omitempty"`
-	InitStreamReceiveWindow uint64                 `protobuf:"varint,9,opt,name=init_stream_receive_window,json=initStreamReceiveWindow,proto3" json:"init_stream_receive_window,omitempty"`
-	MaxStreamReceiveWindow  uint64                 `protobuf:"varint,10,opt,name=max_stream_receive_window,json=maxStreamReceiveWindow,proto3" json:"max_stream_receive_window,omitempty"`
-	InitConnReceiveWindow   uint64                 `protobuf:"varint,11,opt,name=init_conn_receive_window,json=initConnReceiveWindow,proto3" json:"init_conn_receive_window,omitempty"`
-	MaxConnReceiveWindow    uint64                 `protobuf:"varint,12,opt,name=max_conn_receive_window,json=maxConnReceiveWindow,proto3" json:"max_conn_receive_window,omitempty"`
-	MaxIdleTimeout          int64                  `protobuf:"varint,13,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3" json:"max_idle_timeout,omitempty"`
-	KeepAlivePeriod         int64                  `protobuf:"varint,14,opt,name=keep_alive_period,json=keepAlivePeriod,proto3" json:"keep_alive_period,omitempty"`
-	DisablePathMtuDiscovery bool                   `protobuf:"varint,15,opt,name=disable_path_mtu_discovery,json=disablePathMtuDiscovery,proto3" json:"disable_path_mtu_discovery,omitempty"`
-	MaxIncomingStreams      int64                  `protobuf:"varint,16,opt,name=max_incoming_streams,json=maxIncomingStreams,proto3" json:"max_incoming_streams,omitempty"`
-	UdpIdleTimeout          int64                  `protobuf:"varint,17,opt,name=udp_idle_timeout,json=udpIdleTimeout,proto3" json:"udp_idle_timeout,omitempty"`
-	MasqType                string                 `protobuf:"bytes,18,opt,name=masq_type,json=masqType,proto3" json:"masq_type,omitempty"`
-	MasqFile                string                 `protobuf:"bytes,19,opt,name=masq_file,json=masqFile,proto3" json:"masq_file,omitempty"`
-	MasqUrl                 string                 `protobuf:"bytes,20,opt,name=masq_url,json=masqUrl,proto3" json:"masq_url,omitempty"`
-	MasqUrlRewriteHost      bool                   `protobuf:"varint,21,opt,name=masq_url_rewrite_host,json=masqUrlRewriteHost,proto3" json:"masq_url_rewrite_host,omitempty"`
-	MasqUrlInsecure         bool                   `protobuf:"varint,22,opt,name=masq_url_insecure,json=masqUrlInsecure,proto3" json:"masq_url_insecure,omitempty"`
-	MasqString              string                 `protobuf:"bytes,23,opt,name=masq_string,json=masqString,proto3" json:"masq_string,omitempty"`
-	MasqStringHeaders       map[string]string      `protobuf:"bytes,24,rep,name=masq_string_headers,json=masqStringHeaders,proto3" json:"masq_string_headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
-	MasqStringStatusCode    int32                  `protobuf:"varint,25,opt,name=masq_string_status_code,json=masqStringStatusCode,proto3" json:"masq_string_status_code,omitempty"`
-	unknownFields           protoimpl.UnknownFields
-	sizeCache               protoimpl.SizeCache
+	state                protoimpl.MessageState `protogen:"open.v1"`
+	Version              int32                  `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
+	Auth                 string                 `protobuf:"bytes,2,opt,name=auth,proto3" json:"auth,omitempty"`
+	UdpIdleTimeout       int64                  `protobuf:"varint,3,opt,name=udp_idle_timeout,json=udpIdleTimeout,proto3" json:"udp_idle_timeout,omitempty"`
+	MasqType             string                 `protobuf:"bytes,4,opt,name=masq_type,json=masqType,proto3" json:"masq_type,omitempty"`
+	MasqFile             string                 `protobuf:"bytes,5,opt,name=masq_file,json=masqFile,proto3" json:"masq_file,omitempty"`
+	MasqUrl              string                 `protobuf:"bytes,6,opt,name=masq_url,json=masqUrl,proto3" json:"masq_url,omitempty"`
+	MasqUrlRewriteHost   bool                   `protobuf:"varint,7,opt,name=masq_url_rewrite_host,json=masqUrlRewriteHost,proto3" json:"masq_url_rewrite_host,omitempty"`
+	MasqUrlInsecure      bool                   `protobuf:"varint,8,opt,name=masq_url_insecure,json=masqUrlInsecure,proto3" json:"masq_url_insecure,omitempty"`
+	MasqString           string                 `protobuf:"bytes,9,opt,name=masq_string,json=masqString,proto3" json:"masq_string,omitempty"`
+	MasqStringHeaders    map[string]string      `protobuf:"bytes,10,rep,name=masq_string_headers,json=masqStringHeaders,proto3" json:"masq_string_headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+	MasqStringStatusCode int32                  `protobuf:"varint,11,opt,name=masq_string_status_code,json=masqStringStatusCode,proto3" json:"masq_string_status_code,omitempty"`
+	unknownFields        protoimpl.UnknownFields
+	sizeCache            protoimpl.SizeCache
 }
 
 func (x *Config) Reset() {
@@ -96,104 +82,6 @@ func (x *Config) GetAuth() string {
 	return ""
 }
 
-func (x *Config) GetCongestion() string {
-	if x != nil {
-		return x.Congestion
-	}
-	return ""
-}
-
-func (x *Config) GetUp() uint64 {
-	if x != nil {
-		return x.Up
-	}
-	return 0
-}
-
-func (x *Config) GetDown() uint64 {
-	if x != nil {
-		return x.Down
-	}
-	return 0
-}
-
-func (x *Config) GetPorts() []uint32 {
-	if x != nil {
-		return x.Ports
-	}
-	return nil
-}
-
-func (x *Config) GetIntervalMin() int64 {
-	if x != nil {
-		return x.IntervalMin
-	}
-	return 0
-}
-
-func (x *Config) GetIntervalMax() int64 {
-	if x != nil {
-		return x.IntervalMax
-	}
-	return 0
-}
-
-func (x *Config) GetInitStreamReceiveWindow() uint64 {
-	if x != nil {
-		return x.InitStreamReceiveWindow
-	}
-	return 0
-}
-
-func (x *Config) GetMaxStreamReceiveWindow() uint64 {
-	if x != nil {
-		return x.MaxStreamReceiveWindow
-	}
-	return 0
-}
-
-func (x *Config) GetInitConnReceiveWindow() uint64 {
-	if x != nil {
-		return x.InitConnReceiveWindow
-	}
-	return 0
-}
-
-func (x *Config) GetMaxConnReceiveWindow() uint64 {
-	if x != nil {
-		return x.MaxConnReceiveWindow
-	}
-	return 0
-}
-
-func (x *Config) GetMaxIdleTimeout() int64 {
-	if x != nil {
-		return x.MaxIdleTimeout
-	}
-	return 0
-}
-
-func (x *Config) GetKeepAlivePeriod() int64 {
-	if x != nil {
-		return x.KeepAlivePeriod
-	}
-	return 0
-}
-
-func (x *Config) GetDisablePathMtuDiscovery() bool {
-	if x != nil {
-		return x.DisablePathMtuDiscovery
-	}
-	return false
-}
-
-func (x *Config) GetMaxIncomingStreams() int64 {
-	if x != nil {
-		return x.MaxIncomingStreams
-	}
-	return 0
-}
-
 func (x *Config) GetUdpIdleTimeout() int64 {
 	if x != nil {
 		return x.UdpIdleTimeout
@@ -261,37 +149,21 @@ var File_transport_internet_hysteria_config_proto protoreflect.FileDescriptor
 
 const file_transport_internet_hysteria_config_proto_rawDesc = "" +
 	"\n" +
-	"(transport/internet/hysteria/config.proto\x12 xray.transport.internet.hysteria\"\xf0\b\n" +
+	"(transport/internet/hysteria/config.proto\x12 xray.transport.internet.hysteria\"\xa3\x04\n" +
 	"\x06Config\x12\x18\n" +
 	"\aversion\x18\x01 \x01(\x05R\aversion\x12\x12\n" +
-	"\x04auth\x18\x02 \x01(\tR\x04auth\x12\x1e\n" +
-	"\n" +
-	"congestion\x18\x03 \x01(\tR\n" +
-	"congestion\x12\x0e\n" +
-	"\x02up\x18\x04 \x01(\x04R\x02up\x12\x12\n" +
-	"\x04down\x18\x05 \x01(\x04R\x04down\x12\x14\n" +
-	"\x05ports\x18\x06 \x03(\rR\x05ports\x12!\n" +
-	"\finterval_min\x18\a \x01(\x03R\vintervalMin\x12!\n" +
-	"\finterval_max\x18\b \x01(\x03R\vintervalMax\x12;\n" +
-	"\x1ainit_stream_receive_window\x18\t \x01(\x04R\x17initStreamReceiveWindow\x129\n" +
-	"\x19max_stream_receive_window\x18\n" +
-	" \x01(\x04R\x16maxStreamReceiveWindow\x127\n" +
-	"\x18init_conn_receive_window\x18\v \x01(\x04R\x15initConnReceiveWindow\x125\n" +
-	"\x17max_conn_receive_window\x18\f \x01(\x04R\x14maxConnReceiveWindow\x12(\n" +
-	"\x10max_idle_timeout\x18\r \x01(\x03R\x0emaxIdleTimeout\x12*\n" +
-	"\x11keep_alive_period\x18\x0e \x01(\x03R\x0fkeepAlivePeriod\x12;\n" +
-	"\x1adisable_path_mtu_discovery\x18\x0f \x01(\bR\x17disablePathMtuDiscovery\x120\n" +
-	"\x14max_incoming_streams\x18\x10 \x01(\x03R\x12maxIncomingStreams\x12(\n" +
-	"\x10udp_idle_timeout\x18\x11 \x01(\x03R\x0eudpIdleTimeout\x12\x1b\n" +
-	"\tmasq_type\x18\x12 \x01(\tR\bmasqType\x12\x1b\n" +
-	"\tmasq_file\x18\x13 \x01(\tR\bmasqFile\x12\x19\n" +
-	"\bmasq_url\x18\x14 \x01(\tR\amasqUrl\x121\n" +
-	"\x15masq_url_rewrite_host\x18\x15 \x01(\bR\x12masqUrlRewriteHost\x12*\n" +
-	"\x11masq_url_insecure\x18\x16 \x01(\bR\x0fmasqUrlInsecure\x12\x1f\n" +
-	"\vmasq_string\x18\x17 \x01(\tR\n" +
+	"\x04auth\x18\x02 \x01(\tR\x04auth\x12(\n" +
+	"\x10udp_idle_timeout\x18\x03 \x01(\x03R\x0eudpIdleTimeout\x12\x1b\n" +
+	"\tmasq_type\x18\x04 \x01(\tR\bmasqType\x12\x1b\n" +
+	"\tmasq_file\x18\x05 \x01(\tR\bmasqFile\x12\x19\n" +
+	"\bmasq_url\x18\x06 \x01(\tR\amasqUrl\x121\n" +
+	"\x15masq_url_rewrite_host\x18\a \x01(\bR\x12masqUrlRewriteHost\x12*\n" +
+	"\x11masq_url_insecure\x18\b \x01(\bR\x0fmasqUrlInsecure\x12\x1f\n" +
+	"\vmasq_string\x18\t \x01(\tR\n" +
 	"masqString\x12o\n" +
-	"\x13masq_string_headers\x18\x18 \x03(\v2?.xray.transport.internet.hysteria.Config.MasqStringHeadersEntryR\x11masqStringHeaders\x125\n" +
-	"\x17masq_string_status_code\x18\x19 \x01(\x05R\x14masqStringStatusCode\x1aD\n" +
+	"\x13masq_string_headers\x18\n" +
+	" \x03(\v2?.xray.transport.internet.hysteria.Config.MasqStringHeadersEntryR\x11masqStringHeaders\x125\n" +
+	"\x17masq_string_status_code\x18\v \x01(\x05R\x14masqStringStatusCode\x1aD\n" +
 	"\x16MasqStringHeadersEntry\x12\x10\n" +
 	"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
 	"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x82\x01\n" +

+ 9 - 24
transport/internet/hysteria/config.proto

@@ -9,29 +9,14 @@ option java_multiple_files = true;
 message Config {
   int32 version = 1;
   string auth = 2;
-  string congestion = 3;
-  uint64 up = 4;
-  uint64 down = 5;
-  repeated uint32 ports = 6;
-  int64 interval_min = 7;
-  int64 interval_max = 8;
 
-  uint64 init_stream_receive_window = 9;
-  uint64 max_stream_receive_window = 10;
-  uint64 init_conn_receive_window = 11;
-  uint64 max_conn_receive_window = 12;
-  int64 max_idle_timeout = 13;
-  int64 keep_alive_period = 14;
-  bool disable_path_mtu_discovery = 15;
-  int64 max_incoming_streams = 16;
-
-  int64 udp_idle_timeout = 17;
-  string masq_type = 18;
-  string masq_file = 19;
-  string masq_url = 20;
-  bool masq_url_rewrite_host = 21;
-  bool masq_url_insecure = 22;
-  string masq_string = 23;
-  map<string, string> masq_string_headers = 24;
-  int32 masq_string_status_code = 25;
+  int64 udp_idle_timeout = 3;
+  string masq_type = 4;
+  string masq_file = 5;
+  string masq_url = 6;
+  bool masq_url_rewrite_host = 7;
+  bool masq_url_insecure = 8;
+  string masq_string = 9;
+  map<string, string> masq_string_headers = 10;
+  int32 masq_string_status_code = 11;
 }

+ 66 - 32
transport/internet/hysteria/dialer.go

@@ -120,8 +120,10 @@ type client struct {
 	tlsConfig      *go_tls.Config
 	socketConfig   *internet.SocketConfig
 	udpmaskManager *finalmask.UdpmaskManager
-	udpSM          *udpSessionManagerClient
-	mutex          sync.Mutex
+	quicParams     *internet.QuicParams
+
+	udpSM *udpSessionManagerClient
+	mutex sync.Mutex
 }
 
 func (c *client) status() Status {
@@ -153,10 +155,18 @@ func (c *client) dial() error {
 		c.close()
 	}
 
+	quicParams := c.quicParams
+	if quicParams == nil {
+		quicParams = &internet.QuicParams{}
+	}
+	if quicParams.UdpHop == nil {
+		quicParams.UdpHop = &internet.UdpHop{}
+	}
+
 	var index int
-	if len(c.config.Ports) > 0 {
-		index = rand.Intn(len(c.config.Ports))
-		c.dest.Port = net.Port(c.config.Ports[index])
+	if len(quicParams.UdpHop.Ports) > 0 {
+		index = rand.Intn(len(quicParams.UdpHop.Ports))
+		c.dest.Port = net.Port(quicParams.UdpHop.Ports[index])
 	}
 
 	raw, err := internet.DialSystem(c.ctx, c.dest, c.socketConfig)
@@ -179,7 +189,7 @@ func (c *client) dial() error {
 		pktConn = fakeConn
 		remote = fakeConn.RemoteAddr().(*net.UDPAddr)
 
-		if len(c.config.Ports) > 0 {
+		if len(quicParams.UdpHop.Ports) > 0 {
 			raw.Close()
 			return errors.New("udphop requires being at the outermost level")
 		}
@@ -188,12 +198,12 @@ func (c *client) dial() error {
 		return errors.New("unknown conn ", reflect.TypeOf(conn))
 	}
 
-	if len(c.config.Ports) > 0 {
+	if len(quicParams.UdpHop.Ports) > 0 {
 		addr := &udphop.UDPHopAddr{
 			IP:    remote.IP,
-			Ports: c.config.Ports,
+			Ports: quicParams.UdpHop.Ports,
 		}
-		pktConn, err = udphop.NewUDPHopPacketConn(addr, c.config.IntervalMin, c.config.IntervalMax, c.udphopDialer, pktConn, index)
+		pktConn, err = udphop.NewUDPHopPacketConn(addr, index, quicParams.UdpHop.IntervalMin, quicParams.UdpHop.IntervalMax, c.udphopDialer, pktConn)
 		if err != nil {
 			raw.Close()
 			return errors.New("udphop err").Base(err)
@@ -208,21 +218,41 @@ func (c *client) dial() error {
 		}
 	}
 
+	quicConfig := &quic.Config{
+		InitialStreamReceiveWindow:     quicParams.InitStreamReceiveWindow,
+		MaxStreamReceiveWindow:         quicParams.MaxStreamReceiveWindow,
+		InitialConnectionReceiveWindow: quicParams.InitConnReceiveWindow,
+		MaxConnectionReceiveWindow:     quicParams.MaxConnReceiveWindow,
+		MaxIdleTimeout:                 time.Duration(quicParams.MaxIdleTimeout) * time.Second,
+		KeepAlivePeriod:                time.Duration(quicParams.KeepAlivePeriod) * time.Second,
+		DisablePathMTUDiscovery:        quicParams.DisablePathMtuDiscovery,
+		EnableDatagrams:                true,
+		MaxDatagramFrameSize:           MaxDatagramFrameSize,
+		DisablePathManager:             true,
+	}
+	if quicParams.InitStreamReceiveWindow == 0 {
+		quicConfig.InitialStreamReceiveWindow = 8388608
+	}
+	if quicParams.MaxStreamReceiveWindow == 0 {
+		quicConfig.MaxStreamReceiveWindow = 8388608
+	}
+	if quicParams.InitConnReceiveWindow == 0 {
+		quicConfig.InitialConnectionReceiveWindow = 8388608 * 5 / 2
+	}
+	if quicParams.MaxConnReceiveWindow == 0 {
+		quicConfig.MaxConnectionReceiveWindow = 8388608 * 5 / 2
+	}
+	if quicParams.MaxIdleTimeout == 0 {
+		quicConfig.MaxIdleTimeout = 30 * time.Second
+	}
+	// if quicParams.KeepAlivePeriod == 0 {
+	// 	quicConfig.KeepAlivePeriod = 10 * time.Second
+	// }
+
 	var quicConn *quic.Conn
 	rt := &http3.Transport{
 		TLSClientConfig: c.tlsConfig,
-		QUICConfig: &quic.Config{
-			InitialStreamReceiveWindow:     c.config.InitStreamReceiveWindow,
-			MaxStreamReceiveWindow:         c.config.MaxStreamReceiveWindow,
-			InitialConnectionReceiveWindow: c.config.InitConnReceiveWindow,
-			MaxConnectionReceiveWindow:     c.config.MaxConnReceiveWindow,
-			MaxIdleTimeout:                 time.Duration(c.config.MaxIdleTimeout) * time.Second,
-			KeepAlivePeriod:                time.Duration(c.config.KeepAlivePeriod) * time.Second,
-			DisablePathMTUDiscovery:        c.config.DisablePathMtuDiscovery,
-			EnableDatagrams:                true,
-			MaxDatagramFrameSize:           MaxDatagramFrameSize,
-			DisablePathManager:             true,
-		},
+		QUICConfig:      quicConfig,
 		Dial: func(ctx context.Context, _ string, tlsCfg *go_tls.Config, cfg *quic.Config) (*quic.Conn, error) {
 			qc, err := quic.DialEarly(ctx, pktConn, remote, tlsCfg, cfg)
 			if err != nil {
@@ -241,7 +271,7 @@ func (c *client) dial() error {
 		},
 		Header: http.Header{
 			RequestHeaderAuth:   []string{c.config.Auth},
-			CommonHeaderCCRX:    []string{strconv.FormatUint(c.config.Down, 10)},
+			CommonHeaderCCRX:    []string{strconv.FormatUint(quicParams.BrutalDown, 10)},
 			CommonHeaderPadding: []string{authRequestPadding.String()},
 		},
 	}
@@ -264,23 +294,23 @@ func (c *client) dial() error {
 	serverAuto := resp.Header.Get(CommonHeaderCCRX)
 	serverDown, _ := strconv.ParseUint(serverAuto, 10, 64)
 
-	switch c.config.Congestion {
+	switch quicParams.Congestion {
 	case "reno":
 		errors.LogDebug(c.ctx, "congestion reno")
 	case "bbr":
 		errors.LogDebug(c.ctx, "congestion bbr")
 		congestion.UseBBR(quicConn)
 	case "brutal", "":
-		if serverAuto == "auto" || c.config.Up == 0 || serverDown == 0 {
+		if serverAuto == "auto" || quicParams.BrutalUp == 0 || serverDown == 0 {
 			errors.LogDebug(c.ctx, "congestion bbr")
 			congestion.UseBBR(quicConn)
 		} else {
-			errors.LogDebug(c.ctx, "congestion brutal bytes per second ", min(c.config.Up, serverDown))
-			congestion.UseBrutal(quicConn, min(c.config.Up, serverDown))
+			errors.LogDebug(c.ctx, "congestion brutal bytes per second ", min(quicParams.BrutalUp, serverDown))
+			congestion.UseBrutal(quicConn, min(quicParams.BrutalUp, serverDown))
 		}
 	case "force-brutal":
-		errors.LogDebug(c.ctx, "congestion brutal bytes per second ", c.config.Up)
-		congestion.UseBrutal(quicConn, c.config.Up)
+		errors.LogDebug(c.ctx, "congestion brutal bytes per second ", quicParams.BrutalUp)
+		congestion.UseBrutal(quicConn, quicParams.BrutalUp)
 	default:
 		errors.LogDebug(c.ctx, "congestion reno")
 	}
@@ -359,13 +389,14 @@ func (c *client) udphopDialer(addr *net.UDPAddr) (net.PacketConn, error) {
 	defer c.mutex.Unlock()
 
 	if c.status() != StatusActive {
-		errors.LogDebug(c.ctx, "skip hop: disconnected QUIC")
+		errors.LogDebug(context.Background(), "skip hop: disconnected QUIC")
 		return nil, errors.New()
 	}
 
 	raw, err := internet.DialSystem(c.ctx, net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), c.socketConfig)
 	if err != nil {
-		errors.LogDebug(c.ctx, "skip hop: failed to dial to dest")
+		errors.LogDebug(context.Background(), "skip hop: failed to dial to dest")
+		raw.Close()
 		return nil, errors.New()
 	}
 
@@ -377,10 +408,12 @@ func (c *client) udphopDialer(addr *net.UDPAddr) (net.PacketConn, error) {
 	case *net.UDPConn:
 		pktConn = conn
 	case *cnc.Connection:
-		errors.LogDebug(c.ctx, "skip hop: udphop requires being at the outermost level")
+		errors.LogDebug(context.Background(), "skip hop: udphop requires being at the outermost level")
+		raw.Close()
 		return nil, errors.New()
 	default:
-		errors.LogDebug(c.ctx, "skip hop: unknown conn ", reflect.TypeOf(conn))
+		errors.LogDebug(context.Background(), "skip hop: unknown conn ", reflect.TypeOf(conn))
+		raw.Close()
 		return nil, errors.New()
 	}
 
@@ -424,6 +457,7 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
 			tlsConfig:      tlsConfig.GetTLSConfig(),
 			socketConfig:   streamSettings.SocketSettings,
 			udpmaskManager: streamSettings.UdpmaskManager,
+			quicParams:     streamSettings.QuicParams,
 		}
 		manger.m[addr] = c
 	}

+ 43 - 16
transport/internet/hysteria/hub.go

@@ -140,6 +140,7 @@ type httpHandler struct {
 	addConn internet.ConnHandler
 
 	config      *Config
+	quicParams  *internet.QuicParams
 	validator   *account.Validator
 	masqHandler http.Handler
 
@@ -155,7 +156,7 @@ func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 		if h.auth {
 			w.Header().Set(ResponseHeaderUDPEnabled, strconv.FormatBool(hyCtx.RequireDatagramFromContext(h.ctx)))
-			w.Header().Set(CommonHeaderCCRX, strconv.FormatUint(h.config.Down, 10))
+			w.Header().Set(CommonHeaderCCRX, strconv.FormatUint(h.quicParams.BrutalDown, 10))
 			w.Header().Set(CommonHeaderPadding, authResponsePadding.String())
 			w.WriteHeader(StatusAuthOK)
 			return
@@ -176,23 +177,23 @@ func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 			h.auth = true
 			h.user = user
 
-			switch h.config.Congestion {
+			switch h.quicParams.Congestion {
 			case "reno":
 				errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion reno")
 			case "bbr":
 				errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion bbr")
 				congestion.UseBBR(h.conn)
 			case "brutal", "":
-				if h.config.Up == 0 || clientDown == 0 {
+				if h.quicParams.BrutalUp == 0 || clientDown == 0 {
 					errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion bbr")
 					congestion.UseBBR(h.conn)
 				} else {
-					errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion brutal bytes per second ", min(h.config.Up, clientDown))
-					congestion.UseBrutal(h.conn, min(h.config.Up, clientDown))
+					errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion brutal bytes per second ", min(h.quicParams.BrutalUp, clientDown))
+					congestion.UseBrutal(h.conn, min(h.quicParams.BrutalUp, clientDown))
 				}
 			case "force-brutal":
-				errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion brutal bytes per second ", h.config.Up)
-				congestion.UseBrutal(h.conn, h.config.Up)
+				errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion brutal bytes per second ", h.quicParams.BrutalUp)
+				congestion.UseBrutal(h.conn, h.quicParams.BrutalUp)
 			default:
 				errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion reno")
 			}
@@ -212,7 +213,7 @@ func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 			}
 
 			w.Header().Set(ResponseHeaderUDPEnabled, strconv.FormatBool(hyCtx.RequireDatagramFromContext(h.ctx)))
-			w.Header().Set(CommonHeaderCCRX, strconv.FormatUint(h.config.Down, 10))
+			w.Header().Set(CommonHeaderCCRX, strconv.FormatUint(h.quicParams.BrutalDown, 10))
 			w.Header().Set(CommonHeaderPadding, authResponsePadding.String())
 			w.WriteHeader(StatusAuthOK)
 			return
@@ -253,6 +254,7 @@ type Listener struct {
 	addConn  internet.ConnHandler
 
 	config      *Config
+	quicParams  *internet.QuicParams
 	validator   *account.Validator
 	masqHandler http.Handler
 }
@@ -264,6 +266,7 @@ func (l *Listener) handleClient(conn *quic.Conn) {
 		addConn: l.addConn,
 
 		config:      l.config,
+		quicParams:  l.quicParams,
 		validator:   l.validator,
 		masqHandler: l.masqHandler,
 	}
@@ -272,8 +275,8 @@ func (l *Listener) handleClient(conn *quic.Conn) {
 		StreamDispatcher: handler.StreamDispatcher,
 	}
 	err := h3.ServeQUICConn(conn)
-	errors.LogDebug(context.Background(), conn.RemoteAddr(), " disconnected with err ", err)
 	_ = conn.CloseWithError(closeErrCodeOK, "")
+	errors.LogDebug(context.Background(), conn.RemoteAddr(), " disconnected with err ", err)
 }
 
 func (l *Listener) keepAccepting() {
@@ -377,18 +380,41 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
 		}
 	}
 
+	quicParams := streamSettings.QuicParams
+	if quicParams == nil {
+		quicParams = &internet.QuicParams{}
+	}
+
 	quicConfig := &quic.Config{
-		InitialStreamReceiveWindow:     config.InitStreamReceiveWindow,
-		MaxStreamReceiveWindow:         config.MaxStreamReceiveWindow,
-		InitialConnectionReceiveWindow: config.InitConnReceiveWindow,
-		MaxConnectionReceiveWindow:     config.MaxConnReceiveWindow,
-		MaxIdleTimeout:                 time.Duration(config.MaxIdleTimeout) * time.Second,
-		MaxIncomingStreams:             config.MaxIncomingStreams,
-		DisablePathMTUDiscovery:        config.DisablePathMtuDiscovery,
+		InitialStreamReceiveWindow:     quicParams.InitStreamReceiveWindow,
+		MaxStreamReceiveWindow:         quicParams.MaxStreamReceiveWindow,
+		InitialConnectionReceiveWindow: quicParams.InitConnReceiveWindow,
+		MaxConnectionReceiveWindow:     quicParams.MaxConnReceiveWindow,
+		MaxIdleTimeout:                 time.Duration(quicParams.MaxIdleTimeout) * time.Second,
+		MaxIncomingStreams:             quicParams.MaxIncomingStreams,
+		DisablePathMTUDiscovery:        quicParams.DisablePathMtuDiscovery,
 		EnableDatagrams:                true,
 		MaxDatagramFrameSize:           MaxDatagramFrameSize,
 		DisablePathManager:             true,
 	}
+	if quicParams.InitStreamReceiveWindow == 0 {
+		quicConfig.InitialStreamReceiveWindow = 8388608
+	}
+	if quicParams.MaxStreamReceiveWindow == 0 {
+		quicConfig.MaxStreamReceiveWindow = 8388608
+	}
+	if quicParams.InitConnReceiveWindow == 0 {
+		quicConfig.InitialConnectionReceiveWindow = 8388608 * 5 / 2
+	}
+	if quicParams.MaxConnReceiveWindow == 0 {
+		quicConfig.MaxConnectionReceiveWindow = 8388608 * 5 / 2
+	}
+	if quicParams.MaxIdleTimeout == 0 {
+		quicConfig.MaxIdleTimeout = 30 * time.Second
+	}
+	if quicParams.MaxIncomingStreams == 0 {
+		quicConfig.MaxIncomingStreams = 1024
+	}
 
 	qListener, err := quic.Listen(pktConn, tlsConfig.GetTLSConfig(), quicConfig)
 	if err != nil {
@@ -403,6 +429,7 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
 		addConn:  handler,
 
 		config:      config,
+		quicParams:  quicParams,
 		validator:   validator,
 		masqHandler: masqHandler,
 	}

+ 3 - 2
transport/internet/hysteria/udphop/conn.go

@@ -9,11 +9,12 @@ import (
 	"time"
 
 	"github.com/xtls/xray-core/common/crypto"
+	"github.com/xtls/xray-core/transport/internet/finalmask"
 )
 
 const (
 	packetQueueSize = 1024
-	udpBufferSize   = 2048 // QUIC packets are at most 1500 bytes long, so 2k should be more than enough
+	udpBufferSize   = finalmask.UDPSize
 
 	defaultHopInterval = 30 * time.Second
 )
@@ -49,7 +50,7 @@ type udpPacket struct {
 
 type ListenUDPFunc = func(*net.UDPAddr) (net.PacketConn, error)
 
-func NewUDPHopPacketConn(addr *UDPHopAddr, intervalMin int64, intervalMax int64, listenUDPFunc ListenUDPFunc, pktConn net.PacketConn, index int) (net.PacketConn, error) {
+func NewUDPHopPacketConn(addr *UDPHopAddr, index int, intervalMin int64, intervalMax int64, listenUDPFunc ListenUDPFunc, pktConn net.PacketConn) (net.PacketConn, error) {
 	if intervalMin == 0 || intervalMax == 0 {
 		intervalMin = int64(defaultHopInterval)
 		intervalMax = int64(defaultHopInterval)

+ 0 - 107
transport/internet/hysteria/utils/portunion.go

@@ -1,107 +0,0 @@
-package utils
-
-import (
-	"sort"
-	"strconv"
-	"strings"
-)
-
-// PortUnion is a collection of multiple port ranges.
-type PortUnion []PortRange
-
-// PortRange represents a range of ports.
-// Start and End are inclusive. [Start, End]
-type PortRange struct {
-	Start, End uint16
-}
-
-// ParsePortUnion parses a string of comma-separated port ranges (or single ports) into a PortUnion.
-// Returns nil if the input is invalid.
-// The returned PortUnion is guaranteed to be normalized.
-func ParsePortUnion(s string) PortUnion {
-	if s == "all" || s == "*" {
-		// Wildcard special case
-		return PortUnion{PortRange{0, 65535}}
-	}
-	var result PortUnion
-	portStrs := strings.Split(s, ",")
-	for _, portStr := range portStrs {
-		if strings.Contains(portStr, "-") {
-			// Port range
-			portRange := strings.Split(portStr, "-")
-			if len(portRange) != 2 {
-				return nil
-			}
-			start, err := strconv.ParseUint(portRange[0], 10, 16)
-			if err != nil {
-				return nil
-			}
-			end, err := strconv.ParseUint(portRange[1], 10, 16)
-			if err != nil {
-				return nil
-			}
-			if start > end {
-				start, end = end, start
-			}
-			result = append(result, PortRange{uint16(start), uint16(end)})
-		} else {
-			// Single port
-			port, err := strconv.ParseUint(portStr, 10, 16)
-			if err != nil {
-				return nil
-			}
-			result = append(result, PortRange{uint16(port), uint16(port)})
-		}
-	}
-	if result == nil {
-		return nil
-	}
-	return result.Normalize()
-}
-
-// Normalize normalizes a PortUnion.
-// No overlapping ranges, ranges are sorted from low to high.
-func (u PortUnion) Normalize() PortUnion {
-	if len(u) == 0 {
-		return u
-	}
-	sort.Slice(u, func(i, j int) bool {
-		if u[i].Start == u[j].Start {
-			return u[i].End < u[j].End
-		}
-		return u[i].Start < u[j].Start
-	})
-	normalized := PortUnion{u[0]}
-	for _, current := range u[1:] {
-		last := &normalized[len(normalized)-1]
-		if uint32(current.Start) <= uint32(last.End)+1 {
-			if current.End > last.End {
-				last.End = current.End
-			}
-		} else {
-			normalized = append(normalized, current)
-		}
-	}
-	return normalized
-}
-
-// Ports returns all ports in the PortUnion as a slice.
-func (u PortUnion) Ports() []uint16 {
-	var ports []uint16
-	for _, r := range u {
-		for i := uint32(r.Start); i <= uint32(r.End); i++ {
-			ports = append(ports, uint16(i))
-		}
-	}
-	return ports
-}
-
-// Contains returns true if the PortUnion contains the given port.
-func (u PortUnion) Contains(port uint16) bool {
-	for _, r := range u {
-		if port >= r.Start && port <= r.End {
-			return true
-		}
-	}
-	return false
-}

+ 0 - 150
transport/internet/hysteria/utils/portunion_test.go

@@ -1,150 +0,0 @@
-package utils
-
-import (
-	"reflect"
-	"slices"
-	"testing"
-)
-
-func TestParsePortUnion(t *testing.T) {
-	tests := []struct {
-		name string
-		s    string
-		want PortUnion
-	}{
-		{
-			name: "empty",
-			s:    "",
-			want: nil,
-		},
-		{
-			name: "all 1",
-			s:    "all",
-			want: PortUnion{{0, 65535}},
-		},
-		{
-			name: "all 2",
-			s:    "*",
-			want: PortUnion{{0, 65535}},
-		},
-		{
-			name: "single port",
-			s:    "1234",
-			want: PortUnion{{1234, 1234}},
-		},
-		{
-			name: "multiple ports (unsorted)",
-			s:    "5678,1234,9012",
-			want: PortUnion{{1234, 1234}, {5678, 5678}, {9012, 9012}},
-		},
-		{
-			name: "one range",
-			s:    "1234-1240",
-			want: PortUnion{{1234, 1240}},
-		},
-		{
-			name: "one range (reversed)",
-			s:    "1240-1234",
-			want: PortUnion{{1234, 1240}},
-		},
-		{
-			name: "multiple ports and ranges (reversed, unsorted, overlapping)",
-			s:    "5678,1200-1236,9100-9012,1234-1240",
-			want: PortUnion{{1200, 1240}, {5678, 5678}, {9012, 9100}},
-		},
-		{
-			name: "multiple ports and ranges with 65535 (reversed, unsorted, overlapping)",
-			s:    "5678,1200-1236,65531-65535,65532-65534,9100-9012,1234-1240",
-			want: PortUnion{{1200, 1240}, {5678, 5678}, {9012, 9100}, {65531, 65535}},
-		},
-		{
-			name: "multiple ports and ranges with 65535 (reversed, unsorted, overlapping) 2",
-			s:    "5678,1200-1236,65532-65535,65531-65534,9100-9012,1234-1240",
-			want: PortUnion{{1200, 1240}, {5678, 5678}, {9012, 9100}, {65531, 65535}},
-		},
-		{
-			name: "invalid 1",
-			s:    "1234-",
-			want: nil,
-		},
-		{
-			name: "invalid 2",
-			s:    "1234-ggez",
-			want: nil,
-		},
-		{
-			name: "invalid 3",
-			s:    "233,",
-			want: nil,
-		},
-		{
-			name: "invalid 4",
-			s:    "1234-1240-1250",
-			want: nil,
-		},
-		{
-			name: "invalid 5",
-			s:    "-,,",
-			want: nil,
-		},
-		{
-			name: "invalid 6",
-			s:    "http",
-			want: nil,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if got := ParsePortUnion(tt.s); !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("ParsePortUnion() = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
-
-func TestPortUnion_Ports(t *testing.T) {
-	tests := []struct {
-		name string
-		pu   PortUnion
-		want []uint16
-	}{
-		{
-			name: "single port",
-			pu:   PortUnion{{1234, 1234}},
-			want: []uint16{1234},
-		},
-		{
-			name: "multiple ports",
-			pu:   PortUnion{{1234, 1236}},
-			want: []uint16{1234, 1235, 1236},
-		},
-		{
-			name: "multiple ports and ranges",
-			pu:   PortUnion{{1234, 1236}, {5678, 5680}, {9000, 9002}},
-			want: []uint16{1234, 1235, 1236, 5678, 5679, 5680, 9000, 9001, 9002},
-		},
-		{
-			name: "single port 65535",
-			pu:   PortUnion{{65535, 65535}},
-			want: []uint16{65535},
-		},
-		{
-			name: "port range with 65535",
-			pu:   PortUnion{{65530, 65535}},
-			want: []uint16{65530, 65531, 65532, 65533, 65534, 65535},
-		},
-		{
-			name: "multiple ports and ranges with 65535",
-			pu:   PortUnion{{65530, 65535}, {1234, 1236}},
-			want: []uint16{65530, 65531, 65532, 65533, 65534, 65535, 1234, 1235, 1236},
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if got := tt.pu.Ports(); !slices.Equal(got, tt.want) {
-				t.Errorf("PortUnion.Ports() = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}

+ 92 - 26
transport/internet/splithttp/dialer.go

@@ -5,9 +5,11 @@ import (
 	gotls "crypto/tls"
 	"fmt"
 	"io"
+	"math/rand"
 	"net/http"
 	"net/http/httptrace"
 	"net/url"
+	reflect "reflect"
 	"strconv"
 	"sync"
 	"sync/atomic"
@@ -24,6 +26,7 @@ import (
 	"github.com/xtls/xray-core/transport/internet"
 	"github.com/xtls/xray-core/transport/internet/browser_dialer"
 	"github.com/xtls/xray-core/transport/internet/hysteria/congestion"
+	"github.com/xtls/xray-core/transport/internet/hysteria/udphop"
 	"github.com/xtls/xray-core/transport/internet/reality"
 	"github.com/xtls/xray-core/transport/internet/stat"
 	"github.com/xtls/xray-core/transport/internet/tls"
@@ -153,25 +156,73 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
 	var transport http.RoundTripper
 
 	if httpVersion == "3" {
-		if keepAlivePeriod == 0 {
-			keepAlivePeriod = net.QuicgoH3KeepAlivePeriod
+		quicParams := streamSettings.QuicParams
+		if quicParams == nil {
+			quicParams = &internet.QuicParams{}
 		}
-		if keepAlivePeriod < 0 {
-			keepAlivePeriod = 0
+		if quicParams.UdpHop == nil {
+			quicParams.UdpHop = &internet.UdpHop{}
 		}
-		quicConfig := &quic.Config{
-			MaxIdleTimeout: net.ConnIdleTimeout,
 
+		quicConfig := &quic.Config{
+			InitialStreamReceiveWindow:     quicParams.InitStreamReceiveWindow,
+			MaxStreamReceiveWindow:         quicParams.MaxStreamReceiveWindow,
+			InitialConnectionReceiveWindow: quicParams.InitConnReceiveWindow,
+			MaxConnectionReceiveWindow:     quicParams.MaxConnReceiveWindow,
+			MaxIdleTimeout:                 time.Duration(quicParams.MaxIdleTimeout) * time.Second,
+			KeepAlivePeriod:                time.Duration(quicParams.KeepAlivePeriod) * time.Second,
+			MaxIncomingStreams:             quicParams.MaxIncomingStreams,
+			DisablePathMTUDiscovery:        quicParams.DisablePathMtuDiscovery,
+		}
+		if quicParams.MaxIdleTimeout == 0 {
+			quicConfig.MaxIdleTimeout = net.ConnIdleTimeout
+		}
+		if quicParams.KeepAlivePeriod == 0 {
+			if keepAlivePeriod == 0 {
+				quicConfig.KeepAlivePeriod = net.QuicgoH3KeepAlivePeriod
+			}
+		}
+		if quicParams.MaxIncomingStreams == 0 {
 			// these two are defaults of quic-go/http3. the default of quic-go (no
 			// http3) is different, so it is hardcoded here for clarity.
 			// https://github.com/quic-go/quic-go/blob/b8ea5c798155950fb5bbfdd06cad1939c9355878/http3/client.go#L36-L39
-			MaxIncomingStreams: -1,
-			KeepAlivePeriod:    keepAlivePeriod,
+			quicConfig.MaxIncomingStreams = -1
 		}
+
 		transport = &http3.Transport{
 			QUICConfig:      quicConfig,
 			TLSClientConfig: gotlsConfig,
 			Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (*quic.Conn, error) {
+				udphopDialer := func(addr *net.UDPAddr) (net.PacketConn, error) {
+					conn, err := internet.DialSystem(ctx, net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), streamSettings.SocketSettings)
+					if err != nil {
+						errors.LogDebug(context.Background(), "skip hop: failed to dial to dest")
+						conn.Close()
+						return nil, errors.New()
+					}
+
+					var udpConn net.PacketConn
+
+					switch c := conn.(type) {
+					case *internet.PacketConnWrapper:
+						udpConn = c.PacketConn
+					case *net.UDPConn:
+						udpConn = c
+					default:
+						errors.LogDebug(context.Background(), "skip hop: udphop requires being at the outermost level ", reflect.TypeOf(c))
+						conn.Close()
+						return nil, errors.New()
+					}
+
+					return udpConn, nil
+				}
+
+				var index int
+				if len(quicParams.UdpHop.Ports) > 0 {
+					index = rand.Intn(len(quicParams.UdpHop.Ports))
+					dest.Port = net.Port(quicParams.UdpHop.Ports[index])
+				}
+
 				conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
 				if err != nil {
 					return nil, err
@@ -182,54 +233,69 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
 
 				switch c := conn.(type) {
 				case *internet.PacketConnWrapper:
-					var ok bool
-					udpConn, ok = c.PacketConn.(*net.UDPConn)
-					if !ok {
-						return nil, errors.New("PacketConnWrapper does not contain a UDP connection")
-					}
+					udpConn = c.PacketConn
 					udpAddr, err = net.ResolveUDPAddr("udp", c.Dest.String())
 					if err != nil {
+						conn.Close()
 						return nil, err
 					}
 				case *net.UDPConn:
 					udpConn = c
 					udpAddr, err = net.ResolveUDPAddr("udp", c.RemoteAddr().String())
 					if err != nil {
+						conn.Close()
 						return nil, err
 					}
 				default:
 					udpConn = &internet.FakePacketConn{Conn: c}
 					udpAddr, err = net.ResolveUDPAddr("udp", c.RemoteAddr().String())
 					if err != nil {
+						conn.Close()
 						return nil, err
 					}
+
+					if len(quicParams.UdpHop.Ports) > 0 {
+						conn.Close()
+						return nil, errors.New("udphop requires being at the outermost level ", reflect.TypeOf(c))
+					}
+				}
+
+				if len(quicParams.UdpHop.Ports) > 0 {
+					addr := &udphop.UDPHopAddr{
+						IP:    udpAddr.IP,
+						Ports: quicParams.UdpHop.Ports,
+					}
+					udpConn, err = udphop.NewUDPHopPacketConn(addr, index, quicParams.UdpHop.IntervalMin, quicParams.UdpHop.IntervalMax, udphopDialer, udpConn)
+					if err != nil {
+						conn.Close()
+						return nil, errors.New("udphop err").Base(err)
+					}
 				}
 
 				if streamSettings.UdpmaskManager != nil {
-					pktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(udpConn)
+					udpConn, err = streamSettings.UdpmaskManager.WrapPacketConnClient(udpConn)
 					if err != nil {
-						udpConn.Close()
+						conn.Close()
 						return nil, errors.New("mask err").Base(err)
 					}
-					udpConn = pktConn
 				}
 
 				quicConn, err := quic.DialEarly(ctx, udpConn, udpAddr, tlsCfg, cfg)
 				if err != nil {
 					return nil, err
 				}
-				if streamSettings.QuicParams != nil {
-					switch streamSettings.QuicParams.Congestion {
-					case "force-brutal":
-						congestion.UseBrutal(quicConn, streamSettings.QuicParams.Up)
-					case "reno":
-						// quic-go default, do nothing
-					default:
-						congestion.UseBBR(quicConn)
-					}
-				} else {
+
+				switch quicParams.Congestion {
+				case "force-brutal":
+					errors.LogDebug(context.Background(), quicConn.RemoteAddr(), " ", "congestion brutal bytes per second ", quicParams.BrutalUp)
+					congestion.UseBrutal(quicConn, quicParams.BrutalUp)
+				case "reno":
+					errors.LogDebug(context.Background(), quicConn.RemoteAddr(), " ", "congestion reno")
+				default:
+					errors.LogDebug(context.Background(), quicConn.RemoteAddr(), " ", "congestion bbr")
 					congestion.UseBBR(quicConn)
 				}
+
 				return quicConn, nil
 			},
 		}

+ 30 - 14
transport/internet/splithttp/hub.go

@@ -20,10 +20,10 @@ import (
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/errors"
 	"github.com/xtls/xray-core/common/net"
-	"github.com/xtls/xray-core/transport/internet/hysteria/congestion"
 	http_proto "github.com/xtls/xray-core/common/protocol/http"
 	"github.com/xtls/xray-core/common/signal/done"
 	"github.com/xtls/xray-core/transport/internet"
+	"github.com/xtls/xray-core/transport/internet/hysteria/congestion"
 	"github.com/xtls/xray-core/transport/internet/reality"
 	"github.com/xtls/xray-core/transport/internet/stat"
 	"github.com/xtls/xray-core/transport/internet/tls"
@@ -254,7 +254,7 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
 		dataPlacement := h.config.GetNormalizedUplinkDataPlacement()
 		var headerPayload []byte
 		if dataPlacement == PlacementAuto || dataPlacement == PlacementHeader {
-			var headerPayloadChunks [] string
+			var headerPayloadChunks []string
 			for i := 0; true; i++ {
 				chunk := request.Header.Get(fmt.Sprintf("%s-%d", uplinkDataKey, i))
 				if chunk == "" {
@@ -463,7 +463,6 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet
 		if err != nil {
 			return nil, errors.New("failed to listen UDP for XHTTP/3 on ", address, ":", port).Base(err)
 		}
-
 		if streamSettings.UdpmaskManager != nil {
 			pktConn, err := streamSettings.UdpmaskManager.WrapPacketConnServer(Conn)
 			if err != nil {
@@ -473,7 +472,22 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet
 			Conn = pktConn
 		}
 
-		l.h3listener, err = quic.ListenEarly(Conn, tlsConfig, nil)
+		quicParams := streamSettings.QuicParams
+		if quicParams == nil {
+			quicParams = &internet.QuicParams{}
+		}
+
+		quicConfig := &quic.Config{
+			InitialStreamReceiveWindow:     quicParams.InitStreamReceiveWindow,
+			MaxStreamReceiveWindow:         quicParams.MaxStreamReceiveWindow,
+			InitialConnectionReceiveWindow: quicParams.InitConnReceiveWindow,
+			MaxConnectionReceiveWindow:     quicParams.MaxConnReceiveWindow,
+			MaxIdleTimeout:                 time.Duration(quicParams.MaxIdleTimeout) * time.Second,
+			MaxIncomingStreams:             quicParams.MaxIncomingStreams,
+			DisablePathMTUDiscovery:        quicParams.DisablePathMtuDiscovery,
+		}
+
+		l.h3listener, err = quic.ListenEarly(Conn, tlsConfig, quicConfig)
 		if err != nil {
 			return nil, errors.New("failed to listen QUIC for XHTTP/3 on ", address, ":", port).Base(err)
 		}
@@ -491,22 +505,23 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet
 					errors.LogInfoInner(ctx, err, "XHTTP/3 listener closed")
 					return
 				}
-				if streamSettings.QuicParams != nil {
-					switch streamSettings.QuicParams.Congestion {
-					case "force-brutal":
-						congestion.UseBrutal(conn, streamSettings.QuicParams.Up)
-					case "reno":
-						// quic-go default, do nothing
-					default:
-						congestion.UseBBR(conn)
-					}
-				} else {
+
+				switch quicParams.Congestion {
+				case "force-brutal":
+					errors.LogDebug(context.Background(), conn.RemoteAddr(), " ", "congestion brutal bytes per second ", quicParams.BrutalUp)
+					congestion.UseBrutal(conn, quicParams.BrutalUp)
+				case "reno":
+					errors.LogDebug(context.Background(), conn.RemoteAddr(), " ", "congestion reno")
+				default:
+					errors.LogDebug(context.Background(), conn.RemoteAddr(), " ", "congestion bbr")
 					congestion.UseBBR(conn)
 				}
+
 				go func() {
 					if err := l.h3server.ServeQUICConn(conn); err != nil {
 						errors.LogDebugInner(ctx, err, "XHTTP/3 connection ended")
 					}
+					_ = conn.CloseWithError(0, "")
 				}()
 			}
 		}()
@@ -573,6 +588,7 @@ func (ln *Listener) Addr() net.Addr {
 func (ln *Listener) Close() error {
 	if ln.h3server != nil {
 		if err := ln.h3server.Close(); err != nil {
+			_ = ln.h3listener.Close()
 			return err
 		}
 		return ln.h3listener.Close()