Просмотр исходного кода

Add ServerEntry.LimitQUICVersions

Rod Hynes 4 лет назад
Родитель
Сommit
62dd438c9b

+ 19 - 3
psiphon/common/protocol/serverEntry.go

@@ -55,6 +55,7 @@ type ServerEntry struct {
 	SshHostKey                    string   `json:"sshHostKey"`
 	SshObfuscatedPort             int      `json:"sshObfuscatedPort"`
 	SshObfuscatedQUICPort         int      `json:"sshObfuscatedQUICPort"`
+	LimitQUICVersions             []string `json:"limitQUICVersions"`
 	SshObfuscatedTapDancePort     int      `json:"sshObfuscatedTapdancePort"`
 	SshObfuscatedConjurePort      int      `json:"sshObfuscatedConjurePort"`
 	SshObfuscatedKey              string   `json:"sshObfuscatedKey"`
@@ -497,11 +498,12 @@ type TunnelProtocolPortLists map[string]*common.PortList
 func (serverEntry *ServerEntry) GetSupportedProtocols(
 	conditionallyEnabled ConditionallyEnabledComponents,
 	useUpstreamProxy bool,
-	limitTunnelProtocols []string,
+	limitTunnelProtocols TunnelProtocols,
 	limitTunnelDialPortNumbers TunnelProtocolPortLists,
-	excludeIntensive bool) []string {
+	limitQUICVersions QUICVersions,
+	excludeIntensive bool) TunnelProtocols {
 
-	supportedProtocols := make([]string, 0)
+	supportedProtocols := make(TunnelProtocols, 0)
 
 	for _, tunnelProtocol := range SupportedTunnelProtocols {
 
@@ -533,6 +535,20 @@ func (serverEntry *ServerEntry) GetSupportedProtocols(
 			continue
 		}
 
+		// If the server is limiting QUIC versions, at least one must be
+		// supported. And if tactics is also limiting QUIC versions, there
+		// must be a common version in both limit lists for this server entry
+		// to support QUIC-OSSH.
+		if TunnelProtocolUsesQUIC(tunnelProtocol) && len(serverEntry.LimitQUICVersions) > 0 {
+			if !common.ContainsAny(serverEntry.LimitQUICVersions, SupportedQUICVersions) {
+				continue
+			}
+			if len(limitQUICVersions) > 0 &&
+				!common.ContainsAny(serverEntry.LimitQUICVersions, limitQUICVersions) {
+				continue
+			}
+		}
+
 		dialPortNumber, err := serverEntry.GetDialPortNumber(tunnelProtocol)
 		if err != nil {
 			continue

+ 2 - 0
psiphon/common/quic/quic.go

@@ -360,6 +360,8 @@ func Dial(
 		return nil, errors.TraceNew("missing client hello randomization values")
 	}
 
+	// obfuscationKey is always required, as it is used for anti-probing even
+	// when not obfuscating the QUIC payload.
 	if isObfuscated(quicVersion) && (obfuscationPaddingSeed == nil) || obfuscationKey == "" {
 		return nil, errors.TraceNew("missing obfuscation values")
 	}

+ 4 - 0
psiphon/controller.go

@@ -1363,6 +1363,7 @@ type protocolSelectionConstraints struct {
 	initialLimitTunnelProtocolsCandidateCount int
 	limitTunnelProtocols                      protocol.TunnelProtocols
 	limitTunnelDialPortNumbers                protocol.TunnelProtocolPortLists
+	limitQUICVersions                         protocol.QUICVersions
 	replayCandidateCount                      int
 }
 
@@ -1380,6 +1381,7 @@ func (p *protocolSelectionConstraints) isInitialCandidate(
 			p.useUpstreamProxy,
 			p.initialLimitTunnelProtocols,
 			p.limitTunnelDialPortNumbers,
+			p.limitQUICVersions,
 			excludeIntensive)) > 0
 }
 
@@ -1392,6 +1394,7 @@ func (p *protocolSelectionConstraints) isCandidate(
 		p.useUpstreamProxy,
 		p.limitTunnelProtocols,
 		p.limitTunnelDialPortNumbers,
+		p.limitQUICVersions,
 		excludeIntensive)) > 0
 }
 
@@ -1428,6 +1431,7 @@ func (p *protocolSelectionConstraints) supportedProtocols(
 		p.useUpstreamProxy,
 		limitTunnelProtocols,
 		p.limitTunnelDialPortNumbers,
+		p.limitQUICVersions,
 		excludeIntensive)
 }
 

+ 2 - 0
psiphon/dataStore.go

@@ -675,6 +675,7 @@ func newTargetServerEntryIterator(config *Config, isTactics bool) (bool, *Server
 		limitTunnelProtocols := p.TunnelProtocols(parameters.LimitTunnelProtocols)
 		limitTunnelDialPortNumbers := protocol.TunnelProtocolPortLists(
 			p.TunnelProtocolPortLists(parameters.LimitTunnelDialPortNumbers))
+		limitQUICVersions := p.QUICVersions(parameters.LimitQUICVersions)
 
 		if len(limitTunnelProtocols) > 0 {
 			// At the ServerEntryIterator level, only limitTunnelProtocols is applied;
@@ -684,6 +685,7 @@ func newTargetServerEntryIterator(config *Config, isTactics bool) (bool, *Server
 				config.UseUpstreamProxy(),
 				limitTunnelProtocols,
 				limitTunnelDialPortNumbers,
+				limitQUICVersions,
 				false)) == 0 {
 				return false, nil, errors.Tracef(
 					"TargetServerEntry does not support LimitTunnelProtocols: %v", limitTunnelProtocols)

+ 21 - 4
psiphon/dialParameters.go

@@ -659,7 +659,7 @@ func MakeDialParameters(
 		protocol.TunnelProtocolUsesQUIC(dialParams.TunnelProtocol) {
 
 		isFronted := protocol.TunnelProtocolUsesFrontedMeekQUIC(dialParams.TunnelProtocol)
-		dialParams.QUICVersion = selectQUICVersion(isFronted, serverEntry.FrontingProviderID, p)
+		dialParams.QUICVersion = selectQUICVersion(isFronted, serverEntry, p)
 
 		if protocol.QUICVersionHasRandomizedClientHello(dialParams.QUICVersion) {
 			dialParams.QUICClientHelloSeed, err = prng.NewSeed()
@@ -1147,7 +1147,7 @@ func selectFrontingParameters(
 
 func selectQUICVersion(
 	isFronted bool,
-	frontingProviderID string,
+	serverEntry *protocol.ServerEntry,
 	p parameters.ParametersAccessor) string {
 
 	limitQUICVersions := p.QUICVersions(parameters.LimitQUICVersions)
@@ -1155,16 +1155,18 @@ func selectQUICVersion(
 	var disableQUICVersions protocol.QUICVersions
 
 	if isFronted {
-		if frontingProviderID == "" {
+		if serverEntry.FrontingProviderID == "" {
 			// Legacy server entry case
 			disableQUICVersions = protocol.QUICVersions{
 				protocol.QUIC_VERSION_V1,
 				protocol.QUIC_VERSION_RANDOMIZED_V1,
 				protocol.QUIC_VERSION_OBFUSCATED_V1,
+				protocol.QUIC_VERSION_DECOY_V1,
 			}
 		} else {
 			disableQUICVersions = p.LabeledQUICVersions(
-				parameters.DisableFrontingProviderQUICVersions, frontingProviderID)
+				parameters.DisableFrontingProviderQUICVersions,
+				serverEntry.FrontingProviderID)
 		}
 	}
 
@@ -1177,6 +1179,21 @@ func selectQUICVersion(
 			continue
 		}
 
+		// Both tactics and the server entry can specify LimitQUICVersions. In
+		// tactics, the parameter is intended to direct certain clients to
+		// use a successful protocol variant. In the server entry, the
+		// parameter may be used to direct all clients to send
+		// consistent-looking protocol variants to a particular server; e.g.,
+		// only regular QUIC, or only obfuscated QUIC.
+		//
+		// The isFronted/QUICVersionIsObfuscated logic predates
+		// ServerEntry.LimitQUICVersions; ServerEntry.LimitQUICVersions could
+		// now be used to achieve a similar outcome.
+		if len(serverEntry.LimitQUICVersions) > 0 &&
+			!common.Contains(serverEntry.LimitQUICVersions, quicVersion) {
+			continue
+		}
+
 		if isFronted &&
 			protocol.QUICVersionIsObfuscated(quicVersion) {
 			continue

+ 2 - 0
psiphon/server/config.go

@@ -704,6 +704,7 @@ type GenerateConfigParams struct {
 	TacticsRequestObfuscatedKey string
 	Passthrough                 bool
 	LegacyPassthrough           bool
+	LimitQUICVersions           protocol.QUICVersions
 }
 
 // GenerateConfig creates a new Psiphon server config. It returns JSON encoded
@@ -1053,6 +1054,7 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, []byte, []byt
 		SshHostKey:                    base64.RawStdEncoding.EncodeToString(sshPublicKey.Marshal()),
 		SshObfuscatedPort:             obfuscatedSSHPort,
 		SshObfuscatedQUICPort:         obfuscatedSSHQUICPort,
+		LimitQUICVersions:             params.LimitQUICVersions,
 		SshObfuscatedKey:              obfuscatedSSHKey,
 		Capabilities:                  capabilities,
 		Region:                        "US",

+ 67 - 1
psiphon/server/server_test.go

@@ -135,6 +135,7 @@ func TestSSH(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -157,6 +158,7 @@ func TestOSSH(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -179,6 +181,7 @@ func TestFragmentedOSSH(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -201,6 +204,7 @@ func TestUnfrontedMeek(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -224,6 +228,7 @@ func TestUnfrontedMeekHTTPS(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -247,6 +252,7 @@ func TestUnfrontedMeekHTTPSTLS13(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -270,6 +276,7 @@ func TestUnfrontedMeekSessionTicket(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -293,6 +300,7 @@ func TestUnfrontedMeekSessionTicketTLS13(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -318,6 +326,33 @@ func TestQUICOSSH(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
+		})
+}
+
+func TestLimitedQUICOSSH(t *testing.T) {
+	if !quic.Enabled() {
+		t.Skip("QUIC is not enabled")
+	}
+	runServer(t,
+		&runServerConfig{
+			tunnelProtocol:       "QUIC-OSSH",
+			enableSSHAPIRequests: true,
+			doHotReload:          false,
+			doDefaultSponsorID:   false,
+			denyTrafficRules:     false,
+			requireAuthorization: true,
+			omitAuthorization:    false,
+			doTunneledWebRequest: true,
+			doTunneledNTPRequest: true,
+			forceFragmenting:     false,
+			forceLivenessTest:    false,
+			doPruneServerEntries: false,
+			doDanglingTCPConn:    false,
+			doPacketManipulation: false,
+			doBurstMonitor:       false,
+			doSplitTunnel:        false,
+			limitQUICVersions:    true,
 		})
 }
 
@@ -340,6 +375,7 @@ func TestWebTransportAPIRequests(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -362,6 +398,7 @@ func TestHotReload(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -384,6 +421,7 @@ func TestDefaultSponsorID(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -406,6 +444,7 @@ func TestDenyTrafficRules(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -428,6 +467,7 @@ func TestOmitAuthorization(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -450,6 +490,7 @@ func TestNoAuthorization(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -472,6 +513,7 @@ func TestUnusedAuthorization(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -494,6 +536,7 @@ func TestTCPOnlySLOK(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -516,6 +559,7 @@ func TestUDPOnlySLOK(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -538,6 +582,7 @@ func TestLivenessTest(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -560,6 +605,7 @@ func TestPruneServerEntries(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -582,6 +628,7 @@ func TestBurstMonitor(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       true,
 			doSplitTunnel:        false,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -604,6 +651,7 @@ func TestSplitTunnel(t *testing.T) {
 			doPacketManipulation: false,
 			doBurstMonitor:       false,
 			doSplitTunnel:        true,
+			limitQUICVersions:    false,
 		})
 }
 
@@ -625,6 +673,7 @@ type runServerConfig struct {
 	doPacketManipulation bool
 	doBurstMonitor       bool
 	doSplitTunnel        bool
+	limitQUICVersions    bool
 }
 
 var (
@@ -697,11 +746,19 @@ func runServer(t *testing.T, runConfig *runServerConfig) {
 	}
 	psiphonServerPort := 4000
 
+	var limitQUICVersions protocol.QUICVersions
+	if runConfig.limitQUICVersions {
+		selectedQUICVersion := protocol.SupportedQUICVersions[prng.Intn(
+			len(protocol.SupportedQUICVersions))]
+		limitQUICVersions = protocol.QUICVersions{selectedQUICVersion}
+	}
+
 	generateConfigParams := &GenerateConfigParams{
 		ServerIPAddress:      psiphonServerIPAddress,
 		EnableSSHAPIRequests: runConfig.enableSSHAPIRequests,
 		WebServerPort:        8000,
 		TunnelProtocolPorts:  map[string]int{runConfig.tunnelProtocol: psiphonServerPort},
+		LimitQUICVersions:    limitQUICVersions,
 	}
 
 	if doServerTactics {
@@ -1310,6 +1367,10 @@ func runServer(t *testing.T, runConfig *runServerConfig) {
 	expectTCPDataTransfer := runConfig.doTunneledWebRequest && !expectTrafficFailure && !runConfig.doSplitTunnel
 	// Even with expectTrafficFailure, DNS port forwards will succeed
 	expectUDPDataTransfer := runConfig.doTunneledNTPRequest
+	expectQUICVersion := ""
+	if runConfig.limitQUICVersions {
+		expectQUICVersion = limitQUICVersions[0]
+	}
 
 	select {
 	case logFields := <-serverTunnelLog:
@@ -1322,6 +1383,7 @@ func runServer(t *testing.T, runConfig *runServerConfig) {
 			expectTCPPortForwardDial,
 			expectTCPDataTransfer,
 			expectUDPDataTransfer,
+			expectQUICVersion,
 			logFields)
 		if err != nil {
 			t.Fatalf("invalid server tunnel log fields: %s", err)
@@ -1382,6 +1444,7 @@ func checkExpectedServerTunnelLogFields(
 	expectTCPPortForwardDial bool,
 	expectTCPDataTransfer bool,
 	expectUDPDataTransfer bool,
+	expectQUICVersion string,
 	fields map[string]interface{}) error {
 
 	// Limitations:
@@ -1557,7 +1620,10 @@ func checkExpectedServerTunnelLogFields(
 			}
 		}
 
-		if !common.Contains(protocol.SupportedQUICVersions, fields["quic_version"].(string)) {
+		quicVersion := fields["quic_version"].(string)
+		if !common.Contains(protocol.SupportedQUICVersions, quicVersion) ||
+			(runConfig.limitQUICVersions && quicVersion != expectQUICVersion) {
+
 			return fmt.Errorf("unexpected quic_version '%s'", fields["quic_version"])
 		}
 	}

+ 10 - 2
vendor/github.com/Psiphon-Labs/quic-go/packet_packer.go

@@ -123,8 +123,16 @@ func getMaxPacketSize(addr net.Addr, maxPacketSizeAdustment int) protocol.ByteCo
 		}
 
 		// [Psiphon]
-		// Adjust the max packet size to allow for obfuscation overhead.
-		// TODO: internal/congestion.cubicSender continues to use initialMaxDatagramSize = protocol.InitialPacketSizeIPv4
+		//
+		// Adjust the max packet size to allow for obfuscation overhead. This
+		// is a best-effort operation. In practice, maxPacketSizeAdustment
+		// will be tens of bytes and maxSize is over 1200 bytes; the
+		// condition here is a sanity check guard to prevent negative sizes
+		// and possible panics. We don't expect to need to make the largest
+		// adustment that would be possible when the condition is false.
+		//
+		// TODO: internal/congestion.cubicSender continues to use
+		// initialMaxDatagramSize = protocol.InitialPacketSizeIPv4
 		if maxSize > protocol.ByteCount(maxPacketSizeAdustment) {
 			maxSize -= protocol.ByteCount(maxPacketSizeAdustment)
 		}