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

Merge pull request #773 from rod-hynes/dsl-enhancements

DSL relay enhancements
Rod Hynes 1 неделя назад
Родитель
Сommit
1efb453c93

+ 1 - 1
psiphon/common/dsl/api.go

@@ -157,7 +157,7 @@ type GetOSLFileSpecsRequest struct {
 
 // GetOSLFileSpecsResponse includes the list of OSL FileSpecs requested by the
 // client. Each requested OSL ID has a corresponding entry in OSLFileSpecs.
-// When a requsted OSL is no longer active or available for distribution,
+// When a requested OSL is no longer active or available for distribution,
 // there is a nil/empty entry.
 //
 // Here, OSLFileSpec is a []byte, not an osl.FileSpec, as this value doesn't

+ 55 - 3
psiphon/common/dsl/dsl_test.go

@@ -49,6 +49,7 @@ type testConfig struct {
 	isTunneled         bool
 	expectFailure      bool
 	cacheServerEntries bool
+	cacheOSLFileSpecs  bool
 }
 
 func TestDSLs(t *testing.T) {
@@ -103,6 +104,23 @@ func TestDSLs(t *testing.T) {
 			enableRetries:      true,
 			cacheServerEntries: true,
 		},
+		{
+			name: "cache OSL file specs",
+
+			requireOSLKeys:     true,
+			interruptDownloads: true,
+			enableRetries:      true,
+			cacheOSLFileSpecs:  true,
+		},
+		{
+			name: "cache both",
+
+			requireOSLKeys:     true,
+			interruptDownloads: true,
+			enableRetries:      true,
+			cacheServerEntries: true,
+			cacheOSLFileSpecs:  true,
+		},
 	}
 
 	for _, testConfig := range tests {
@@ -175,10 +193,12 @@ func testDSLs(testConfig *testConfig) error {
 
 	expectValidMetric := false
 	metricsValidator := func(metric string, fields common.LogFields) bool { return false }
-	if testConfig.cacheServerEntries {
+	if testConfig.cacheServerEntries || testConfig.cacheOSLFileSpecs {
 		expectValidMetric = true
 		metricsValidator = func(metric string, fields common.LogFields) bool {
-			return metric == "dsl_relay_get_server_entries"
+			// TODO: in "both" test case, check that both events are logged
+			return (testConfig.cacheServerEntries && metric == "dsl_relay_get_server_entries") ||
+				(testConfig.cacheOSLFileSpecs && metric == "dsl_relay_get_osl_file_specs")
 		}
 	}
 
@@ -219,9 +239,24 @@ func testDSLs(testConfig *testConfig) error {
 		return errors.Trace(err)
 	}
 
+	serverEntryCacheTTL := defaultServerEntryCacheTTL
+	serverEntryCacheMaxSize := defaultServerEntryCacheMaxSize
+	oslFileSpecCacheTTL := defaultOSLFileSpecCacheTTL
+	oslFileSpecCacheMaxSize := defaultOSLFileSpecCacheMaxSize
+
 	if !testConfig.cacheServerEntries {
-		relay.SetCacheParameters(0, 0)
+		serverEntryCacheTTL = 0
+		serverEntryCacheMaxSize = 0
 	}
+	if !testConfig.cacheOSLFileSpecs {
+		oslFileSpecCacheTTL = 0
+		oslFileSpecCacheMaxSize = 0
+	}
+	relay.SetCacheParameters(
+		serverEntryCacheTTL,
+		serverEntryCacheMaxSize,
+		oslFileSpecCacheTTL,
+		oslFileSpecCacheMaxSize)
 
 	// Initialize client fetcher
 
@@ -438,6 +473,23 @@ func testDSLs(testConfig *testConfig) error {
 			"unexpected server entry store count: %d", dslClient.serverEntryStoreCount)
 	}
 
+	if testConfig.cacheOSLFileSpecs {
+		if !testConfig.requireOSLKeys {
+			return errors.TraceNew("invalid test config")
+		}
+
+		// Refetch OSL file specs.
+
+		dslClient.lastFetchTime = time.Time{}
+		dslClient.lastActiveOSLsTime = time.Time{}
+		dslClient.oslStates = make(map[string][]byte)
+
+		err = fetcher.Run(ctx)
+		if err != nil {
+			return errors.Trace(err)
+		}
+	}
+
 	if testConfig.requireOSLKeys {
 
 		// Rotate to the next OSL period and clear all server entries. The

+ 226 - 27
psiphon/common/dsl/relay.go

@@ -47,7 +47,10 @@ const (
 	defaultRequestRetryCount   = 1
 
 	defaultServerEntryCacheTTL     = 24 * time.Hour
-	defaultServerEntryCacheMaxSize = 200000
+	defaultServerEntryCacheMaxSize = 250000
+
+	defaultOSLFileSpecCacheTTL     = 24 * time.Hour
+	defaultOSLFileSpecCacheMaxSize = 250000
 )
 
 // RelayConfig specifies the configuration for a Relay.
@@ -94,6 +97,7 @@ type Relay struct {
 	hostKeyFile         common.ReloadableFile
 
 	mutex                   sync.Mutex
+	tlsSessionCache         tls.ClientSessionCache
 	tlsConfig               *tls.Config
 	httpClient              *http.Client
 	requestTimeout          time.Duration
@@ -101,6 +105,12 @@ type Relay struct {
 	serverEntryCache        *lrucache.Cache
 	serverEntryCacheTTL     time.Duration
 	serverEntryCacheMaxSize int
+	oslFileSpecCache        *lrucache.Cache
+	oslFileSpecCacheTTL     time.Duration
+	oslFileSpecCacheMaxSize int
+
+	getServerEntriesBufferPool sync.Pool
+	getOSLFileSpecsBufferPool  sync.Pool
 }
 
 // NewRelay creates a new Relay.
@@ -112,6 +122,8 @@ func NewRelay(config *RelayConfig) (*Relay, error) {
 		caCertificatesFile:  common.NewReloadableFile(config.CACertificatesFilename, false, nil),
 		hostCertificateFile: common.NewReloadableFile(config.HostCertificateFilename, false, nil),
 		hostKeyFile:         common.NewReloadableFile(config.HostKeyFilename, false, nil),
+
+		tlsSessionCache: tls.NewLRUClientSessionCache(0),
 	}
 
 	_, err := relay.Reload()
@@ -128,7 +140,12 @@ func NewRelay(config *RelayConfig) (*Relay, error) {
 
 	relay.SetCacheParameters(
 		defaultServerEntryCacheTTL,
-		defaultServerEntryCacheMaxSize)
+		defaultServerEntryCacheMaxSize,
+		defaultOSLFileSpecCacheTTL,
+		defaultOSLFileSpecCacheMaxSize)
+
+	relay.getServerEntriesBufferPool.New = func() any { return []*SourcedServerEntry{} }
+	relay.getOSLFileSpecsBufferPool.New = func() any { return []OSLFileSpec{} }
 
 	return relay, nil
 }
@@ -187,9 +204,13 @@ func (r *Relay) Reload() (bool, error) {
 	r.mutex.Lock()
 	defer r.mutex.Unlock()
 
+	r.tlsSessionCache = tls.NewLRUClientSessionCache(0)
+
 	r.tlsConfig = &tls.Config{
 		RootCAs:      caCertificates,
 		Certificates: []tls.Certificate{hostCertificate},
+
+		ClientSessionCache: r.tlsSessionCache,
 	}
 
 	if r.httpClient != nil {
@@ -273,22 +294,24 @@ func (r *Relay) SetRequestParameters(
 // entry caching. When the parameters change, any existing cache is flushed
 // and replaced.
 func (r *Relay) SetCacheParameters(
-	TTL time.Duration,
-	maxSize int) {
+	serverEntryCacheTTL time.Duration,
+	serverEntryCacheMaxSize int,
+	oslFileSpecCacheTTL time.Duration,
+	oslFileSpecCacheMaxSize int) {
 
 	r.mutex.Lock()
 	defer r.mutex.Unlock()
 
 	if r.serverEntryCache == nil ||
-		r.serverEntryCacheTTL != TTL ||
-		r.serverEntryCacheMaxSize != maxSize {
+		r.serverEntryCacheTTL != serverEntryCacheTTL ||
+		r.serverEntryCacheMaxSize != serverEntryCacheMaxSize {
 
 		if r.serverEntryCache != nil {
 			r.serverEntryCache.Flush()
 		}
 
-		r.serverEntryCacheTTL = TTL
-		r.serverEntryCacheMaxSize = maxSize
+		r.serverEntryCacheTTL = serverEntryCacheTTL
+		r.serverEntryCacheMaxSize = serverEntryCacheMaxSize
 
 		if r.serverEntryCacheTTL > 0 {
 
@@ -302,6 +325,30 @@ func (r *Relay) SetCacheParameters(
 			r.serverEntryCache = nil
 		}
 	}
+
+	if r.oslFileSpecCache == nil ||
+		r.oslFileSpecCacheTTL != oslFileSpecCacheTTL ||
+		r.oslFileSpecCacheMaxSize != oslFileSpecCacheMaxSize {
+
+		if r.oslFileSpecCache != nil {
+			r.oslFileSpecCache.Flush()
+		}
+
+		r.oslFileSpecCacheTTL = oslFileSpecCacheTTL
+		r.oslFileSpecCacheMaxSize = oslFileSpecCacheMaxSize
+
+		if r.oslFileSpecCacheTTL > 0 {
+
+			r.oslFileSpecCache = lrucache.NewWithLRU(
+				r.oslFileSpecCacheTTL,
+				1*time.Minute,
+				r.oslFileSpecCacheMaxSize)
+
+		} else {
+
+			r.oslFileSpecCache = nil
+		}
+	}
 }
 
 // HandleRequest relays a DSL request.
@@ -393,13 +440,31 @@ func (r *Relay) HandleRequest(
 	//   DiscoverServerEntriesResponses and, for each tag/version pair, if
 	//   the tag is in the cache and the cached entry is an old version,
 	//   delete from the cache. This would require unpacking each server entry.
+	//
+	// Similarly, for requestTypeGetOSLFileSpecs, peek at the
+	// RelayedResponse.Response and extract OSL file specs and add to the
+	// local cache, keyed by OSL ID; and peek at RelayedRequest.Request, and
+	// if all requested OSL file specs are in the cache, serve the request
+	// entirely from the local cache.
 
 	var response []byte
 	cachedResponse := false
 
-	if relayedRequest.RequestType == requestTypeGetServerEntries {
+	var serveCachedResponse func([]byte, common.GeoIPData) ([]byte, error)
+	var updateCache func([]byte, []byte) error
+
+	switch relayedRequest.RequestType {
+	case requestTypeGetServerEntries:
+		serveCachedResponse = r.getCachedGetServerEntriesResponse
+		updateCache = r.cacheGetServerEntriesResponse
+	case requestTypeGetOSLFileSpecs:
+		serveCachedResponse = r.getCachedGetOSLFileSpecsResponse
+		updateCache = r.cacheGetOSLFileSpecsResponse
+	}
+
+	if serveCachedResponse != nil {
 		var err error
-		response, err = r.getCachedGetServerEntriesResponse(
+		response, err = serveCachedResponse(
 			relayedRequest.Request, clientGeoIPData)
 		if err != nil {
 			r.config.Logger.WithTraceFields(common.LogFields{
@@ -471,13 +536,13 @@ func (r *Relay) HandleRequest(
 
 		if err == nil {
 
-			if relayedRequest.RequestType == requestTypeGetServerEntries {
-				err := r.cacheGetServerEntriesResponse(
+			if updateCache != nil {
+				err := updateCache(
 					relayedRequest.Request, response)
 				if err != nil {
 					r.config.Logger.WithTraceFields(common.LogFields{
 						"error": err.Error(),
-					}).Warning("DSL: cache response failed")
+					}).Warning("DSL: update cache failed")
 					// Proceed with relaying response
 				}
 			}
@@ -542,7 +607,11 @@ func (r *Relay) cacheGetServerEntriesResponse(
 	cborRequest []byte,
 	cborResponse []byte) error {
 
-	if r.serverEntryCacheTTL == 0 {
+	r.mutex.Lock()
+	cache := r.serverEntryCache
+	r.mutex.Unlock()
+
+	if cache == nil {
 		// Caching is disabled
 		return nil
 	}
@@ -571,7 +640,7 @@ func (r *Relay) cacheGetServerEntriesResponse(
 			// this tag, in case the server entry version is new. This also
 			// extends the cache TTL, since the server entry is fresh.
 
-			r.serverEntryCache.Set(
+			cache.Set(
 				string(serverEntryTag),
 				response.SourcedServerEntries[i],
 				lrucache.DefaultExpiration)
@@ -584,7 +653,7 @@ func (r *Relay) cacheGetServerEntriesResponse(
 			// is an edge case since DiscoverServerEntries won't return
 			// invalid tags and so the "nil" value/state isn't cached.
 
-			r.serverEntryCache.Delete(string(serverEntryTag))
+			cache.Delete(string(serverEntryTag))
 		}
 	}
 
@@ -595,7 +664,11 @@ func (r *Relay) getCachedGetServerEntriesResponse(
 	cborRequest []byte,
 	clientGeoIPData common.GeoIPData) ([]byte, error) {
 
-	if r.serverEntryCacheTTL == 0 {
+	r.mutex.Lock()
+	cache := r.serverEntryCache
+	r.mutex.Unlock()
+
+	if cache == nil {
 		// Caching is disabled
 		return nil, nil
 	}
@@ -607,19 +680,29 @@ func (r *Relay) getCachedGetServerEntriesResponse(
 	}
 
 	// Since we anticipate that most server entries will be cached, allocate
-	// response slices optimistically.
+	// response slices optimistically. Use buffer pools to mitigate GC churn.
 	//
 	// TODO: check for sufficient cache entries before allocating these
 	// response slices? Would doubling the cache lookups use less resources
 	// than unused allocations?
 
-	serverEntryTags := make([]string, len(request.ServerEntryTags))
+	buffer := r.getServerEntriesBufferPool.Get().([]*SourcedServerEntry)
+	size := len(request.ServerEntryTags)
+	if cap(buffer) < size {
+		buffer = make([]*SourcedServerEntry, size)
+	} else {
+		buffer = buffer[:size]
+	}
+	defer func() {
+		clear(buffer)
+		r.getServerEntriesBufferPool.Put(buffer)
+	}()
 
 	var response GetServerEntriesResponse
-	response.SourcedServerEntries = make([]*SourcedServerEntry, len(request.ServerEntryTags))
+	response.SourcedServerEntries = buffer
 
 	for i, serverEntryTag := range request.ServerEntryTags {
-		cacheEntry, ok := r.serverEntryCache.Get(string(serverEntryTag))
+		cacheEntry, ok := cache.Get(string(serverEntryTag))
 		if !ok {
 
 			// The request can't be served from the cache, as some server
@@ -634,10 +717,6 @@ func (r *Relay) getCachedGetServerEntriesResponse(
 
 		// The cached entry's TTL is not extended on a hit.
 
-		// serverEntryTags are used for logging the request event when served
-		// from the cache.
-		serverEntryTags[i] = serverEntryTag.String()
-
 		response.SourcedServerEntries[i] = cacheEntry.(*SourcedServerEntry)
 	}
 
@@ -646,7 +725,7 @@ func (r *Relay) getCachedGetServerEntriesResponse(
 		return nil, errors.Trace(err)
 	}
 
-	// Log the request event. Since this request is server from the relay
+	// Log the request event. Since this request is served from the relay
 	// cache, the DSL backend will not see the request and log the event
 	// itself. This log should match the DSL log format and can be shipped to
 	// the same log aggregator.
@@ -662,12 +741,132 @@ func (r *Relay) getCachedGetServerEntriesResponse(
 	}
 
 	logFields := r.config.APIParameterLogFieldFormatter("", clientGeoIPData, baseParams)
-	logFields["server_entry_tags"] = serverEntryTags
+	logFields["server_entry_tag_count"] = len(response.SourcedServerEntries)
 	r.config.Logger.LogMetric("dsl_relay_get_server_entries", logFields)
 
 	return cborResponse, nil
 }
 
+func (r *Relay) cacheGetOSLFileSpecsResponse(
+	cborRequest []byte,
+	cborResponse []byte) error {
+
+	r.mutex.Lock()
+	cache := r.oslFileSpecCache
+	r.mutex.Unlock()
+
+	if cache == nil {
+		// Caching is disabled
+		return nil
+	}
+
+	var request GetOSLFileSpecsRequest
+	err := cbor.Unmarshal(cborRequest, &request)
+	if err != nil {
+		return errors.Trace(err)
+	}
+
+	var response GetOSLFileSpecsResponse
+	err = cbor.Unmarshal(cborResponse, &response)
+	if err != nil {
+		return errors.Trace(err)
+	}
+
+	if len(request.OSLIDs) != len(response.OSLFileSpecs) {
+		return errors.TraceNew("unexpected spec count mismatch")
+	}
+
+	for i, oslID := range request.OSLIDs {
+
+		if response.OSLFileSpecs[i] != nil {
+
+			// This will extend the cache TTL for existing entries.
+
+			cache.Set(
+				string(oslID),
+				response.OSLFileSpecs[i],
+				lrucache.DefaultExpiration)
+
+		} else {
+
+			// In this case, the DSL backend is indicating that the OSL file
+			// spec is not longer active or available for distribution.
+
+			cache.Delete(string(oslID))
+		}
+	}
+
+	return nil
+}
+
+func (r *Relay) getCachedGetOSLFileSpecsResponse(
+	cborRequest []byte,
+	clientGeoIPData common.GeoIPData) ([]byte, error) {
+
+	r.mutex.Lock()
+	cache := r.oslFileSpecCache
+	r.mutex.Unlock()
+
+	if cache == nil {
+		// Caching is disabled
+		return nil, nil
+	}
+
+	var request GetOSLFileSpecsRequest
+	err := cbor.Unmarshal(cborRequest, &request)
+	if err != nil {
+		return nil, errors.Trace(err)
+	}
+
+	// This logic mirrors getCachedGetServerEntriesResponse. See the comments
+	// in that function.
+
+	buffer := r.getOSLFileSpecsBufferPool.Get().([]OSLFileSpec)
+	size := len(request.OSLIDs)
+	if cap(buffer) < size {
+		buffer = make([]OSLFileSpec, size)
+	} else {
+		buffer = buffer[:size]
+	}
+	defer func() {
+		clear(buffer)
+		r.getOSLFileSpecsBufferPool.Put(buffer)
+	}()
+
+	var response GetOSLFileSpecsResponse
+	response.OSLFileSpecs = buffer
+
+	for i, oslID := range request.OSLIDs {
+		cacheEntry, ok := cache.Get(string(oslID))
+		if !ok {
+			return nil, nil
+		}
+
+		response.OSLFileSpecs[i] = cacheEntry.(OSLFileSpec)
+	}
+
+	cborResponse, err := protocol.CBOREncoding.Marshal(&response)
+	if err != nil {
+		return nil, errors.Trace(err)
+	}
+
+	baseParams, err := protocol.DecodePackedAPIParameters(request.BaseAPIParameters)
+	if err != nil {
+		return nil, errors.Trace(err)
+	}
+
+	err = r.config.APIParameterValidator(baseParams)
+	if err != nil {
+		return nil, errors.Trace(err)
+	}
+
+	logFields := r.config.APIParameterLogFieldFormatter("", clientGeoIPData, baseParams)
+	logFields["osl_id_count"] = len(response.OSLFileSpecs)
+	r.config.Logger.LogMetric("dsl_relay_get_osl_file_specs", logFields)
+
+	return cborResponse, nil
+}
+
 var relayGenericErrorResponse []byte
 
 func init() {

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

@@ -533,8 +533,10 @@ const (
 	DSLRelayHttpIdleConnTimeout                        = "DSLRelayHttpIdleConnTimeout"
 	DSLRelayRequestTimeout                             = "DSLRelayRequestTimeout"
 	DSLRelayRetryCount                                 = "DSLRelayRetryCount"
-	DSLRelayCacheTTL                                   = "DSLRelayCacheTTL"
-	DSLRelayCacheMaxSize                               = "DSLRelayCacheMaxSize"
+	DSLRelayServerEntryCacheTTL                        = "DSLRelayServerEntryCacheTTL"
+	DSLRelayServerEntryCacheMaxSize                    = "DSLRelayServerEntryCacheMaxSize"
+	DSLRelayOSLFileSpecCacheTTL                        = "DSLRelayOSLFileSpecCacheTTL"
+	DSLRelayOSLFileSpecCacheMaxSize                    = "DSLRelayOSLFileSpecCacheMaxSize"
 	EnableDSLFetcher                                   = "EnableDSLFetcher"
 	DSLFetcherTunneledRequestTimeout                   = "DSLFetcherTunneledRequestTimeout"
 	DSLFetcherTunneledRequestRetryCount                = "DSLFetcherTunneledRequestRetryCount"
@@ -1182,8 +1184,10 @@ var defaultParameters = map[string]struct {
 	DSLRelayHttpIdleConnTimeout:                       {value: 120 * time.Second, minimum: time.Duration(0), flags: serverSideOnly},
 	DSLRelayRequestTimeout:                            {value: 30 * time.Second, minimum: time.Duration(0), flags: serverSideOnly},
 	DSLRelayRetryCount:                                {value: 1, minimum: 0, flags: serverSideOnly},
-	DSLRelayCacheTTL:                                  {value: 24 * time.Hour, minimum: time.Duration(0), flags: serverSideOnly},
-	DSLRelayCacheMaxSize:                              {value: 200000, minimum: 0, flags: serverSideOnly},
+	DSLRelayServerEntryCacheTTL:                       {value: 24 * time.Hour, minimum: time.Duration(0), flags: serverSideOnly},
+	DSLRelayServerEntryCacheMaxSize:                   {value: 250000, minimum: 0, flags: serverSideOnly},
+	DSLRelayOSLFileSpecCacheTTL:                       {value: 24 * time.Hour, minimum: time.Duration(0), flags: serverSideOnly},
+	DSLRelayOSLFileSpecCacheMaxSize:                   {value: 250000, minimum: 0, flags: serverSideOnly},
 	EnableDSLFetcher:                                  {value: false},
 	DSLFetcherTunneledRequestTimeout:                  {value: 5 * time.Second, minimum: time.Duration(0), flags: useNetworkLatencyMultiplier},
 	DSLFetcherTunneledRequestRetryCount:               {value: 0, minimum: 0},

+ 4 - 2
psiphon/server/dsl.go

@@ -83,8 +83,10 @@ func dslReloadRelayTactics(support *SupportServices) error {
 		p.Int(parameters.DSLRelayRetryCount))
 
 	dslRelay.SetCacheParameters(
-		p.Duration(parameters.DSLRelayCacheTTL),
-		p.Int(parameters.DSLRelayCacheMaxSize))
+		p.Duration(parameters.DSLRelayServerEntryCacheTTL),
+		p.Int(parameters.DSLRelayServerEntryCacheMaxSize),
+		p.Duration(parameters.DSLRelayOSLFileSpecCacheTTL),
+		p.Int(parameters.DSLRelayOSLFileSpecCacheMaxSize))
 
 	return nil
 }

+ 83 - 20
psiphon/server/pb/psiphond/dsl_relay.pb.go

@@ -22,11 +22,11 @@ const (
 )
 
 type DslRelayGetServerEntries struct {
-	state           protoimpl.MessageState `protogen:"open.v1"`
-	BaseParams      *BaseParams            `protobuf:"bytes,1,opt,name=base_params,json=baseParams,proto3,oneof" json:"base_params,omitempty"`
-	ServerEntryTags []string               `protobuf:"bytes,100,rep,name=server_entry_tags,json=serverEntryTags,proto3" json:"server_entry_tags,omitempty"`
-	unknownFields   protoimpl.UnknownFields
-	sizeCache       protoimpl.SizeCache
+	state               protoimpl.MessageState `protogen:"open.v1"`
+	BaseParams          *BaseParams            `protobuf:"bytes,1,opt,name=base_params,json=baseParams,proto3,oneof" json:"base_params,omitempty"`
+	ServerEntryTagCount *int64                 `protobuf:"varint,101,opt,name=server_entry_tag_count,json=serverEntryTagCount,proto3,oneof" json:"server_entry_tag_count,omitempty"`
+	unknownFields       protoimpl.UnknownFields
+	sizeCache           protoimpl.SizeCache
 }
 
 func (x *DslRelayGetServerEntries) Reset() {
@@ -66,23 +66,83 @@ func (x *DslRelayGetServerEntries) GetBaseParams() *BaseParams {
 	return nil
 }
 
-func (x *DslRelayGetServerEntries) GetServerEntryTags() []string {
+func (x *DslRelayGetServerEntries) GetServerEntryTagCount() int64 {
+	if x != nil && x.ServerEntryTagCount != nil {
+		return *x.ServerEntryTagCount
+	}
+	return 0
+}
+
+type DslRelayGetOslFileSpecs struct {
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	BaseParams    *BaseParams            `protobuf:"bytes,1,opt,name=base_params,json=baseParams,proto3,oneof" json:"base_params,omitempty"`
+	OslIdCount    *int64                 `protobuf:"varint,100,opt,name=osl_id_count,json=oslIdCount,proto3,oneof" json:"osl_id_count,omitempty"`
+	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
+}
+
+func (x *DslRelayGetOslFileSpecs) Reset() {
+	*x = DslRelayGetOslFileSpecs{}
+	mi := &file_ca_psiphon_psiphond_dsl_relay_proto_msgTypes[1]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *DslRelayGetOslFileSpecs) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DslRelayGetOslFileSpecs) ProtoMessage() {}
+
+func (x *DslRelayGetOslFileSpecs) ProtoReflect() protoreflect.Message {
+	mi := &file_ca_psiphon_psiphond_dsl_relay_proto_msgTypes[1]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use DslRelayGetOslFileSpecs.ProtoReflect.Descriptor instead.
+func (*DslRelayGetOslFileSpecs) Descriptor() ([]byte, []int) {
+	return file_ca_psiphon_psiphond_dsl_relay_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *DslRelayGetOslFileSpecs) GetBaseParams() *BaseParams {
 	if x != nil {
-		return x.ServerEntryTags
+		return x.BaseParams
 	}
 	return nil
 }
 
+func (x *DslRelayGetOslFileSpecs) GetOslIdCount() int64 {
+	if x != nil && x.OslIdCount != nil {
+		return *x.OslIdCount
+	}
+	return 0
+}
+
 var File_ca_psiphon_psiphond_dsl_relay_proto protoreflect.FileDescriptor
 
 const file_ca_psiphon_psiphond_dsl_relay_proto_rawDesc = "" +
 	"\n" +
-	"#ca.psiphon.psiphond/dsl_relay.proto\x12\x13ca.psiphon.psiphond\x1a%ca.psiphon.psiphond/base_params.proto\"\x9d\x01\n" +
+	"#ca.psiphon.psiphond/dsl_relay.proto\x12\x13ca.psiphon.psiphond\x1a%ca.psiphon.psiphond/base_params.proto\"\xcc\x01\n" +
 	"\x18DslRelayGetServerEntries\x12E\n" +
 	"\vbase_params\x18\x01 \x01(\v2\x1f.ca.psiphon.psiphond.BaseParamsH\x00R\n" +
-	"baseParams\x88\x01\x01\x12*\n" +
-	"\x11server_entry_tags\x18d \x03(\tR\x0fserverEntryTagsB\x0e\n" +
-	"\f_base_paramsBHZFgithub.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/pb/psiphondb\x06proto3"
+	"baseParams\x88\x01\x01\x128\n" +
+	"\x16server_entry_tag_count\x18e \x01(\x03H\x01R\x13serverEntryTagCount\x88\x01\x01B\x0e\n" +
+	"\f_base_paramsB\x19\n" +
+	"\x17_server_entry_tag_countJ\x04\bd\x10e\"\xa8\x01\n" +
+	"\x17DslRelayGetOslFileSpecs\x12E\n" +
+	"\vbase_params\x18\x01 \x01(\v2\x1f.ca.psiphon.psiphond.BaseParamsH\x00R\n" +
+	"baseParams\x88\x01\x01\x12%\n" +
+	"\fosl_id_count\x18d \x01(\x03H\x01R\n" +
+	"oslIdCount\x88\x01\x01B\x0e\n" +
+	"\f_base_paramsB\x0f\n" +
+	"\r_osl_id_countBHZFgithub.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/pb/psiphondb\x06proto3"
 
 var (
 	file_ca_psiphon_psiphond_dsl_relay_proto_rawDescOnce sync.Once
@@ -96,18 +156,20 @@ func file_ca_psiphon_psiphond_dsl_relay_proto_rawDescGZIP() []byte {
 	return file_ca_psiphon_psiphond_dsl_relay_proto_rawDescData
 }
 
-var file_ca_psiphon_psiphond_dsl_relay_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_ca_psiphon_psiphond_dsl_relay_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
 var file_ca_psiphon_psiphond_dsl_relay_proto_goTypes = []any{
 	(*DslRelayGetServerEntries)(nil), // 0: ca.psiphon.psiphond.DslRelayGetServerEntries
-	(*BaseParams)(nil),               // 1: ca.psiphon.psiphond.BaseParams
+	(*DslRelayGetOslFileSpecs)(nil),  // 1: ca.psiphon.psiphond.DslRelayGetOslFileSpecs
+	(*BaseParams)(nil),               // 2: ca.psiphon.psiphond.BaseParams
 }
 var file_ca_psiphon_psiphond_dsl_relay_proto_depIdxs = []int32{
-	1, // 0: ca.psiphon.psiphond.DslRelayGetServerEntries.base_params:type_name -> ca.psiphon.psiphond.BaseParams
-	1, // [1:1] is the sub-list for method output_type
-	1, // [1:1] is the sub-list for method input_type
-	1, // [1:1] is the sub-list for extension type_name
-	1, // [1:1] is the sub-list for extension extendee
-	0, // [0:1] is the sub-list for field type_name
+	2, // 0: ca.psiphon.psiphond.DslRelayGetServerEntries.base_params:type_name -> ca.psiphon.psiphond.BaseParams
+	2, // 1: ca.psiphon.psiphond.DslRelayGetOslFileSpecs.base_params:type_name -> ca.psiphon.psiphond.BaseParams
+	2, // [2:2] is the sub-list for method output_type
+	2, // [2:2] is the sub-list for method input_type
+	2, // [2:2] is the sub-list for extension type_name
+	2, // [2:2] is the sub-list for extension extendee
+	0, // [0:2] is the sub-list for field type_name
 }
 
 func init() { file_ca_psiphon_psiphond_dsl_relay_proto_init() }
@@ -117,13 +179,14 @@ func file_ca_psiphon_psiphond_dsl_relay_proto_init() {
 	}
 	file_ca_psiphon_psiphond_base_params_proto_init()
 	file_ca_psiphon_psiphond_dsl_relay_proto_msgTypes[0].OneofWrappers = []any{}
+	file_ca_psiphon_psiphond_dsl_relay_proto_msgTypes[1].OneofWrappers = []any{}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: unsafe.Slice(unsafe.StringData(file_ca_psiphon_psiphond_dsl_relay_proto_rawDesc), len(file_ca_psiphon_psiphond_dsl_relay_proto_rawDesc)),
 			NumEnums:      0,
-			NumMessages:   1,
+			NumMessages:   2,
 			NumExtensions: 0,
 			NumServices:   0,
 		},

BIN
psiphon/server/pb/psiphond/psiphond.desc


+ 27 - 7
psiphon/server/pb/psiphond/psiphond.pb.go

@@ -49,6 +49,7 @@ type Psiphond struct {
 	//	*Psiphond_AsnDestBytes
 	//	*Psiphond_DomainDestBytes
 	//	*Psiphond_DslRelayGetServerEntries
+	//	*Psiphond_DslRelayGetOslFileSpecs
 	Metric        isPsiphond_Metric `protobuf_oneof:"metric"`
 	unknownFields protoimpl.UnknownFields
 	sizeCache     protoimpl.SizeCache
@@ -288,6 +289,15 @@ func (x *Psiphond) GetDslRelayGetServerEntries() *DslRelayGetServerEntries {
 	return nil
 }
 
+func (x *Psiphond) GetDslRelayGetOslFileSpecs() *DslRelayGetOslFileSpecs {
+	if x != nil {
+		if x, ok := x.Metric.(*Psiphond_DslRelayGetOslFileSpecs); ok {
+			return x.DslRelayGetOslFileSpecs
+		}
+	}
+	return nil
+}
+
 type isPsiphond_Metric interface {
 	isPsiphond_Metric()
 }
@@ -364,6 +374,10 @@ type Psiphond_DslRelayGetServerEntries struct {
 	DslRelayGetServerEntries *DslRelayGetServerEntries `protobuf:"bytes,120,opt,name=dsl_relay_get_server_entries,json=dslRelayGetServerEntries,proto3,oneof"`
 }
 
+type Psiphond_DslRelayGetOslFileSpecs struct {
+	DslRelayGetOslFileSpecs *DslRelayGetOslFileSpecs `protobuf:"bytes,121,opt,name=dsl_relay_get_osl_file_specs,json=dslRelayGetOslFileSpecs,proto3,oneof"`
+}
+
 func (*Psiphond_FailedTunnel) isPsiphond_Metric() {}
 
 func (*Psiphond_InproxyBroker) isPsiphond_Metric() {}
@@ -400,11 +414,13 @@ func (*Psiphond_DomainDestBytes) isPsiphond_Metric() {}
 
 func (*Psiphond_DslRelayGetServerEntries) isPsiphond_Metric() {}
 
+func (*Psiphond_DslRelayGetOslFileSpecs) isPsiphond_Metric() {}
+
 var File_ca_psiphon_psiphond_psiphond_proto protoreflect.FileDescriptor
 
 const file_ca_psiphon_psiphond_psiphond_proto_rawDesc = "" +
 	"\n" +
-	"\"ca.psiphon.psiphond/psiphond.proto\x12\x13ca.psiphon.psiphond\x1a\x1fgoogle/protobuf/timestamp.proto\x1a(ca.psiphon.psiphond/asn_dest_bytes.proto\x1a+ca.psiphon.psiphond/domain_dest_bytes.proto\x1a'ca.psiphon.psiphond/failed_tunnel.proto\x1a(ca.psiphon.psiphond/inproxy_broker.proto\x1a*ca.psiphon.psiphond/irregular_tunnel.proto\x1a'ca.psiphon.psiphond/orphan_packet.proto\x1a,ca.psiphon.psiphond/remote_server_list.proto\x1a*ca.psiphon.psiphond/server_blocklist.proto\x1a%ca.psiphon.psiphond/server_load.proto\x1a&ca.psiphon.psiphond/server_panic.proto\x1a'ca.psiphon.psiphond/server_packet.proto\x1a'ca.psiphon.psiphond/server_tunnel.proto\x1a!ca.psiphon.psiphond/tactics.proto\x1a%ca.psiphon.psiphond/unique_user.proto\x1a#ca.psiphon.psiphond/dsl_relay.proto\"\xe1\f\n" +
+	"\"ca.psiphon.psiphond/psiphond.proto\x12\x13ca.psiphon.psiphond\x1a\x1fgoogle/protobuf/timestamp.proto\x1a(ca.psiphon.psiphond/asn_dest_bytes.proto\x1a+ca.psiphon.psiphond/domain_dest_bytes.proto\x1a'ca.psiphon.psiphond/failed_tunnel.proto\x1a(ca.psiphon.psiphond/inproxy_broker.proto\x1a*ca.psiphon.psiphond/irregular_tunnel.proto\x1a'ca.psiphon.psiphond/orphan_packet.proto\x1a,ca.psiphon.psiphond/remote_server_list.proto\x1a*ca.psiphon.psiphond/server_blocklist.proto\x1a%ca.psiphon.psiphond/server_load.proto\x1a&ca.psiphon.psiphond/server_panic.proto\x1a'ca.psiphon.psiphond/server_packet.proto\x1a'ca.psiphon.psiphond/server_tunnel.proto\x1a!ca.psiphon.psiphond/tactics.proto\x1a%ca.psiphon.psiphond/unique_user.proto\x1a#ca.psiphon.psiphond/dsl_relay.proto\"\xd0\r\n" +
 	"\bPsiphond\x128\n" +
 	"\ttimestamp\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\x12\x17\n" +
 	"\ahost_id\x18\x02 \x01(\tR\x06hostId\x12\x1b\n" +
@@ -430,7 +446,8 @@ const file_ca_psiphon_psiphond_psiphond_proto_rawDesc = "" +
 	"uniqueUser\x12I\n" +
 	"\x0easn_dest_bytes\x18v \x01(\v2!.ca.psiphon.psiphond.AsnDestBytesH\x00R\fasnDestBytes\x12R\n" +
 	"\x11domain_dest_bytes\x18w \x01(\v2$.ca.psiphon.psiphond.DomainDestBytesH\x00R\x0fdomainDestBytes\x12o\n" +
-	"\x1cdsl_relay_get_server_entries\x18x \x01(\v2-.ca.psiphon.psiphond.DslRelayGetServerEntriesH\x00R\x18dslRelayGetServerEntriesB\b\n" +
+	"\x1cdsl_relay_get_server_entries\x18x \x01(\v2-.ca.psiphon.psiphond.DslRelayGetServerEntriesH\x00R\x18dslRelayGetServerEntries\x12m\n" +
+	"\x1cdsl_relay_get_osl_file_specs\x18y \x01(\v2,.ca.psiphon.psiphond.DslRelayGetOslFileSpecsH\x00R\x17dslRelayGetOslFileSpecsB\b\n" +
 	"\x06metricJ\x04\be\x10fJ\x04\br\x10sBHZFgithub.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/pb/psiphondb\x06proto3"
 
 var (
@@ -467,6 +484,7 @@ var file_ca_psiphon_psiphond_psiphond_proto_goTypes = []any{
 	(*AsnDestBytes)(nil),             // 17: ca.psiphon.psiphond.AsnDestBytes
 	(*DomainDestBytes)(nil),          // 18: ca.psiphon.psiphond.DomainDestBytes
 	(*DslRelayGetServerEntries)(nil), // 19: ca.psiphon.psiphond.DslRelayGetServerEntries
+	(*DslRelayGetOslFileSpecs)(nil),  // 20: ca.psiphon.psiphond.DslRelayGetOslFileSpecs
 }
 var file_ca_psiphon_psiphond_psiphond_proto_depIdxs = []int32{
 	1,  // 0: ca.psiphon.psiphond.Psiphond.timestamp:type_name -> google.protobuf.Timestamp
@@ -488,11 +506,12 @@ var file_ca_psiphon_psiphond_psiphond_proto_depIdxs = []int32{
 	17, // 16: ca.psiphon.psiphond.Psiphond.asn_dest_bytes:type_name -> ca.psiphon.psiphond.AsnDestBytes
 	18, // 17: ca.psiphon.psiphond.Psiphond.domain_dest_bytes:type_name -> ca.psiphon.psiphond.DomainDestBytes
 	19, // 18: ca.psiphon.psiphond.Psiphond.dsl_relay_get_server_entries:type_name -> ca.psiphon.psiphond.DslRelayGetServerEntries
-	19, // [19:19] is the sub-list for method output_type
-	19, // [19:19] is the sub-list for method input_type
-	19, // [19:19] is the sub-list for extension type_name
-	19, // [19:19] is the sub-list for extension extendee
-	0,  // [0:19] is the sub-list for field type_name
+	20, // 19: ca.psiphon.psiphond.Psiphond.dsl_relay_get_osl_file_specs:type_name -> ca.psiphon.psiphond.DslRelayGetOslFileSpecs
+	20, // [20:20] is the sub-list for method output_type
+	20, // [20:20] is the sub-list for method input_type
+	20, // [20:20] is the sub-list for extension type_name
+	20, // [20:20] is the sub-list for extension extendee
+	0,  // [0:20] is the sub-list for field type_name
 }
 
 func init() { file_ca_psiphon_psiphond_psiphond_proto_init() }
@@ -534,6 +553,7 @@ func file_ca_psiphon_psiphond_psiphond_proto_init() {
 		(*Psiphond_AsnDestBytes)(nil),
 		(*Psiphond_DomainDestBytes)(nil),
 		(*Psiphond_DslRelayGetServerEntries)(nil),
+		(*Psiphond_DslRelayGetOslFileSpecs)(nil),
 	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{

+ 11 - 1
psiphon/server/proto/ca.psiphon.psiphond/dsl_relay.proto

@@ -11,5 +11,15 @@ message DslRelayGetServerEntries {
 
     // Fields 1-99 are reserved for field groupings.
 
-    repeated string server_entry_tags = 100;
+    reserved 100; // retired fields
+
+    optional int64 server_entry_tag_count = 101;
+}
+
+message DslRelayGetOslFileSpecs {
+    optional ca.psiphon.psiphond.BaseParams base_params = 1;
+
+    // Fields 1-99 are reserved for field groupings.
+
+    optional int64 osl_id_count = 100;
 }

+ 1 - 0
psiphon/server/proto/ca.psiphon.psiphond/psiphond.proto

@@ -50,5 +50,6 @@ message Psiphond {
     ca.psiphon.psiphond.AsnDestBytes asn_dest_bytes = 118;
     ca.psiphon.psiphond.DomainDestBytes domain_dest_bytes = 119;
     ca.psiphon.psiphond.DslRelayGetServerEntries dsl_relay_get_server_entries = 120;
+    ca.psiphon.psiphond.DslRelayGetOslFileSpecs dsl_relay_get_osl_file_specs = 121;
   }
 }

+ 7 - 0
psiphon/server/protobufConverter.go

@@ -68,6 +68,9 @@ var protobufMessageFieldGroups = map[string]protobufFieldGroupConfig{
 	"dsl_relay_get_server_entries": {
 		baseParams: true,
 	},
+	"dsl_relay_get_osl_file_specs": {
+		baseParams: true,
+	},
 }
 
 // NewProtobufRoutedMessage returns a populated Router protobuf message.
@@ -293,6 +296,10 @@ func logFieldsToProtobuf(logFields LogFields) []*pbr.Router {
 		msg := &pb.DslRelayGetServerEntries{}
 		protobufPopulateMessage(logFields, msg, eventName)
 		psiphondWrapped.Metric = &pb.Psiphond_DslRelayGetServerEntries{DslRelayGetServerEntries: msg}
+	case "dsl_relay_get_osl_file_specs":
+		msg := &pb.DslRelayGetOslFileSpecs{}
+		protobufPopulateMessage(logFields, msg, eventName)
+		psiphondWrapped.Metric = &pb.Psiphond_DslRelayGetOslFileSpecs{DslRelayGetOslFileSpecs: msg}
 	}
 
 	// Single append for all non-special cases.