Rod Hynes 6 месяцев назад
Родитель
Сommit
8605fb6f35

+ 8 - 0
psiphon/common/inproxy/broker.go

@@ -151,6 +151,12 @@ type BrokerConfig struct {
 	// adds server-side enforcement.
 	AllowDomainFrontedDestinations func(common.GeoIPData) bool
 
+	// AllowMatch is a callback which can indicate whether a proxy and client
+	// pair, with the given, respective GeoIP data, is allowed to match
+	// together. Pairs are always allowed to match based on personal
+	// compartment ID.
+	AllowMatch func(common.GeoIPData, common.GeoIPData) bool
+
 	// LookupGeoIP provides GeoIP lookup service.
 	LookupGeoIP LookupGeoIP
 
@@ -261,6 +267,8 @@ func NewBroker(config *BrokerConfig) (*Broker, error) {
 			ProxyQualityState: proxyQuality,
 
 			IsLoadLimiting: config.IsLoadLimiting,
+
+			AllowMatch: config.AllowMatch,
 		}),
 
 		proxyQualityState: proxyQuality,

+ 1 - 0
psiphon/common/inproxy/inproxy_test.go

@@ -285,6 +285,7 @@ func runTestInproxy(doMustUpgrade bool) error {
 		AllowProxy:                     func(common.GeoIPData) bool { return true },
 		AllowClient:                    func(common.GeoIPData) bool { return true },
 		AllowDomainFrontedDestinations: func(common.GeoIPData) bool { return true },
+		AllowMatch:                     func(common.GeoIPData, common.GeoIPData) bool { return true },
 	}
 
 	broker, err := NewBroker(brokerConfig)

+ 19 - 9
psiphon/common/inproxy/matcher.go

@@ -132,8 +132,11 @@ type MatcherConfig struct {
 	// Proxy quality state.
 	ProxyQualityState *ProxyQualityState
 
-	// Broker process load limit state callback. See Broker.Config.
+	// Broker process load limit state callback. See BrokerConfig.
 	IsLoadLimiting func() bool
+
+	// Proxy/client allow match callback. See BrokerConfig.
+	AllowMatch func(common.GeoIPData, common.GeoIPData) bool
 }
 
 // MatchProperties specifies the compartment, GeoIP, and network topology
@@ -852,20 +855,27 @@ func (m *Matcher) matchOffer(offerEntry *offerEntry) (*announcementEntry, int) {
 			continue
 		}
 
-		// Disallow matching the same country and ASN, except for personal
+		// Disallow matching the same country and ASN, or GeoIP combinations
+		// prohibited by the AllowMatch callback, except for personal
 		// compartment ID matches.
 		//
 		// For common matching, hopping through the same ISP is assumed to
 		// have no circumvention benefit. For personal matching, the user may
 		// wish to hop their their own or their friend's proxy regardless.
 
-		if isCommonCompartments &&
-			!GetAllowCommonASNMatching() &&
-			(offerProperties.GeoIPData.Country ==
-				announcementProperties.GeoIPData.Country &&
-				offerProperties.GeoIPData.ASN ==
-					announcementProperties.GeoIPData.ASN) {
-			continue
+		if isCommonCompartments {
+			if !GetAllowCommonASNMatching() &&
+				(offerProperties.GeoIPData.Country ==
+					announcementProperties.GeoIPData.Country &&
+					offerProperties.GeoIPData.ASN ==
+						announcementProperties.GeoIPData.ASN) {
+				continue
+			}
+			if !m.config.AllowMatch(
+				announcementProperties.GeoIPData,
+				offerProperties.GeoIPData) {
+				continue
+			}
 		}
 
 		// Check if this is a preferred NAT match. Ultimately, a match may be

+ 46 - 1
psiphon/common/inproxy/matcher_test.go

@@ -62,6 +62,8 @@ func runTestMatcher() error {
 			OfferRateLimitInterval: rateLimitInterval,
 
 			ProxyQualityState: NewProxyQuality(),
+
+			AllowMatch: func(common.GeoIPData, common.GeoIPData) bool { return true },
 		})
 	err := m.Start()
 	if err != nil {
@@ -572,6 +574,48 @@ func runTestMatcher() error {
 		return errors.Tracef("unexpected result: %v", err)
 	}
 
+	// Test: AllowMatch disallow
+
+	m.config.AllowMatch = func(proxy common.GeoIPData, client common.GeoIPData) bool {
+		return proxy != geoIPData1.GeoIPData && client != geoIPData2.GeoIPData
+	}
+
+	go proxyFunc(proxyResultChan, proxyIP, compartment1, 10*time.Millisecond, nil, true)
+	go clientFunc(clientResultChan, clientIP, compartment1And3, 10*time.Millisecond)
+
+	err = <-proxyResultChan
+	if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
+		return errors.Tracef("unexpected result: %v", err)
+	}
+
+	err = <-clientResultChan
+	if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
+		return errors.Tracef("unexpected result: %v", err)
+	}
+
+	// Test: AllowMatch allow
+
+	m.config.AllowMatch = func(proxy common.GeoIPData, client common.GeoIPData) bool {
+		return proxy == geoIPData1.GeoIPData && client == geoIPData2.GeoIPData
+	}
+
+	go proxyFunc(proxyResultChan, proxyIP, compartment1, 10*time.Millisecond, nil, true)
+	go clientFunc(clientResultChan, clientIP, compartment1And3, 10*time.Millisecond)
+
+	err = <-proxyResultChan
+	if err != nil {
+		return errors.Trace(err)
+	}
+
+	err = <-clientResultChan
+	if err != nil {
+		return errors.Trace(err)
+	}
+
+	m.config.AllowMatch = func(proxy common.GeoIPData, client common.GeoIPData) bool {
+		return true
+	}
+
 	// Test: downgrade-compatible protocol version match
 
 	protocolVersion1 := &MatchProperties{
@@ -1120,7 +1164,8 @@ func BenchmarkMatcherQueue(b *testing.B) {
 
 				m = NewMatcher(
 					&MatcherConfig{
-						Logger: newTestLogger(),
+						Logger:     newTestLogger(),
+						AllowMatch: func(common.GeoIPData, common.GeoIPData) bool { return true },
 					})
 
 				for j := 0; j < size; j++ {

+ 8 - 0
psiphon/common/parameters/parameters.go

@@ -403,6 +403,10 @@ const (
 	InproxyAllowProxy                                  = "InproxyAllowProxy"
 	InproxyAllowClient                                 = "InproxyAllowClient"
 	InproxyAllowDomainFrontedDestinations              = "InproxyAllowDomainFrontedDestinations"
+	InproxyAllowMatchByRegion                          = "InproxyAllowMatchByRegion"
+	InproxyAllowMatchByASN                             = "InproxyAllowMatchByASN"
+	InproxyDisallowMatchByRegion                       = "InproxyDisallowMatchByRegion"
+	InproxyDisallowMatchByASN                          = "InproxyDisallowMatchByASN"
 	InproxyAllBrokerSpecs                              = "InproxyAllBrokerSpecs"
 	InproxyBrokerSpecs                                 = "InproxyBrokerSpecs"
 	InproxyPersonalPairingBrokerSpecs                  = "InproxyPersonalPairingBrokerSpecs"
@@ -986,6 +990,10 @@ var defaultParameters = map[string]struct {
 	InproxyAllowProxy:                                  {value: false},
 	InproxyAllowClient:                                 {value: false, flags: serverSideOnly},
 	InproxyAllowDomainFrontedDestinations:              {value: false, flags: serverSideOnly},
+	InproxyAllowMatchByRegion:                          {value: KeyStrings{}, flags: serverSideOnly},
+	InproxyAllowMatchByASN:                             {value: KeyStrings{}, flags: serverSideOnly},
+	InproxyDisallowMatchByRegion:                       {value: KeyStrings{}, flags: serverSideOnly},
+	InproxyDisallowMatchByASN:                          {value: KeyStrings{}, flags: serverSideOnly},
 	InproxyTunnelProtocolSelectionProbability:          {value: 1.0, minimum: 0.0},
 	InproxyAllBrokerPublicKeys:                         {value: []string{}, flags: serverSideOnly},
 	InproxyAllBrokerSpecs:                              {value: InproxyBrokerSpecsValue{}, flags: serverSideOnly},

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
psiphon/server/geoip_test.go


+ 64 - 0
psiphon/server/meek.go

@@ -138,6 +138,7 @@ type MeekServer struct {
 	rateLimitSignalGC               chan struct{}
 	normalizer                      *transforms.HTTPNormalizerListener
 	inproxyBroker                   *inproxy.Broker
+	inproxyCheckAllowMatch          atomic.Value
 }
 
 // NewMeekServer initializes a new meek server.
@@ -1871,6 +1872,62 @@ func (server *MeekServer) inproxyReloadTactics() error {
 		p.Duration(parameters.InproxyProxyQualityPendingFailedMatchDeadline),
 		p.Int(parameters.InproxyProxyQualityFailedMatchThreshold))
 
+	// Configure proxy/client match checklists.
+	//
+	// When an allow list is set, the client GeoIP data must appear in the
+	// proxy's list or the match isn't allowed. When a disallow list is set,
+	// the match isn't allowed if the client GeoIP data appears in the
+	// proxy's list.
+
+	makeCheckListLookup := func(
+		lists map[string][]string,
+		isAllowList bool) func(string, string) bool {
+
+		if len(lists) == 0 {
+			return func(string, string) bool {
+				// Allow when no list
+				return true
+			}
+		}
+		lookup := make(map[string]map[string]struct{})
+		for key, items := range lists {
+			// TODO: use linear search for lists below stringLookupThreshold?
+			itemLookup := make(map[string]struct{})
+			for _, item := range items {
+				itemLookup[item] = struct{}{}
+			}
+			lookup[key] = itemLookup
+		}
+		return func(key, item string) bool {
+			itemLookup := lookup[key]
+			if itemLookup == nil {
+				// Allow when no list
+				return true
+			}
+			_, found := itemLookup[item]
+			// Allow or disallow based on list type
+			return found == isAllowList
+		}
+	}
+
+	inproxyCheckAllowMatchByRegion := makeCheckListLookup(p.KeyStringsValue(
+		parameters.InproxyAllowMatchByRegion), true)
+	inproxyCheckAllowMatchByASN := makeCheckListLookup(p.KeyStringsValue(
+		parameters.InproxyAllowMatchByASN), true)
+	inproxyCheckDisallowMatchByRegion := makeCheckListLookup(p.KeyStringsValue(
+		parameters.InproxyDisallowMatchByRegion), false)
+	inproxyCheckDisallowMatchByRASN := makeCheckListLookup(p.KeyStringsValue(
+		parameters.InproxyDisallowMatchByASN), false)
+
+	checkAllowMatch := func(proxyGeoIPData, clientGeoIPData common.GeoIPData) bool {
+		return inproxyCheckAllowMatchByRegion(proxyGeoIPData.Country, clientGeoIPData.Country) &&
+			inproxyCheckAllowMatchByASN(proxyGeoIPData.ASN, clientGeoIPData.ASN) &&
+			inproxyCheckDisallowMatchByRegion(proxyGeoIPData.Country, clientGeoIPData.Country) &&
+			inproxyCheckDisallowMatchByRASN(proxyGeoIPData.ASN, clientGeoIPData.ASN)
+	}
+
+	server.inproxyCheckAllowMatch.Store(checkAllowMatch)
+
 	return nil
 }
 
@@ -1901,6 +1958,13 @@ func (server *MeekServer) inproxyBrokerAllowDomainFrontedDestinations(clientGeoI
 	return server.lookupAllowTactic(clientGeoIPData, parameters.InproxyAllowDomainFrontedDestinations)
 }
 
+func (server *MeekServer) inproxyBrokerAllowMatch(
+	proxyGeoIPData common.GeoIPData, clientGeoIPData common.GeoIPData) bool {
+
+	return server.inproxyCheckAllowMatch.Load().(func(proxy, client common.GeoIPData) bool)(
+		proxyGeoIPData, clientGeoIPData)
+}
+
 func (server *MeekServer) inproxyBrokerPrioritizeProxy(
 	proxyInproxyProtocolVersion int,
 	proxyGeoIPData common.GeoIPData,

+ 8 - 0
psiphon/server/server_test.go

@@ -4200,6 +4200,10 @@ func generateInproxyTestConfig(
 	tacticsParametersJSONFormat := `
             "InproxyAllowProxy": true,
             "InproxyAllowClient": true,
+            "InproxyAllowMatchByRegion": {[%s]:[%s]},
+            "InproxyAllowMatchByASN": : {[%s]:[%s]},
+            "InproxyDisallowMatchByRegion": {[%s]:[%s]},
+            "InproxyDisallowMatchByASN": {[%s]:[%s]},
             "InproxyTunnelProtocolSelectionProbability": 1.0,
             "InproxyAllBrokerSpecs": %s,
             "InproxyBrokerSpecs": %s,
@@ -4230,6 +4234,10 @@ func generateInproxyTestConfig(
 	tacticsParametersJSON := fmt.Sprintf(
 		tacticsParametersJSONFormat,
 		allBrokerSpecsJSON,
+		testGeoIPCountry, testGeoIPCountry,
+		testGeoIPASN, testGeoIPASN,
+		testGeoIPCountry, "_"+testGeoIPCountry,
+		testGeoIPASN, "_"+testGeoIPASN,
 		brokerSpecsJSON,
 		proxyBrokerSpecsJSON,
 		clientBrokerSpecsJSON,

Некоторые файлы не были показаны из-за большого количества измененных файлов