Procházet zdrojové kódy

Support dynamic disabling of QUIC path MTU discovery via tactics

Rod Hynes před 4 roky
rodič
revize
866e7cf943

+ 5 - 3
psiphon/common/parameters/parameters.go

@@ -111,6 +111,7 @@ const (
 	LimitQUICVersionsProbability                     = "LimitQUICVersionsProbability"
 	LimitQUICVersionsProbability                     = "LimitQUICVersionsProbability"
 	LimitQUICVersions                                = "LimitQUICVersions"
 	LimitQUICVersions                                = "LimitQUICVersions"
 	DisableFrontingProviderQUICVersions              = "DisableFrontingProviderQUICVersions"
 	DisableFrontingProviderQUICVersions              = "DisableFrontingProviderQUICVersions"
+	QUICDisableClientPathMTUDiscoveryProbability     = "QUICDisableClientPathMTUDiscoveryProbability"
 	FragmentorProbability                            = "FragmentorProbability"
 	FragmentorProbability                            = "FragmentorProbability"
 	FragmentorLimitProtocols                         = "FragmentorLimitProtocols"
 	FragmentorLimitProtocols                         = "FragmentorLimitProtocols"
 	FragmentorMinTotalBytes                          = "FragmentorMinTotalBytes"
 	FragmentorMinTotalBytes                          = "FragmentorMinTotalBytes"
@@ -376,9 +377,10 @@ var defaultParameters = map[string]struct {
 	NoDefaultTLSSessionIDProbability:      {value: 0.5, minimum: 0.0},
 	NoDefaultTLSSessionIDProbability:      {value: 0.5, minimum: 0.0},
 	DisableFrontingProviderTLSProfiles:    {value: protocol.LabeledTLSProfiles{}},
 	DisableFrontingProviderTLSProfiles:    {value: protocol.LabeledTLSProfiles{}},
 
 
-	LimitQUICVersionsProbability:        {value: 1.0, minimum: 0.0},
-	LimitQUICVersions:                   {value: protocol.QUICVersions{}},
-	DisableFrontingProviderQUICVersions: {value: protocol.LabeledQUICVersions{}},
+	LimitQUICVersionsProbability:                 {value: 1.0, minimum: 0.0},
+	LimitQUICVersions:                            {value: protocol.QUICVersions{}},
+	DisableFrontingProviderQUICVersions:          {value: protocol.LabeledQUICVersions{}},
+	QUICDisableClientPathMTUDiscoveryProbability: {value: 0.0, minimum: 0.0},
 
 
 	FragmentorProbability:              {value: 0.5, minimum: 0.0},
 	FragmentorProbability:              {value: 0.5, minimum: 0.0},
 	FragmentorLimitProtocols:           {value: protocol.TunnelProtocols{}},
 	FragmentorLimitProtocols:           {value: protocol.TunnelProtocols{}},

+ 22 - 14
psiphon/common/quic/quic.go

@@ -354,7 +354,8 @@ func Dial(
 	quicVersion string,
 	quicVersion string,
 	clientHelloSeed *prng.Seed,
 	clientHelloSeed *prng.Seed,
 	obfuscationKey string,
 	obfuscationKey string,
-	obfuscationPaddingSeed *prng.Seed) (net.Conn, error) {
+	obfuscationPaddingSeed *prng.Seed,
+	disablePathMTUDiscovery bool) (net.Conn, error) {
 
 
 	if quicVersion == "" {
 	if quicVersion == "" {
 		return nil, errors.TraceNew("missing version")
 		return nil, errors.TraceNew("missing version")
@@ -435,6 +436,7 @@ func Dial(
 		clientHelloSeed,
 		clientHelloSeed,
 		getClientHelloRandom,
 		getClientHelloRandom,
 		maxPacketSizeAdjustment,
 		maxPacketSizeAdjustment,
+		disablePathMTUDiscovery,
 		false)
 		false)
 	if err != nil {
 	if err != nil {
 		packetConn.Close()
 		packetConn.Close()
@@ -638,12 +640,13 @@ func (conn *Conn) SetWriteDeadline(t time.Time) error {
 // CloseIdleConnections.
 // CloseIdleConnections.
 type QUICTransporter struct {
 type QUICTransporter struct {
 	quicRoundTripper
 	quicRoundTripper
-	noticeEmitter   func(string)
-	udpDialer       func(ctx context.Context) (net.PacketConn, *net.UDPAddr, error)
-	quicSNIAddress  string
-	quicVersion     string
-	clientHelloSeed *prng.Seed
-	packetConn      atomic.Value
+	noticeEmitter           func(string)
+	udpDialer               func(ctx context.Context) (net.PacketConn, *net.UDPAddr, error)
+	quicSNIAddress          string
+	quicVersion             string
+	clientHelloSeed         *prng.Seed
+	disablePathMTUDiscovery bool
+	packetConn              atomic.Value
 
 
 	mutex sync.Mutex
 	mutex sync.Mutex
 	ctx   context.Context
 	ctx   context.Context
@@ -656,7 +659,8 @@ func NewQUICTransporter(
 	udpDialer func(ctx context.Context) (net.PacketConn, *net.UDPAddr, error),
 	udpDialer func(ctx context.Context) (net.PacketConn, *net.UDPAddr, error),
 	quicSNIAddress string,
 	quicSNIAddress string,
 	quicVersion string,
 	quicVersion string,
-	clientHelloSeed *prng.Seed) (*QUICTransporter, error) {
+	clientHelloSeed *prng.Seed,
+	disablePathMTUDiscovery bool) (*QUICTransporter, error) {
 
 
 	if quicVersion == "" {
 	if quicVersion == "" {
 		return nil, errors.TraceNew("missing version")
 		return nil, errors.TraceNew("missing version")
@@ -672,12 +676,13 @@ func NewQUICTransporter(
 	}
 	}
 
 
 	t := &QUICTransporter{
 	t := &QUICTransporter{
-		noticeEmitter:   noticeEmitter,
-		udpDialer:       udpDialer,
-		quicSNIAddress:  quicSNIAddress,
-		quicVersion:     quicVersion,
-		clientHelloSeed: clientHelloSeed,
-		ctx:             ctx,
+		noticeEmitter:           noticeEmitter,
+		udpDialer:               udpDialer,
+		quicSNIAddress:          quicSNIAddress,
+		quicVersion:             quicVersion,
+		clientHelloSeed:         clientHelloSeed,
+		disablePathMTUDiscovery: disablePathMTUDiscovery,
+		ctx:                     ctx,
 	}
 	}
 
 
 	if isIETFVersionNumber(versionNumber) {
 	if isIETFVersionNumber(versionNumber) {
@@ -764,6 +769,7 @@ func (t *QUICTransporter) dialQUIC() (retSession quicSession, retErr error) {
 		t.clientHelloSeed,
 		t.clientHelloSeed,
 		nil,
 		nil,
 		0,
 		0,
+		t.disablePathMTUDiscovery,
 		true)
 		true)
 	if err != nil {
 	if err != nil {
 		packetConn.Close()
 		packetConn.Close()
@@ -890,6 +896,7 @@ func dialQUIC(
 	clientHelloSeed *prng.Seed,
 	clientHelloSeed *prng.Seed,
 	getClientHelloRandom func() ([]byte, error),
 	getClientHelloRandom func() ([]byte, error),
 	clientMaxPacketSizeAdjustment int,
 	clientMaxPacketSizeAdjustment int,
+	disablePathMTUDiscovery bool,
 	dialEarly bool) (quicSession, error) {
 	dialEarly bool) (quicSession, error) {
 
 
 	if isIETFVersionNumber(versionNumber) {
 	if isIETFVersionNumber(versionNumber) {
@@ -902,6 +909,7 @@ func dialQUIC(
 			ClientHelloSeed:               clientHelloSeed,
 			ClientHelloSeed:               clientHelloSeed,
 			GetClientHelloRandom:          getClientHelloRandom,
 			GetClientHelloRandom:          getClientHelloRandom,
 			ClientMaxPacketSizeAdjustment: clientMaxPacketSizeAdjustment,
 			ClientMaxPacketSizeAdjustment: clientMaxPacketSizeAdjustment,
+			DisablePathMTUDiscovery:       disablePathMTUDiscovery,
 		}
 		}
 
 
 		deadline, ok := ctx.Deadline()
 		deadline, ok := ctx.Deadline()

+ 4 - 2
psiphon/common/quic/quic_disabled.go

@@ -59,7 +59,8 @@ func Dial(
 	_ string,
 	_ string,
 	_ *prng.Seed,
 	_ *prng.Seed,
 	_ string,
 	_ string,
-	_ *prng.Seed) (net.Conn, error) {
+	_ *prng.Seed,
+	_ bool) (net.Conn, error) {
 
 
 	return nil, errors.TraceNew("operation is not enabled")
 	return nil, errors.TraceNew("operation is not enabled")
 }
 }
@@ -83,7 +84,8 @@ func NewQUICTransporter(
 	_ func(ctx context.Context) (net.PacketConn, *net.UDPAddr, error),
 	_ func(ctx context.Context) (net.PacketConn, *net.UDPAddr, error),
 	_ string,
 	_ string,
 	_ string,
 	_ string,
-	_ *prng.Seed) (*QUICTransporter, error) {
+	_ *prng.Seed,
+	_ bool) (*QUICTransporter, error) {
 
 
 	return nil, errors.TraceNew("operation is not enabled")
 	return nil, errors.TraceNew("operation is not enabled")
 }
 }

+ 4 - 1
psiphon/common/quic/quic_test.go

@@ -151,6 +151,8 @@ func runQUIC(
 
 
 	for i := 0; i < clients; i++ {
 	for i := 0; i < clients; i++ {
 
 
+		disablePathMTUDiscovery := i%2 == 0
+
 		testGroup.Go(func() error {
 		testGroup.Go(func() error {
 
 
 			ctx, cancelFunc := context.WithTimeout(
 			ctx, cancelFunc := context.WithTimeout(
@@ -194,7 +196,8 @@ func runQUIC(
 				quicVersion,
 				quicVersion,
 				clientHelloSeed,
 				clientHelloSeed,
 				clientObfuscationKey,
 				clientObfuscationKey,
-				obfuscationPaddingSeed)
+				obfuscationPaddingSeed,
+				disablePathMTUDiscovery)
 
 
 			if invokeAntiProbing {
 			if invokeAntiProbing {
 
 

+ 12 - 0
psiphon/config.go

@@ -761,6 +761,9 @@ type Config struct {
 	// LimitTunnelDialPortNumbers is for testing purposes.
 	// LimitTunnelDialPortNumbers is for testing purposes.
 	LimitTunnelDialPortNumbers parameters.TunnelProtocolPortLists
 	LimitTunnelDialPortNumbers parameters.TunnelProtocolPortLists
 
 
+	// QUICDisablePathMTUDiscoveryProbability is for testing purposes.
+	QUICDisablePathMTUDiscoveryProbability *float64
+
 	// params is the active parameters.Parameters with defaults, config values,
 	// params is the active parameters.Parameters with defaults, config values,
 	// and, optionally, tactics applied.
 	// and, optionally, tactics applied.
 	//
 	//
@@ -1740,6 +1743,10 @@ func (config *Config) makeConfigParameters() map[string]interface{} {
 		applyParameters[parameters.LimitTunnelDialPortNumbers] = config.LimitTunnelDialPortNumbers
 		applyParameters[parameters.LimitTunnelDialPortNumbers] = config.LimitTunnelDialPortNumbers
 	}
 	}
 
 
+	if config.QUICDisablePathMTUDiscoveryProbability != nil {
+		applyParameters[parameters.QUICDisableClientPathMTUDiscoveryProbability] = *config.QUICDisablePathMTUDiscoveryProbability
+	}
+
 	// When adding new config dial parameters that may override tactics, also
 	// When adding new config dial parameters that may override tactics, also
 	// update setDialParametersHash.
 	// update setDialParametersHash.
 
 
@@ -2071,6 +2078,11 @@ func (config *Config) setDialParametersHash() {
 		hash.Write(encodedLimitTunnelDialPortNumbers)
 		hash.Write(encodedLimitTunnelDialPortNumbers)
 	}
 	}
 
 
+	if config.QUICDisablePathMTUDiscoveryProbability != nil {
+		hash.Write([]byte("QUICDisablePathMTUDiscoveryProbability"))
+		binary.Write(hash, binary.LittleEndian, *config.QUICDisablePathMTUDiscoveryProbability)
+	}
+
 	config.dialParametersHash = hash.Sum(nil)
 	config.dialParametersHash = hash.Sum(nil)
 }
 }
 
 

+ 9 - 4
psiphon/dialParameters.go

@@ -112,10 +112,11 @@ type DialParameters struct {
 	TLSVersion               string
 	TLSVersion               string
 	RandomizedTLSProfileSeed *prng.Seed
 	RandomizedTLSProfileSeed *prng.Seed
 
 
-	QUICVersion               string
-	QUICDialSNIAddress        string
-	QUICClientHelloSeed       *prng.Seed
-	ObfuscatedQUICPaddingSeed *prng.Seed
+	QUICVersion                 string
+	QUICDialSNIAddress          string
+	QUICClientHelloSeed         *prng.Seed
+	ObfuscatedQUICPaddingSeed   *prng.Seed
+	QUICDisablePathMTUDiscovery bool
 
 
 	ConjureCachedRegistrationTTL        time.Duration
 	ConjureCachedRegistrationTTL        time.Duration
 	ConjureAPIRegistration              bool
 	ConjureAPIRegistration              bool
@@ -678,6 +679,9 @@ func MakeDialParameters(
 				return nil, errors.Trace(err)
 				return nil, errors.Trace(err)
 			}
 			}
 		}
 		}
+
+		dialParams.QUICDisablePathMTUDiscovery =
+			p.WeightedCoinFlip(parameters.QUICDisableClientPathMTUDiscoveryProbability)
 	}
 	}
 
 
 	if (!isReplay || !replayObfuscatedQUIC) &&
 	if (!isReplay || !replayObfuscatedQUIC) &&
@@ -882,6 +886,7 @@ func MakeDialParameters(
 			UseQUIC:                       protocol.TunnelProtocolUsesFrontedMeekQUIC(dialParams.TunnelProtocol),
 			UseQUIC:                       protocol.TunnelProtocolUsesFrontedMeekQUIC(dialParams.TunnelProtocol),
 			QUICVersion:                   dialParams.QUICVersion,
 			QUICVersion:                   dialParams.QUICVersion,
 			QUICClientHelloSeed:           dialParams.QUICClientHelloSeed,
 			QUICClientHelloSeed:           dialParams.QUICClientHelloSeed,
+			QUICDisablePathMTUDiscovery:   dialParams.QUICDisablePathMTUDiscovery,
 			UseHTTPS:                      usingTLS,
 			UseHTTPS:                      usingTLS,
 			TLSProfile:                    dialParams.TLSProfile,
 			TLSProfile:                    dialParams.TLSProfile,
 			LegacyPassthrough:             serverEntry.ProtocolUsesLegacyPassthrough(dialParams.TunnelProtocol),
 			LegacyPassthrough:             serverEntry.ProtocolUsesLegacyPassthrough(dialParams.TunnelProtocol),

+ 6 - 1
psiphon/meekConn.go

@@ -118,6 +118,10 @@ type MeekConfig struct {
 	// QUICClientHelloSeed is used for randomized QUIC Client Hellos.
 	// QUICClientHelloSeed is used for randomized QUIC Client Hellos.
 	QUICClientHelloSeed *prng.Seed
 	QUICClientHelloSeed *prng.Seed
 
 
+	// QUICDisablePathMTUDiscovery indicates whether to disable path MTU
+	// discovery in the QUIC client.
+	QUICDisablePathMTUDiscovery bool
+
 	// UseHTTPS indicates whether to use HTTPS (true) or HTTP (false).
 	// UseHTTPS indicates whether to use HTTPS (true) or HTTP (false).
 	UseHTTPS bool
 	UseHTTPS bool
 
 
@@ -368,7 +372,8 @@ func DialMeek(
 			udpDialer,
 			udpDialer,
 			quicDialSNIAddress,
 			quicDialSNIAddress,
 			meekConfig.QUICVersion,
 			meekConfig.QUICVersion,
-			meekConfig.QUICClientHelloSeed)
+			meekConfig.QUICClientHelloSeed,
+			meekConfig.QUICDisablePathMTUDiscovery)
 		if err != nil {
 		if err != nil {
 			return nil, errors.Trace(err)
 			return nil, errors.Trace(err)
 		}
 		}

+ 4 - 0
psiphon/notice.go

@@ -537,6 +537,10 @@ func noticeWithDialParameters(noticeType string, dialParams *DialParameters) {
 			args = append(args, "QUICDialSNIAddress", dialParams.QUICDialSNIAddress)
 			args = append(args, "QUICDialSNIAddress", dialParams.QUICDialSNIAddress)
 		}
 		}
 
 
+		if dialParams.QUICDisablePathMTUDiscovery {
+			args = append(args, "QUICDisableClientPathMTUDiscovery", dialParams.QUICDisablePathMTUDiscovery)
+		}
+
 		if dialParams.DialDuration > 0 {
 		if dialParams.DialDuration > 0 {
 			args = append(args, "dialDuration", dialParams.DialDuration)
 			args = append(args, "dialDuration", dialParams.DialDuration)
 		}
 		}

+ 1 - 0
psiphon/server/api.go

@@ -885,6 +885,7 @@ var baseDialParams = []requestParamSpec{
 	{"dial_port_number", isIntString, requestParamOptional | requestParamLogStringAsInt},
 	{"dial_port_number", isIntString, requestParamOptional | requestParamLogStringAsInt},
 	{"quic_version", isAnyString, requestParamOptional},
 	{"quic_version", isAnyString, requestParamOptional},
 	{"quic_dial_sni_address", isAnyString, requestParamOptional},
 	{"quic_dial_sni_address", isAnyString, requestParamOptional},
+	{"quic_disable_client_path_mtu_discovery", isBooleanFlag, requestParamOptional | requestParamLogFlagAsBool},
 	{"upstream_bytes_fragmented", isIntString, requestParamOptional | requestParamLogStringAsInt},
 	{"upstream_bytes_fragmented", isIntString, requestParamOptional | requestParamLogStringAsInt},
 	{"upstream_min_bytes_written", isIntString, requestParamOptional | requestParamLogStringAsInt},
 	{"upstream_min_bytes_written", isIntString, requestParamOptional | requestParamLogStringAsInt},
 	{"upstream_max_bytes_written", isIntString, requestParamOptional | requestParamLogStringAsInt},
 	{"upstream_max_bytes_written", isIntString, requestParamOptional | requestParamLogStringAsInt},

+ 4 - 0
psiphon/serverApi.go

@@ -1011,6 +1011,10 @@ func getBaseAPIParameters(
 			params["quic_dial_sni_address"] = dialParams.QUICDialSNIAddress
 			params["quic_dial_sni_address"] = dialParams.QUICDialSNIAddress
 		}
 		}
 
 
+		if dialParams.QUICDisablePathMTUDiscovery {
+			params["quic_disable_client_path_mtu_discovery"] = "1"
+		}
+
 		isReplay := "0"
 		isReplay := "0"
 		if dialParams.IsReplay {
 		if dialParams.IsReplay {
 			isReplay = "1"
 			isReplay = "1"

+ 2 - 1
psiphon/tunnel.go

@@ -771,7 +771,8 @@ func dialTunnel(
 			dialParams.QUICVersion,
 			dialParams.QUICVersion,
 			dialParams.QUICClientHelloSeed,
 			dialParams.QUICClientHelloSeed,
 			dialParams.ServerEntry.SshObfuscatedKey,
 			dialParams.ServerEntry.SshObfuscatedKey,
-			dialParams.ObfuscatedQUICPaddingSeed)
+			dialParams.ObfuscatedQUICPaddingSeed,
+			dialParams.QUICDisablePathMTUDiscovery)
 		if err != nil {
 		if err != nil {
 			return nil, errors.Trace(err)
 			return nil, errors.Trace(err)
 		}
 		}