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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions app/cmd/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
"github.com/apernet/hysteria/extras/v2/obfs"
"github.com/apernet/hysteria/extras/v2/realm"
"github.com/apernet/hysteria/extras/v2/transport/udphop"
"github.com/apernet/quic-go"
)

// Ref: https://ip.skk.moe/stun
Expand Down Expand Up @@ -170,6 +171,7 @@ type clientConfigQUIC struct {
KeepAlivePeriod time.Duration `mapstructure:"keepAlivePeriod"`
DisablePathMTUDiscovery bool `mapstructure:"disablePathMTUDiscovery"`
DisableChromeParrot bool `mapstructure:"disableChromeParrot"`
Version string `mapstructure:"version"`
Sockopts clientConfigQUICSockopts `mapstructure:"sockopts"`
}

Expand Down Expand Up @@ -459,7 +461,21 @@ func (c *clientConfig) validateMimic() error {
}

func (c *clientConfig) fillQUICConfig(hyConfig *client.Config) error {
var version quic.Version
switch c.QUIC.Version {
case "":
case "v1":
version = quic.Version1
case "v2":
version = quic.Version2
if !c.QUIC.DisableChromeParrot {
logger.Warn("Chrome parrot is disabled when using QUIC v2")
}
default:
return configError{Field: "quic.version", Err: errors.New("must be v1 or v2")}
}
hyConfig.QUICConfig = client.QUICConfig{
Version: version,
InitialStreamReceiveWindow: c.QUIC.InitStreamReceiveWindow,
MaxStreamReceiveWindow: c.QUIC.MaxStreamReceiveWindow,
InitialConnectionReceiveWindow: c.QUIC.InitConnectionReceiveWindow,
Expand Down
1 change: 1 addition & 0 deletions app/cmd/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func TestClientConfig(t *testing.T) {
KeepAlivePeriod: 4 * time.Second,
DisablePathMTUDiscovery: true,
DisableChromeParrot: true,
Version: "v2",
Sockopts: clientConfigQUICSockopts{
BindInterface: stringRef("eth0"),
FirewallMark: uint32Ref(1234),
Expand Down
1 change: 1 addition & 0 deletions app/cmd/client_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ quic:
keepAlivePeriod: 4s
disablePathMTUDiscovery: true
disableChromeParrot: true
version: v2
sockopts:
bindInterface: eth0
fwmark: 1234
Expand Down
2 changes: 1 addition & 1 deletion app/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/apernet/go-tproxy v0.0.0-20230809025308-8f4723fd742f
github.com/apernet/hysteria/core/v2 v2.0.0-00010101000000-000000000000
github.com/apernet/hysteria/extras/v2 v2.0.0-00010101000000-000000000000
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e
github.com/apernet/sing-tun v0.2.6-0.20250920121535-299f04629986
github.com/caddyserver/certmagic v0.25.4
github.com/libdns/cloudflare v0.2.2
Expand All @@ -33,7 +34,6 @@ require (

require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e // indirect
github.com/caddyserver/zerossl v0.1.5 // indirect
github.com/database64128/netx-go v0.1.1 // indirect
github.com/database64128/tfo-go/v2 v2.3.3 // indirect
Expand Down
3 changes: 3 additions & 0 deletions core/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ func (c *clientImpl) connect() (*HandshakeInfo, error) {
DisablePathManager: true,
ChromeParrot: !c.config.QUICConfig.DisableChromeParrot,
}
if v := c.config.QUICConfig.Version; v != 0 {
quicConfig.Versions = []quic.Version{v}
}
tr := &quic.Transport{Conn: pktConn, DisableGSO: c.config.QUICConfig.DisableGSO}
if !c.config.QUICConfig.DisableChromeParrot {
// Chrome uses a zero-length source connection ID. This has to be set on the
Expand Down
11 changes: 10 additions & 1 deletion core/client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"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/quic-go"
)

const (
Expand Down Expand Up @@ -74,6 +75,13 @@ 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
if v := c.QUICConfig.Version; v != 0 && v != quic.Version1 && v != quic.Version2 {
return errors.ConfigError{Field: "QUICConfig.Version", Reason: "must be v1 or v2"}
}
if c.QUICConfig.Version == quic.Version2 {
// The parrot advertises v1 in version_information, and Chrome never offers v2.
c.QUICConfig.DisableChromeParrot = true
}
var err error
c.CongestionConfig.Type, err = congestion.NormalizeType(c.CongestionConfig.Type)
if err != nil {
Expand Down Expand Up @@ -120,7 +128,8 @@ type QUICConfig struct {
KeepAlivePeriod time.Duration
DisablePathMTUDiscovery bool // The server may still override this to true on unsupported platforms.
DisableGSO bool
DisableChromeParrot bool // Chrome QUIC fingerprint parroting is on by default.
DisableChromeParrot bool // Chrome QUIC fingerprint parroting is on by default.
Version quic.Version // v1 or v2. Zero = quic-go default (v1).
}

type CongestionConfig struct {
Expand Down