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
This commit is contained in:
LjhAUMEM
2026-03-09 20:17:32 +08:00
committed by GitHub
parent 766fa71eb1
commit 0321cdd0d2
13 changed files with 590 additions and 694 deletions
+92 -26
View File
@@ -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
},
}