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

Add support for the "QUICv1" server entry capability

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

+ 9 - 1
psiphon/common/protocol/protocol.go

@@ -392,7 +392,15 @@ const (
 )
 
 // The value of SupportedQUICVersions is conditionally compiled based on
-// whether gQUIC is enabled.
+// whether gQUIC is enabled. SupportedQUICv1Versions are the supported QUIC
+// versions that are based on QUICv1.
+
+var SupportedQUICv1Versions = QUICVersions{
+	QUIC_VERSION_V1,
+	QUIC_VERSION_RANDOMIZED_V1,
+	QUIC_VERSION_OBFUSCATED_V1,
+	QUIC_VERSION_DECOY_V1,
+}
 
 var legacyQUICVersions = QUICVersions{
 	"IETF-draft-24",

+ 28 - 2
psiphon/common/protocol/serverEntry.go

@@ -450,12 +450,26 @@ func GetTacticsCapability(protocol string) string {
 // Any internal "PASSTHROUGH-v2 or "PASSTHROUGH" componant in the server
 // entry's capabilities is ignored. These PASSTHROUGH components are used to
 // mask protocols which are running the passthrough mechanisms from older
-// clients which do not implement the passthrough messages. Older clients will
-// treat these capabilities as unknown protocols and skip them.
+// clients which do not implement the passthrough messages. Older clients
+// will treat these capabilities as unknown protocols and skip them.
+//
+// Any "QUICv1" capability is treated as "QUIC". "QUICv1" is used to mask the
+// QUIC-OSSH capability from older clients to ensure that older clients do
+// not send gQUIC packets to second generation QUICv1-only QUIC-OSSH servers.
+// New clients must check SupportsOnlyQUICv1 before selecting a QUIC version;
+// for "QUICv1", this ensures that new clients also do not select gQUIC to
+// QUICv1-only servers.
 func (serverEntry *ServerEntry) hasCapability(requiredCapability string) bool {
 	for _, capability := range serverEntry.Capabilities {
+
 		capability = strings.ReplaceAll(capability, "-PASSTHROUGH-v2", "")
 		capability = strings.ReplaceAll(capability, "-PASSTHROUGH", "")
+
+		quicCapability := GetCapability(TUNNEL_PROTOCOL_QUIC_OBFUSCATED_SSH)
+		if capability == quicCapability+"v1" {
+			capability = quicCapability
+		}
+
 		if capability == requiredCapability {
 			return true
 		}
@@ -472,6 +486,10 @@ func (serverEntry *ServerEntry) SupportsProtocol(protocol string) bool {
 
 // ProtocolUsesLegacyPassthrough indicates whether the ServerEntry supports
 // the specified protocol using legacy passthrough messages.
+//
+// There is no correspondong check for v2 passthrough, as clients send v2
+// passthrough messages unconditionally, by default, for passthrough
+// protocols.
 func (serverEntry *ServerEntry) ProtocolUsesLegacyPassthrough(protocol string) bool {
 	legacyCapability := GetCapability(protocol) + "-PASSTHROUGH"
 	for _, capability := range serverEntry.Capabilities {
@@ -482,6 +500,14 @@ func (serverEntry *ServerEntry) ProtocolUsesLegacyPassthrough(protocol string) b
 	return false
 }
 
+// SupportsOnlyQUICv1 indicates that the QUIC-OSSH server supports only QUICv1
+// and gQUIC versions should not be selected, as they will fail to connect
+// while sending atypical traffic to the server.
+func (serverEntry *ServerEntry) SupportsOnlyQUICv1() bool {
+	quicCapability := GetCapability(TUNNEL_PROTOCOL_QUIC_OBFUSCATED_SSH)
+	return serverEntry.hasCapability(quicCapability+"v1") && !serverEntry.hasCapability(quicCapability)
+}
+
 // ConditionallyEnabledComponents defines an interface which can be queried to
 // determine which conditionally compiled protocol components are present.
 type ConditionallyEnabledComponents interface {

+ 7 - 1
psiphon/dialParameters.go

@@ -1188,7 +1188,13 @@ func selectQUICVersion(
 
 	quicVersions := make([]string, 0)
 
-	for _, quicVersion := range protocol.SupportedQUICVersions {
+	// Don't use gQUIC versions when the server entry specifies QUICv1-only.
+	supportedQUICVersions := protocol.SupportedQUICVersions
+	if serverEntry.SupportsOnlyQUICv1() {
+		supportedQUICVersions = protocol.SupportedQUICv1Versions
+	}
+
+	for _, quicVersion := range supportedQUICVersions {
 
 		if len(limitQUICVersions) > 0 &&
 			!common.Contains(limitQUICVersions, quicVersion) {

+ 14 - 6
psiphon/server/config.go

@@ -719,6 +719,7 @@ type GenerateConfigParams struct {
 	Passthrough                 bool
 	LegacyPassthrough           bool
 	LimitQUICVersions           protocol.QUICVersions
+	EnableGQUIC                 bool
 }
 
 // GenerateConfig creates a new Psiphon server config. It returns JSON encoded
@@ -916,6 +917,7 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, []byte, []byt
 		OSLConfigFilename:              params.OSLConfigFilename,
 		TacticsConfigFilename:          params.TacticsConfigFilename,
 		LegacyPassthrough:              params.LegacyPassthrough,
+		EnableGQUIC:                    params.EnableGQUIC,
 	}
 
 	encodedConfig, err := json.MarshalIndent(config, "\n", "    ")
@@ -1011,6 +1013,7 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, []byte, []byt
 	for tunnelProtocol := range params.TunnelProtocolPorts {
 
 		capability := protocol.GetCapability(tunnelProtocol)
+
 		if params.Passthrough && protocol.TunnelProtocolSupportsPassthrough(tunnelProtocol) {
 			if !params.LegacyPassthrough {
 				capability += "-PASSTHROUGH-v2"
@@ -1018,6 +1021,11 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, []byte, []byt
 				capability += "-PASSTHROUGH"
 			}
 		}
+
+		if tunnelProtocol == protocol.TUNNEL_PROTOCOL_QUIC_OBFUSCATED_SSH && !params.EnableGQUIC {
+			capability += "v1"
+		}
+
 		capabilities = append(capabilities, capability)
 
 		if params.TacticsRequestPublicKey != "" && params.TacticsRequestObfuscatedKey != "" &&
@@ -1027,19 +1035,19 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, []byte, []byt
 		}
 	}
 
-	sshPort := params.TunnelProtocolPorts["SSH"]
-	obfuscatedSSHPort := params.TunnelProtocolPorts["OSSH"]
-	obfuscatedSSHQUICPort := params.TunnelProtocolPorts["QUIC-OSSH"]
+	sshPort := params.TunnelProtocolPorts[protocol.TUNNEL_PROTOCOL_SSH]
+	obfuscatedSSHPort := params.TunnelProtocolPorts[protocol.TUNNEL_PROTOCOL_OBFUSCATED_SSH]
+	obfuscatedSSHQUICPort := params.TunnelProtocolPorts[protocol.TUNNEL_PROTOCOL_QUIC_OBFUSCATED_SSH]
 
 	// Meek port limitations
 	// - fronted meek protocols are hard-wired in the client to be port 443 or 80.
 	// - only one other meek port may be specified.
-	meekPort := params.TunnelProtocolPorts["UNFRONTED-MEEK-OSSH"]
+	meekPort := params.TunnelProtocolPorts[protocol.TUNNEL_PROTOCOL_UNFRONTED_MEEK]
 	if meekPort == 0 {
-		meekPort = params.TunnelProtocolPorts["UNFRONTED-MEEK-HTTPS-OSSH"]
+		meekPort = params.TunnelProtocolPorts[protocol.TUNNEL_PROTOCOL_UNFRONTED_MEEK_HTTPS]
 	}
 	if meekPort == 0 {
-		meekPort = params.TunnelProtocolPorts["UNFRONTED-MEEK-SESSION-TICKET-OSSH"]
+		meekPort = params.TunnelProtocolPorts[protocol.TUNNEL_PROTOCOL_UNFRONTED_MEEK_SESSION_TICKET]
 	}
 
 	// Note: fronting params are a stub; this server entry will exercise

+ 9 - 2
psiphon/server/server_test.go

@@ -748,8 +748,14 @@ func runServer(t *testing.T, runConfig *runServerConfig) {
 
 	var limitQUICVersions protocol.QUICVersions
 	if runConfig.limitQUICVersions {
-		selectedQUICVersion := protocol.SupportedQUICVersions[prng.Intn(
-			len(protocol.SupportedQUICVersions))]
+
+		// Limit the server entry to one specific QUICv1 version, and check
+		// that this is used (see expectQUICVersion below). This test case
+		// also exercises disabling gQUIC in the server config and
+		// using "QUICv1" as the server entry capability.
+
+		selectedQUICVersion := protocol.SupportedQUICv1Versions[prng.Intn(
+			len(protocol.SupportedQUICv1Versions))]
 		limitQUICVersions = protocol.QUICVersions{selectedQUICVersion}
 	}
 
@@ -759,6 +765,7 @@ func runServer(t *testing.T, runConfig *runServerConfig) {
 		WebServerPort:        8000,
 		TunnelProtocolPorts:  map[string]int{runConfig.tunnelProtocol: psiphonServerPort},
 		LimitQUICVersions:    limitQUICVersions,
+		EnableGQUIC:          !runConfig.limitQUICVersions,
 	}
 
 	if doServerTactics {