瀏覽代碼

Add ProxyAnnounceRequest.PreCheckTactics

- Also allow InproxyPersonalPairingMaxBrokerSpecCount to be set to 0, the
  no-max setting
Rod Hynes 1 月之前
父節點
當前提交
8a1374707e

+ 6 - 0
psiphon/common/inproxy/api.go

@@ -299,12 +299,18 @@ type ClientMetrics struct {
 // overhead, proxies with multiple workers should designate just one worker
 // to set CheckTactics.
 //
+// When PreCheckTactics is set, the broker checks tactics as with
+// CheckTactics, but responds immediately without awaiting a match. This
+// option enables the proxy to quickly establish the shared Noise protocol
+// session and launch all workers.
+//
 // The proxy's session public key is an implicit and cryptographically
 // verified proxy ID.
 type ProxyAnnounceRequest struct {
 	PersonalCompartmentIDs []ID          `cbor:"1,keyasint,omitempty"`
 	Metrics                *ProxyMetrics `cbor:"2,keyasint,omitempty"`
 	CheckTactics           bool          `cbor:"3,keyasint,omitempty"`
+	PreCheckTactics        bool          `cbor:"4,keyasint,omitempty"`
 }
 
 // WebRTCSessionDescription is compatible with pion/webrtc.SessionDescription

+ 12 - 2
psiphon/common/inproxy/broker.go

@@ -729,16 +729,21 @@ func (b *Broker) handleProxyAnnounce(
 	// existing, cached tactics. In the case where tactics have changed,
 	// don't enqueue the proxy announcement and return no-match so that the
 	// proxy can store and apply the new tactics before announcing again.
+	//
+	// For PreCheckTactics requests, an immediate no-match response is
+	// returned even when there are no new tactics.
 
 	var tacticsPayload []byte
-	if announceRequest.CheckTactics {
+	if announceRequest.CheckTactics || announceRequest.PreCheckTactics {
 		tacticsPayload, newTacticsTag, err =
 			b.config.GetTacticsPayload(geoIPData, apiParams)
 		if err != nil {
 			return nil, errors.Trace(err)
 		}
 
-		if tacticsPayload != nil && newTacticsTag != "" {
+		if (tacticsPayload != nil && newTacticsTag != "") ||
+			announceRequest.PreCheckTactics {
+
 			responsePayload, err := MarshalProxyAnnounceResponse(
 				&ProxyAnnounceResponse{
 					TacticsPayload: tacticsPayload,
@@ -756,6 +761,11 @@ func (b *Broker) handleProxyAnnounce(
 	// such as censored locations, from announcing. Proxies with personal
 	// compartment IDs are always allowed, as they will be used only by
 	// clients specifically configured to use them.
+	//
+	// AllowProxy is not enforced until after CheckTactics/PreCheckTactics
+	// cases, which may return an immediate response. This allows proxies to
+	// download new tactics that may set AllowProxy, which well-behaved
+	// proxies can enforce locally as well.
 
 	if !hasPersonalCompartmentIDs &&
 		!b.config.AllowProxy(geoIPData) {

+ 45 - 15
psiphon/common/inproxy/proxy.go

@@ -371,14 +371,13 @@ func (p *Proxy) Run(ctx context.Context) {
 	//   session establisher to be a different worker than the no-delay worker.
 	//
 	// The first worker is the only proxy worker which sets
-	// ProxyAnnounceRequest.CheckTactics.
-	//
-	// Limitation: currently, the first proxy is always common (unless
-	// MaxCommonClients == 0). We might want to change this later
-	// so that the first message is just an announcement, and not a full
-	// proxy, so we don't have to decide its type.
+	// ProxyAnnounceRequest.CheckTactics/PreCheckTactics. PreCheckTactics is
+	// used on the first announcement so the request returns immediately
+	// without awaiting a match. This allows all workers to be launched
+	// quickly.
 
-	commonProxiesToCreate, personalProxiesToCreate := p.config.MaxCommonClients, p.config.MaxPersonalClients
+	commonProxiesToCreate, personalProxiesToCreate :=
+		p.config.MaxCommonClients, p.config.MaxPersonalClients
 
 	// Doing this outside of the go routine to avoid race conditions
 	firstWorkerIsPersonal := p.config.MaxCommonClients <= 0
@@ -450,8 +449,11 @@ func (p *Proxy) activityUpdate(period time.Duration) {
 	greaterThanSwapInt64(&p.peakBytesUp, bytesUp)
 	greaterThanSwapInt64(&p.peakBytesDown, bytesDown)
 
-	personalRegionActivity := p.snapshotAndResetRegionActivity(&p.personalStatsMutex, p.personalRegionActivity)
-	commonRegionActivity := p.snapshotAndResetRegionActivity(&p.commonStatsMutex, p.commonRegionActivity)
+	personalRegionActivity := p.snapshotAndResetRegionActivity(
+		&p.personalStatsMutex, p.personalRegionActivity)
+
+	commonRegionActivity := p.snapshotAndResetRegionActivity(
+		&p.commonStatsMutex, p.commonRegionActivity)
 
 	stateChanged := announcing != p.lastAnnouncing ||
 		connectingClients != p.lastConnectingClients ||
@@ -675,6 +677,8 @@ func (p *Proxy) proxyClients(
 		return false
 	}
 
+	preCheckTacticsDone := false
+
 	for ctx.Err() == nil {
 
 		if !p.config.WaitForNetworkConnectivity() {
@@ -714,7 +718,7 @@ func (p *Proxy) proxyClients(
 		}
 
 		backOff, err := p.proxyOneClient(
-			ctx, logAnnounce, signalAnnounceDone, isPersonal)
+			ctx, logAnnounce, &preCheckTacticsDone, signalAnnounceDone, isPersonal)
 
 		if !backOff || err == nil {
 			failureDelayFactor = 1
@@ -839,6 +843,7 @@ func (p *Proxy) doNetworkDiscovery(
 func (p *Proxy) proxyOneClient(
 	ctx context.Context,
 	logAnnounce func() bool,
+	preCheckTacticsDone *bool,
 	signalAnnounceDone func(),
 	isPersonal bool) (bool, error) {
 
@@ -914,7 +919,8 @@ func (p *Proxy) proxyOneClient(
 
 	// Only the first worker, which has signalAnnounceDone configured, checks
 	// for tactics.
-	checkTactics := signalAnnounceDone != nil
+	checkTactics := signalAnnounceDone != nil && *preCheckTacticsDone
+	preCheckTactics := signalAnnounceDone != nil && !*preCheckTacticsDone
 
 	maxCommonClients, maxPersonalClients, rateLimits := p.getLimits()
 
@@ -929,7 +935,12 @@ func (p *Proxy) proxyOneClient(
 	// with the original network ID.
 
 	metrics, tacticsNetworkID, compressTactics, err := p.getMetrics(
-		checkTactics, brokerCoordinator, webRTCCoordinator, maxCommonClients, maxPersonalClients, rateLimits)
+		checkTactics || preCheckTactics,
+		brokerCoordinator,
+		webRTCCoordinator,
+		maxCommonClients,
+		maxPersonalClients,
+		rateLimits)
 	if err != nil {
 		return backOff, errors.Trace(err)
 	}
@@ -997,6 +1008,7 @@ func (p *Proxy) proxyOneClient(
 			PersonalCompartmentIDs: personalCompartmentIDs,
 			Metrics:                metrics,
 			CheckTactics:           checkTactics,
+			PreCheckTactics:        preCheckTactics,
 		})
 	if logAnnounce() {
 		p.config.Logger.WithTraceFields(common.LogFields{
@@ -1029,12 +1041,16 @@ func (p *Proxy) proxyOneClient(
 		}
 	}
 
-	// Signal that the announce round trip is complete. At this point, the
-	// broker Noise session should be established and any fresh tactics
-	// applied.
+	// Signal that the announce round trip is complete, allowing other workers
+	// to launch. At this point, the broker Noise session should be established
+	// and any fresh tactics applied. Also toggle preCheckTacticsDone since
+	// there's no need to retry PreCheckTactics once a round trip succeeds.
 	if signalAnnounceDone != nil {
 		signalAnnounceDone()
 	}
+	if preCheckTactics {
+		*preCheckTacticsDone = true
+	}
 
 	// MustUpgrade has precedence over other cases, to ensure the callback is
 	// invoked. Trigger back-off back off when rate/entry limited or must
@@ -1055,10 +1071,24 @@ func (p *Proxy) proxyOneClient(
 
 	} else if announceResponse.NoMatch {
 
+		// No backoff for no-match.
+		//
+		// This is also the expected response for CheckTactics with a tactics
+		// payload and PreCheckTactics with or without a tactics payload,
+		// distinct cases which should not back off.
+
 		return backOff, errors.TraceNew("no match")
 
 	}
 
+	if preCheckTactics && !announceResponse.NoMatch {
+
+		// Sanity check: the broker should always respond with no-match for
+		// PreCheckTactics.
+
+		return backOff, errors.TraceNew("unexpected PreCheckTactics response")
+	}
+
 	if announceResponse.SelectedProtocolVersion < ProtocolVersion1 ||
 		(announceResponse.UseMediaStreams &&
 			announceResponse.SelectedProtocolVersion < ProtocolVersion2) ||

+ 1 - 1
psiphon/common/parameters/parameters.go

@@ -1042,7 +1042,7 @@ var defaultParameters = map[string]struct {
 	InproxyAllBrokerSpecs:                              {value: InproxyBrokerSpecsValue{}, flags: serverSideOnly},
 	InproxyBrokerSpecs:                                 {value: InproxyBrokerSpecsValue{}},
 	InproxyPersonalPairingBrokerSpecs:                  {value: InproxyBrokerSpecsValue{}},
-	InproxyPersonalPairingMaxBrokerSpecCount:           {value: 3, minimum: 1},
+	InproxyPersonalPairingMaxBrokerSpecCount:           {value: 3, minimum: 0},
 	InproxyProxyBrokerSpecs:                            {value: InproxyBrokerSpecsValue{}},
 	InproxyProxyPersonalPairingBrokerSpecs:             {value: InproxyBrokerSpecsValue{}},
 	InproxyClientBrokerSpecs:                           {value: InproxyBrokerSpecsValue{}},