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

Add metrics

- server_tunnel/server_load/irregular_tunnel server_entry_tag
- server_tunnel/failed_tunnel server_entry_count
Rod Hynes 3 месяцев назад
Родитель
Сommit
daf2a5d65a

+ 5 - 1
psiphon/common/protocol/packed.go

@@ -838,7 +838,11 @@ func init() {
 
 
 		{168, "psiphon_api_response_version", intConverter},
 		{168, "psiphon_api_response_version", intConverter},
 
 
-		// Next key value = 169
+		// Specs: server.baseDialParams
+
+		{169, "server_entry_count", intConverter},
+
+		// Next key value = 170
 	}
 	}
 
 
 	for _, spec := range packedAPIParameterSpecs {
 	for _, spec := range packedAPIParameterSpecs {

+ 6 - 0
psiphon/common/protocol/protocol.go

@@ -918,3 +918,9 @@ func GetCompressTactics(params common.APIParameters) bool {
 func SetCompressTactics(params common.APIParameters) {
 func SetCompressTactics(params common.APIParameters) {
 	params[PSIPHON_API_RESPONSE_VERSION_FIELD_NAME] = PSIPHON_API_RESPONSE_V1
 	params[PSIPHON_API_RESPONSE_VERSION_FIELD_NAME] = PSIPHON_API_RESPONSE_V1
 }
 }
+
+// ServerEntryCountRoundingIncrement specifies the rounding increment for
+// client-reported server_entry_count metrics. Except for the value 0, the
+// metric is rounded up to the nearest increment to avoid a potentially
+// unique client fingerprint.
+var ServerEntryCountRoundingIncrement = 50

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

@@ -971,8 +971,16 @@ func (serverEntry *ServerEntry) HasSignature() bool {
 	return serverEntry.Signature != ""
 	return serverEntry.Signature != ""
 }
 }
 
 
+func (serverEntry *ServerEntry) GetTag() string {
+	if serverEntry.Tag != "" {
+		return serverEntry.Tag
+	}
+	return GenerateServerEntryTag(
+		serverEntry.IpAddress, serverEntry.WebServerSecret)
+}
+
 func (serverEntry *ServerEntry) GetDiagnosticID() string {
 func (serverEntry *ServerEntry) GetDiagnosticID() string {
-	return TagToDiagnosticID(serverEntry.Tag)
+	return TagToDiagnosticID(serverEntry.GetTag())
 }
 }
 
 
 // GenerateServerEntryTag creates a server entry tag value that is
 // GenerateServerEntryTag creates a server entry tag value that is

+ 50 - 5
psiphon/dataStore.go

@@ -68,13 +68,18 @@ var (
 
 
 	datastoreServerEntryFetchGCThreshold = 10
 	datastoreServerEntryFetchGCThreshold = 10
 
 
-	datastoreReferenceCountMutex sync.RWMutex
-	datastoreReferenceCount      int64
-	datastoreMutex               sync.RWMutex
-	activeDatastoreDB            *datastoreDB
-	disableCheckServerEntryTags  atomic.Bool
+	datastoreReferenceCountMutex  sync.RWMutex
+	datastoreReferenceCount       int64
+	datastoreMutex                sync.RWMutex
+	activeDatastoreDB             *datastoreDB
+	disableCheckServerEntryTags   atomic.Bool
+	datastoreLastServerEntryCount atomic.Int64
 )
 )
 
 
+func init() {
+	datastoreLastServerEntryCount.Store(-1)
+}
+
 // OpenDataStore opens and initializes the singleton datastore instance.
 // OpenDataStore opens and initializes the singleton datastore instance.
 //
 //
 // Nested Open/CloseDataStore calls are supported: OpenDataStore will succeed
 // Nested Open/CloseDataStore calls are supported: OpenDataStore will succeed
@@ -567,6 +572,25 @@ func DeleteServerEntryAffinity(ipAddress string) error {
 	return nil
 	return nil
 }
 }
 
 
+// GetLastServerEntryCount returns a generalized number of server entries in
+// the datastore recorded by the last ServerEntryIterator New/Reset call.
+// Similar to last_connected and persistent stats timestamps, the count is
+// rounded to avoid a potentially unique client fingerprint. The return value
+// is -1 if no count has been recorded.
+func GetLastServerEntryCount() int {
+	count := int(datastoreLastServerEntryCount.Load())
+
+	if count <= 0 {
+		// Return -1 (no count) and 0 (no server entries) as-is.
+		return count
+	}
+
+	n := protocol.ServerEntryCountRoundingIncrement
+
+	// Round up to the nearest ServerEntryCountRoundingIncrement.
+	return ((count + (n - 1)) / n) * n
+}
+
 func makeServerEntryFilterValue(config *Config) ([]byte, error) {
 func makeServerEntryFilterValue(config *Config) ([]byte, error) {
 
 
 	// Currently, only a change of EgressRegion will "break" server affinity.
 	// Currently, only a change of EgressRegion will "break" server affinity.
@@ -749,6 +773,11 @@ func newTargetServerEntryIterator(config *Config, isTactics bool) (bool, *Server
 		targetServerEntry:            serverEntry,
 		targetServerEntry:            serverEntry,
 	}
 	}
 
 
+	err = iterator.reset(true)
+	if err != nil {
+		return false, nil, errors.Trace(err)
+	}
+
 	NoticeInfo("using TargetServerEntry: %s", serverEntry.GetDiagnosticID())
 	NoticeInfo("using TargetServerEntry: %s", serverEntry.GetDiagnosticID())
 
 
 	return false, iterator, nil
 	return false, iterator, nil
@@ -765,6 +794,15 @@ func (iterator *ServerEntryIterator) reset(isInitialRound bool) error {
 
 
 	if iterator.isTargetServerEntryIterator {
 	if iterator.isTargetServerEntryIterator {
 		iterator.hasNextTargetServerEntry = true
 		iterator.hasNextTargetServerEntry = true
+
+		// Provide the GetLastServerEntryCount implementation. See comment below.
+		count := 0
+		err := getBucketKeys(datastoreServerEntriesBucket, func(_ []byte) { count += 1 })
+		if err != nil {
+			return errors.Trace(err)
+		}
+		datastoreLastServerEntryCount.Store(int64(count))
+
 		return nil
 		return nil
 	}
 	}
 
 
@@ -837,6 +875,13 @@ func (iterator *ServerEntryIterator) reset(isInitialRound bool) error {
 		}
 		}
 		cursor.close()
 		cursor.close()
 
 
+		// Provide the GetLastServerEntryCount implementation. This snapshot
+		// of the number of server entries in the datastore is used for
+		// metrics; a snapshot is recorded here to avoid the overhead of
+		// datastore scans or operations when the metric is logged.
+
+		datastoreLastServerEntryCount.Store(int64(len(serverEntryIDs)))
+
 		// Randomly shuffle the entire list of server IDs, excluding the
 		// Randomly shuffle the entire list of server IDs, excluding the
 		// server affinity candidate.
 		// server affinity candidate.
 
 

+ 1 - 0
psiphon/server/api.go

@@ -1276,6 +1276,7 @@ var baseDialParams = []requestParamSpec{
 	{"quic_did_resume", isBooleanFlag, requestParamOptional | requestParamLogFlagAsBool},
 	{"quic_did_resume", isBooleanFlag, requestParamOptional | requestParamLogFlagAsBool},
 	{"quic_dial_early", isBooleanFlag, requestParamOptional | requestParamLogFlagAsBool},
 	{"quic_dial_early", isBooleanFlag, requestParamOptional | requestParamLogFlagAsBool},
 	{"quic_obfuscated_psk", isBooleanFlag, requestParamOptional | requestParamLogFlagAsBool},
 	{"quic_obfuscated_psk", isBooleanFlag, requestParamOptional | requestParamLogFlagAsBool},
+	{"server_entry_count", isIntString, requestParamOptional | requestParamLogStringAsInt},
 }
 }
 
 
 var inproxyDialParams = []requestParamSpec{
 var inproxyDialParams = []requestParamSpec{

+ 31 - 8
psiphon/server/config.go

@@ -31,7 +31,6 @@ import (
 	"os"
 	"os"
 	"slices"
 	"slices"
 	"strconv"
 	"strconv"
-	"strings"
 	"sync/atomic"
 	"sync/atomic"
 	"time"
 	"time"
 
 
@@ -586,6 +585,7 @@ type Config struct {
 	providerID                                     string
 	providerID                                     string
 	frontingProviderID                             string
 	frontingProviderID                             string
 	region                                         string
 	region                                         string
+	serverEntryTag                                 string
 	runningProtocols                               []string
 	runningProtocols                               []string
 	runningOnlyInproxyBroker                       bool
 	runningOnlyInproxyBroker                       bool
 }
 }
@@ -672,6 +672,18 @@ func (config *Config) GetRegion() string {
 	return config.region
 	return config.region
 }
 }
 
 
+// GetServerEntryTag adds a log field for the server entry tag associated with
+// the server.
+//
+// Limitation: for servers with more than one entry in OwnEncodedServerEntries,
+// the server entry tag for a given tunnel cannot be determined unambiguously
+// and no tag is added to the log fields.
+func (config *Config) AddServerEntryTag(logFields LogFields) {
+	if config.serverEntryTag != "" {
+		logFields["server_entry_tag"] = config.serverEntryTag
+	}
+}
+
 // GetRunningProtocols returns the list of protcols this server is running.
 // GetRunningProtocols returns the list of protcols this server is running.
 // The caller must not mutate the return value.
 // The caller must not mutate the return value.
 func (config *Config) GetRunningProtocols() []string {
 func (config *Config) GetRunningProtocols() []string {
@@ -934,6 +946,9 @@ func LoadConfig(configJSON []byte) (*Config, error) {
 		} else if config.region != serverEntry.Region {
 		} else if config.region != serverEntry.Region {
 			return nil, errors.Tracef("unsupported multiple Region values")
 			return nil, errors.Tracef("unsupported multiple Region values")
 		}
 		}
+		if len(config.OwnEncodedServerEntries) == 1 {
+			config.serverEntryTag = serverEntry.GetTag()
+		}
 	}
 	}
 
 
 	return &config, nil
 	return &config, nil
@@ -980,6 +995,8 @@ type GenerateConfigParams struct {
 	LimitQUICVersions                  protocol.QUICVersions
 	LimitQUICVersions                  protocol.QUICVersions
 	EnableGQUIC                        bool
 	EnableGQUIC                        bool
 	FrontingProviderID                 string
 	FrontingProviderID                 string
+	ProviderID                         string
+	Region                             string
 }
 }
 
 
 // GenerateConfig creates a new Psiphon server config. It returns JSON encoded
 // GenerateConfig creates a new Psiphon server config. It returns JSON encoded
@@ -1212,11 +1229,6 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, []byte, []byt
 		InproxyServerObfuscationRootSecret: inproxyServerObfuscationRootSecret,
 		InproxyServerObfuscationRootSecret: inproxyServerObfuscationRootSecret,
 	}
 	}
 
 
-	encodedConfig, err := json.MarshalIndent(config, "\n", "    ")
-	if err != nil {
-		return nil, nil, nil, nil, nil, errors.Trace(err)
-	}
-
 	intPtr := func(i int) *int {
 	intPtr := func(i int) *int {
 		return &i
 		return &i
 	}
 	}
@@ -1406,8 +1418,8 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, []byte, []byt
 		LimitQUICVersions:                   params.LimitQUICVersions,
 		LimitQUICVersions:                   params.LimitQUICVersions,
 		SshObfuscatedKey:                    obfuscatedSSHKey,
 		SshObfuscatedKey:                    obfuscatedSSHKey,
 		Capabilities:                        capabilities,
 		Capabilities:                        capabilities,
-		Region:                              "US",
-		ProviderID:                          strings.ToUpper(prng.HexString(8)),
+		Region:                              params.Region,
+		ProviderID:                          params.ProviderID,
 		FrontingProviderID:                  frontingProviderID,
 		FrontingProviderID:                  frontingProviderID,
 		MeekServerPort:                      meekPort,
 		MeekServerPort:                      meekPort,
 		MeekCookieEncryptionPublicKey:       meekCookieEncryptionPublicKey,
 		MeekCookieEncryptionPublicKey:       meekCookieEncryptionPublicKey,
@@ -1455,5 +1467,16 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, []byte, []byt
 		return nil, nil, nil, nil, nil, errors.Trace(err)
 		return nil, nil, nil, nil, nil, errors.Trace(err)
 	}
 	}
 
 
+	// Add encoded server entry to server config
+
+	config.OwnEncodedServerEntries = map[string]string{
+		serverEntry.GetTag(): encodedServerEntry,
+	}
+
+	encodedConfig, err := json.MarshalIndent(config, "\n", "    ")
+	if err != nil {
+		return nil, nil, nil, nil, nil, errors.Trace(err)
+	}
+
 	return encodedConfig, encodedTrafficRulesSet, encodedOSLConfig, encodedTacticsConfig, []byte(encodedServerEntry), nil
 	return encodedConfig, encodedTrafficRulesSet, encodedOSLConfig, encodedTacticsConfig, []byte(encodedServerEntry), nil
 }
 }

+ 13 - 3
psiphon/server/pb/psiphond/dial_params.pb.go

@@ -106,6 +106,7 @@ type DialParams struct {
 	EstablishedTunnelsCount           *int64                 `protobuf:"varint,80,opt,name=established_tunnels_count,json=establishedTunnelsCount,proto3,oneof" json:"established_tunnels_count,omitempty"`
 	EstablishedTunnelsCount           *int64                 `protobuf:"varint,80,opt,name=established_tunnels_count,json=establishedTunnelsCount,proto3,oneof" json:"established_tunnels_count,omitempty"`
 	NetworkLatencyMultiplier          *float64               `protobuf:"fixed64,81,opt,name=network_latency_multiplier,json=networkLatencyMultiplier,proto3,oneof" json:"network_latency_multiplier,omitempty"`
 	NetworkLatencyMultiplier          *float64               `protobuf:"fixed64,81,opt,name=network_latency_multiplier,json=networkLatencyMultiplier,proto3,oneof" json:"network_latency_multiplier,omitempty"`
 	SeedTransform                     *string                `protobuf:"bytes,82,opt,name=seed_transform,json=seedTransform,proto3,oneof" json:"seed_transform,omitempty"`
 	SeedTransform                     *string                `protobuf:"bytes,82,opt,name=seed_transform,json=seedTransform,proto3,oneof" json:"seed_transform,omitempty"`
+	ServerEntryCount                  *int64                 `protobuf:"varint,83,opt,name=server_entry_count,json=serverEntryCount,proto3,oneof" json:"server_entry_count,omitempty"`
 	unknownFields                     protoimpl.UnknownFields
 	unknownFields                     protoimpl.UnknownFields
 	sizeCache                         protoimpl.SizeCache
 	sizeCache                         protoimpl.SizeCache
 }
 }
@@ -714,11 +715,18 @@ func (x *DialParams) GetSeedTransform() string {
 	return ""
 	return ""
 }
 }
 
 
+func (x *DialParams) GetServerEntryCount() int64 {
+	if x != nil && x.ServerEntryCount != nil {
+		return *x.ServerEntryCount
+	}
+	return 0
+}
+
 var File_ca_psiphon_psiphond_dial_params_proto protoreflect.FileDescriptor
 var File_ca_psiphon_psiphond_dial_params_proto protoreflect.FileDescriptor
 
 
 const file_ca_psiphon_psiphond_dial_params_proto_rawDesc = "" +
 const file_ca_psiphon_psiphond_dial_params_proto_rawDesc = "" +
 	"\n" +
 	"\n" +
-	"%ca.psiphon.psiphond/dial_params.proto\x12\x13ca.psiphon.psiphond\x1a\x1fgoogle/protobuf/timestamp.proto\"\xb20\n" +
+	"%ca.psiphon.psiphond/dial_params.proto\x12\x13ca.psiphon.psiphond\x1a\x1fgoogle/protobuf/timestamp.proto\"\xfc0\n" +
 	"\n" +
 	"\n" +
 	"DialParams\x12*\n" +
 	"DialParams\x12*\n" +
 	"\x0econjure_cached\x18\x01 \x01(\bH\x00R\rconjureCached\x88\x01\x01\x12(\n" +
 	"\x0econjure_cached\x18\x01 \x01(\bH\x00R\rconjureCached\x88\x01\x01\x12(\n" +
@@ -812,7 +820,8 @@ const file_ca_psiphon_psiphond_dial_params_proto_rawDesc = "" +
 	"server_bpf\x18O \x01(\tHMR\tserverBpf\x88\x01\x01\x12?\n" +
 	"server_bpf\x18O \x01(\tHMR\tserverBpf\x88\x01\x01\x12?\n" +
 	"\x19established_tunnels_count\x18P \x01(\x03HNR\x17establishedTunnelsCount\x88\x01\x01\x12A\n" +
 	"\x19established_tunnels_count\x18P \x01(\x03HNR\x17establishedTunnelsCount\x88\x01\x01\x12A\n" +
 	"\x1anetwork_latency_multiplier\x18Q \x01(\x01HOR\x18networkLatencyMultiplier\x88\x01\x01\x12*\n" +
 	"\x1anetwork_latency_multiplier\x18Q \x01(\x01HOR\x18networkLatencyMultiplier\x88\x01\x01\x12*\n" +
-	"\x0eseed_transform\x18R \x01(\tHPR\rseedTransform\x88\x01\x01B\x11\n" +
+	"\x0eseed_transform\x18R \x01(\tHPR\rseedTransform\x88\x01\x01\x121\n" +
+	"\x12server_entry_count\x18S \x01(\x03HQR\x10serverEntryCount\x88\x01\x01B\x11\n" +
 	"\x0f_conjure_cachedB\x10\n" +
 	"\x0f_conjure_cachedB\x10\n" +
 	"\x0e_conjure_delayB\x17\n" +
 	"\x0e_conjure_delayB\x17\n" +
 	"\x15_conjure_empty_packetB\x12\n" +
 	"\x15_conjure_empty_packetB\x12\n" +
@@ -895,7 +904,8 @@ const file_ca_psiphon_psiphond_dial_params_proto_rawDesc = "" +
 	"\v_server_bpfB\x1c\n" +
 	"\v_server_bpfB\x1c\n" +
 	"\x1a_established_tunnels_countB\x1d\n" +
 	"\x1a_established_tunnels_countB\x1d\n" +
 	"\x1b_network_latency_multiplierB\x11\n" +
 	"\x1b_network_latency_multiplierB\x11\n" +
-	"\x0f_seed_transformBHZFgithub.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/pb/psiphondb\x06proto3"
+	"\x0f_seed_transformB\x15\n" +
+	"\x13_server_entry_countBHZFgithub.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/pb/psiphondb\x06proto3"
 
 
 var (
 var (
 	file_ca_psiphon_psiphond_dial_params_proto_rawDescOnce sync.Once
 	file_ca_psiphon_psiphond_dial_params_proto_rawDescOnce sync.Once

+ 13 - 3
psiphon/server/pb/psiphond/irregular_tunnel.pb.go

@@ -37,6 +37,7 @@ type IrregularTunnel struct {
 	ListenerPortNumber                 *int64                 `protobuf:"varint,110,opt,name=listener_port_number,json=listenerPortNumber,proto3,oneof" json:"listener_port_number,omitempty"`
 	ListenerPortNumber                 *int64                 `protobuf:"varint,110,opt,name=listener_port_number,json=listenerPortNumber,proto3,oneof" json:"listener_port_number,omitempty"`
 	ListenerProtocol                   *string                `protobuf:"bytes,111,opt,name=listener_protocol,json=listenerProtocol,proto3,oneof" json:"listener_protocol,omitempty"`
 	ListenerProtocol                   *string                `protobuf:"bytes,111,opt,name=listener_protocol,json=listenerProtocol,proto3,oneof" json:"listener_protocol,omitempty"`
 	TunnelError                        *string                `protobuf:"bytes,112,opt,name=tunnel_error,json=tunnelError,proto3,oneof" json:"tunnel_error,omitempty"`
 	TunnelError                        *string                `protobuf:"bytes,112,opt,name=tunnel_error,json=tunnelError,proto3,oneof" json:"tunnel_error,omitempty"`
+	ServerEntryTag                     *string                `protobuf:"bytes,113,opt,name=server_entry_tag,json=serverEntryTag,proto3,oneof" json:"server_entry_tag,omitempty"`
 	unknownFields                      protoimpl.UnknownFields
 	unknownFields                      protoimpl.UnknownFields
 	sizeCache                          protoimpl.SizeCache
 	sizeCache                          protoimpl.SizeCache
 }
 }
@@ -169,11 +170,18 @@ func (x *IrregularTunnel) GetTunnelError() string {
 	return ""
 	return ""
 }
 }
 
 
+func (x *IrregularTunnel) GetServerEntryTag() string {
+	if x != nil && x.ServerEntryTag != nil {
+		return *x.ServerEntryTag
+	}
+	return ""
+}
+
 var File_ca_psiphon_psiphond_irregular_tunnel_proto protoreflect.FileDescriptor
 var File_ca_psiphon_psiphond_irregular_tunnel_proto protoreflect.FileDescriptor
 
 
 const file_ca_psiphon_psiphond_irregular_tunnel_proto_rawDesc = "" +
 const file_ca_psiphon_psiphond_irregular_tunnel_proto_rawDesc = "" +
 	"\n" +
 	"\n" +
-	"*ca.psiphon.psiphond/irregular_tunnel.proto\x12\x13ca.psiphon.psiphond\x1a%ca.psiphon.psiphond/base_params.proto\"\xbb\n" +
+	"*ca.psiphon.psiphond/irregular_tunnel.proto\x12\x13ca.psiphon.psiphond\x1a%ca.psiphon.psiphond/base_params.proto\"\xff\n" +
 	"\n" +
 	"\n" +
 	"\x0fIrregularTunnel\x12E\n" +
 	"\x0fIrregularTunnel\x12E\n" +
 	"\vbase_params\x18\x01 \x01(\v2\x1f.ca.psiphon.psiphond.BaseParamsH\x00R\n" +
 	"\vbase_params\x18\x01 \x01(\v2\x1f.ca.psiphon.psiphond.BaseParamsH\x00R\n" +
@@ -191,7 +199,8 @@ const file_ca_psiphon_psiphond_irregular_tunnel_proto_rawDesc = "" +
 	"R\x11duplicateSeedType\x88\x01\x01\x125\n" +
 	"R\x11duplicateSeedType\x88\x01\x01\x125\n" +
 	"\x14listener_port_number\x18n \x01(\x03H\vR\x12listenerPortNumber\x88\x01\x01\x120\n" +
 	"\x14listener_port_number\x18n \x01(\x03H\vR\x12listenerPortNumber\x88\x01\x01\x120\n" +
 	"\x11listener_protocol\x18o \x01(\tH\fR\x10listenerProtocol\x88\x01\x01\x12&\n" +
 	"\x11listener_protocol\x18o \x01(\tH\fR\x10listenerProtocol\x88\x01\x01\x12&\n" +
-	"\ftunnel_error\x18p \x01(\tH\rR\vtunnelError\x88\x01\x01B\x0e\n" +
+	"\ftunnel_error\x18p \x01(\tH\rR\vtunnelError\x88\x01\x01\x12-\n" +
+	"\x10server_entry_tag\x18q \x01(\tH\x0eR\x0eserverEntryTag\x88\x01\x01B\x0e\n" +
 	"\f_base_paramsB%\n" +
 	"\f_base_paramsB%\n" +
 	"#_duplicate_authorization_client_asnB%\n" +
 	"#_duplicate_authorization_client_asnB%\n" +
 	"#_duplicate_authorization_client_asoB&\n" +
 	"#_duplicate_authorization_client_asoB&\n" +
@@ -205,7 +214,8 @@ const file_ca_psiphon_psiphond_irregular_tunnel_proto_rawDesc = "" +
 	"\x14_duplicate_seed_typeB\x17\n" +
 	"\x14_duplicate_seed_typeB\x17\n" +
 	"\x15_listener_port_numberB\x14\n" +
 	"\x15_listener_port_numberB\x14\n" +
 	"\x12_listener_protocolB\x0f\n" +
 	"\x12_listener_protocolB\x0f\n" +
-	"\r_tunnel_errorBHZFgithub.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/pb/psiphondb\x06proto3"
+	"\r_tunnel_errorB\x13\n" +
+	"\x11_server_entry_tagBHZFgithub.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/pb/psiphondb\x06proto3"
 
 
 var (
 var (
 	file_ca_psiphon_psiphond_irregular_tunnel_proto_rawDescOnce sync.Once
 	file_ca_psiphon_psiphond_irregular_tunnel_proto_rawDescOnce sync.Once

+ 13 - 3
psiphon/server/pb/psiphond/server_load.pb.go

@@ -213,6 +213,7 @@ type ServerLoad struct {
 	UdpPortForwards                         *int64                 `protobuf:"varint,142,opt,name=udp_port_forwards,json=udpPortForwards,proto3,oneof" json:"udp_port_forwards,omitempty"`
 	UdpPortForwards                         *int64                 `protobuf:"varint,142,opt,name=udp_port_forwards,json=udpPortForwards,proto3,oneof" json:"udp_port_forwards,omitempty"`
 	TotalTcpPortForwards                    *int64                 `protobuf:"varint,143,opt,name=total_tcp_port_forwards,json=totalTcpPortForwards,proto3,oneof" json:"total_tcp_port_forwards,omitempty"`
 	TotalTcpPortForwards                    *int64                 `protobuf:"varint,143,opt,name=total_tcp_port_forwards,json=totalTcpPortForwards,proto3,oneof" json:"total_tcp_port_forwards,omitempty"`
 	TotalUdpPortForwards                    *int64                 `protobuf:"varint,144,opt,name=total_udp_port_forwards,json=totalUdpPortForwards,proto3,oneof" json:"total_udp_port_forwards,omitempty"`
 	TotalUdpPortForwards                    *int64                 `protobuf:"varint,144,opt,name=total_udp_port_forwards,json=totalUdpPortForwards,proto3,oneof" json:"total_udp_port_forwards,omitempty"`
+	ServerEntryTag                          *string                `protobuf:"bytes,145,opt,name=server_entry_tag,json=serverEntryTag,proto3,oneof" json:"server_entry_tag,omitempty"`
 	unknownFields                           protoimpl.UnknownFields
 	unknownFields                           protoimpl.UnknownFields
 	sizeCache                               protoimpl.SizeCache
 	sizeCache                               protoimpl.SizeCache
 }
 }
@@ -562,6 +563,13 @@ func (x *ServerLoad) GetTotalUdpPortForwards() int64 {
 	return 0
 	return 0
 }
 }
 
 
+func (x *ServerLoad) GetServerEntryTag() string {
+	if x != nil && x.ServerEntryTag != nil {
+		return *x.ServerEntryTag
+	}
+	return ""
+}
+
 var File_ca_psiphon_psiphond_server_load_proto protoreflect.FileDescriptor
 var File_ca_psiphon_psiphond_server_load_proto protoreflect.FileDescriptor
 
 
 const file_ca_psiphon_psiphond_server_load_proto_rawDesc = "" +
 const file_ca_psiphon_psiphond_server_load_proto_rawDesc = "" +
@@ -588,7 +596,7 @@ const file_ca_psiphon_psiphond_server_load_proto_rawDesc = "" +
 	"_dns_countB\x0f\n" +
 	"_dns_countB\x0f\n" +
 	"\r_dns_durationB\x13\n" +
 	"\r_dns_durationB\x13\n" +
 	"\x11_dns_failed_countB\x16\n" +
 	"\x11_dns_failed_countB\x16\n" +
-	"\x14_dns_failed_duration\"\xbc!\n" +
+	"\x14_dns_failed_duration\"\x81\"\n" +
 	"\n" +
 	"\n" +
 	"ServerLoad\x12$\n" +
 	"ServerLoad\x12$\n" +
 	"\vcpu_percent\x18d \x01(\x01H\x00R\n" +
 	"\vcpu_percent\x18d \x01(\x01H\x00R\n" +
@@ -639,7 +647,8 @@ const file_ca_psiphon_psiphond_server_load_proto_rawDesc = "" +
 	"*udp_port_forward_rejected_disallowed_count\x18\x8d\x01 \x01(\x03H)R%udpPortForwardRejectedDisallowedCount\x88\x01\x01\x120\n" +
 	"*udp_port_forward_rejected_disallowed_count\x18\x8d\x01 \x01(\x03H)R%udpPortForwardRejectedDisallowedCount\x88\x01\x01\x120\n" +
 	"\x11udp_port_forwards\x18\x8e\x01 \x01(\x03H*R\x0fudpPortForwards\x88\x01\x01\x12;\n" +
 	"\x11udp_port_forwards\x18\x8e\x01 \x01(\x03H*R\x0fudpPortForwards\x88\x01\x01\x12;\n" +
 	"\x17total_tcp_port_forwards\x18\x8f\x01 \x01(\x03H+R\x14totalTcpPortForwards\x88\x01\x01\x12;\n" +
 	"\x17total_tcp_port_forwards\x18\x8f\x01 \x01(\x03H+R\x14totalTcpPortForwards\x88\x01\x01\x12;\n" +
-	"\x17total_udp_port_forwards\x18\x90\x01 \x01(\x03H,R\x14totalUdpPortForwards\x88\x01\x01B\x0e\n" +
+	"\x17total_udp_port_forwards\x18\x90\x01 \x01(\x03H,R\x14totalUdpPortForwards\x88\x01\x01\x12.\n" +
+	"\x10server_entry_tag\x18\x91\x01 \x01(\tH-R\x0eserverEntryTag\x88\x01\x01B\x0e\n" +
 	"\f_cpu_percentB\r\n" +
 	"\f_cpu_percentB\r\n" +
 	"\v_heap_allocB\f\n" +
 	"\v_heap_allocB\f\n" +
 	"\n" +
 	"\n" +
@@ -686,7 +695,8 @@ const file_ca_psiphon_psiphond_server_load_proto_rawDesc = "" +
 	"+_udp_port_forward_rejected_disallowed_countB\x14\n" +
 	"+_udp_port_forward_rejected_disallowed_countB\x14\n" +
 	"\x12_udp_port_forwardsB\x1a\n" +
 	"\x12_udp_port_forwardsB\x1a\n" +
 	"\x18_total_tcp_port_forwardsB\x1a\n" +
 	"\x18_total_tcp_port_forwardsB\x1a\n" +
-	"\x18_total_udp_port_forwardsBHZFgithub.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/pb/psiphondb\x06proto3"
+	"\x18_total_udp_port_forwardsB\x13\n" +
+	"\x11_server_entry_tagBHZFgithub.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/pb/psiphondb\x06proto3"
 
 
 var (
 var (
 	file_ca_psiphon_psiphond_server_load_proto_rawDescOnce sync.Once
 	file_ca_psiphon_psiphond_server_load_proto_rawDescOnce sync.Once

+ 19 - 23
psiphon/server/pb/psiphond/server_tunnel.pb.go

@@ -98,29 +98,25 @@ type ServerTunnel struct {
 	TotalPortForwardCountTcp                      *int64                 `protobuf:"varint,168,opt,name=total_port_forward_count_tcp,json=totalPortForwardCountTcp,proto3,oneof" json:"total_port_forward_count_tcp,omitempty"`
 	TotalPortForwardCountTcp                      *int64                 `protobuf:"varint,168,opt,name=total_port_forward_count_tcp,json=totalPortForwardCountTcp,proto3,oneof" json:"total_port_forward_count_tcp,omitempty"`
 	TotalPortForwardCountUdp                      *int64                 `protobuf:"varint,169,opt,name=total_port_forward_count_udp,json=totalPortForwardCountUdp,proto3,oneof" json:"total_port_forward_count_udp,omitempty"`
 	TotalPortForwardCountUdp                      *int64                 `protobuf:"varint,169,opt,name=total_port_forward_count_udp,json=totalPortForwardCountUdp,proto3,oneof" json:"total_port_forward_count_udp,omitempty"`
 	TotalUdpgwChannelCount                        *int64                 `protobuf:"varint,170,opt,name=total_udpgw_channel_count,json=totalUdpgwChannelCount,proto3,oneof" json:"total_udpgw_channel_count,omitempty"`
 	TotalUdpgwChannelCount                        *int64                 `protobuf:"varint,170,opt,name=total_udpgw_channel_count,json=totalUdpgwChannelCount,proto3,oneof" json:"total_udpgw_channel_count,omitempty"`
-	// Post-handshake random stream fields
-	RandomStreamCount                 *int64 `protobuf:"varint,171,opt,name=random_stream_count,json=randomStreamCount,proto3,oneof" json:"random_stream_count,omitempty"`
-	RandomStreamUpstreamBytes         *int64 `protobuf:"varint,172,opt,name=random_stream_upstream_bytes,json=randomStreamUpstreamBytes,proto3,oneof" json:"random_stream_upstream_bytes,omitempty"`
-	RandomStreamReceivedUpstreamBytes *int64 `protobuf:"varint,173,opt,name=random_stream_received_upstream_bytes,json=randomStreamReceivedUpstreamBytes,proto3,oneof" json:"random_stream_received_upstream_bytes,omitempty"`
-	RandomStreamDownstreamBytes       *int64 `protobuf:"varint,174,opt,name=random_stream_downstream_bytes,json=randomStreamDownstreamBytes,proto3,oneof" json:"random_stream_downstream_bytes,omitempty"`
-	RandomStreamSentDownstreamBytes   *int64 `protobuf:"varint,175,opt,name=random_stream_sent_downstream_bytes,json=randomStreamSentDownstreamBytes,proto3,oneof" json:"random_stream_sent_downstream_bytes,omitempty"`
-	// Destination bytes fields (legacy format)
-	DestBytesAsn     *string `protobuf:"bytes,176,opt,name=dest_bytes_asn,json=destBytesAsn,proto3,oneof" json:"dest_bytes_asn,omitempty"`
-	DestBytes        *int64  `protobuf:"varint,177,opt,name=dest_bytes,json=destBytes,proto3,oneof" json:"dest_bytes,omitempty"`
-	DestBytesUpTcp   *int64  `protobuf:"varint,178,opt,name=dest_bytes_up_tcp,json=destBytesUpTcp,proto3,oneof" json:"dest_bytes_up_tcp,omitempty"`
-	DestBytesDownTcp *int64  `protobuf:"varint,179,opt,name=dest_bytes_down_tcp,json=destBytesDownTcp,proto3,oneof" json:"dest_bytes_down_tcp,omitempty"`
-	DestBytesUpUdp   *int64  `protobuf:"varint,180,opt,name=dest_bytes_up_udp,json=destBytesUpUdp,proto3,oneof" json:"dest_bytes_up_udp,omitempty"`
-	DestBytesDownUdp *int64  `protobuf:"varint,181,opt,name=dest_bytes_down_udp,json=destBytesDownUdp,proto3,oneof" json:"dest_bytes_down_udp,omitempty"`
-	// Additional transport and server entry fields
-	RelayedSteeringIp           *string `protobuf:"bytes,182,opt,name=relayed_steering_ip,json=relayedSteeringIp,proto3,oneof" json:"relayed_steering_ip,omitempty"`
-	RequestCheckServerEntryTags *int64  `protobuf:"varint,183,opt,name=request_check_server_entry_tags,json=requestCheckServerEntryTags,proto3,oneof" json:"request_check_server_entry_tags,omitempty"`
-	CheckedServerEntryTags      *int64  `protobuf:"varint,184,opt,name=checked_server_entry_tags,json=checkedServerEntryTags,proto3,oneof" json:"checked_server_entry_tags,omitempty"`
-	InvalidServerEntryTags      *int64  `protobuf:"varint,185,opt,name=invalid_server_entry_tags,json=invalidServerEntryTags,proto3,oneof" json:"invalid_server_entry_tags,omitempty"`
-	// Protocol Overhead
-	SshProtocolBytes         *int64 `protobuf:"varint,186,opt,name=ssh_protocol_bytes,json=sshProtocolBytes,proto3,oneof" json:"ssh_protocol_bytes,omitempty"`
-	SshProtocolBytesOverhead *int64 `protobuf:"varint,187,opt,name=ssh_protocol_bytes_overhead,json=sshProtocolBytesOverhead,proto3,oneof" json:"ssh_protocol_bytes_overhead,omitempty"`
-	unknownFields            protoimpl.UnknownFields
-	sizeCache                protoimpl.SizeCache
+	RandomStreamCount                             *int64                 `protobuf:"varint,171,opt,name=random_stream_count,json=randomStreamCount,proto3,oneof" json:"random_stream_count,omitempty"`
+	RandomStreamUpstreamBytes                     *int64                 `protobuf:"varint,172,opt,name=random_stream_upstream_bytes,json=randomStreamUpstreamBytes,proto3,oneof" json:"random_stream_upstream_bytes,omitempty"`
+	RandomStreamReceivedUpstreamBytes             *int64                 `protobuf:"varint,173,opt,name=random_stream_received_upstream_bytes,json=randomStreamReceivedUpstreamBytes,proto3,oneof" json:"random_stream_received_upstream_bytes,omitempty"`
+	RandomStreamDownstreamBytes                   *int64                 `protobuf:"varint,174,opt,name=random_stream_downstream_bytes,json=randomStreamDownstreamBytes,proto3,oneof" json:"random_stream_downstream_bytes,omitempty"`
+	RandomStreamSentDownstreamBytes               *int64                 `protobuf:"varint,175,opt,name=random_stream_sent_downstream_bytes,json=randomStreamSentDownstreamBytes,proto3,oneof" json:"random_stream_sent_downstream_bytes,omitempty"`
+	DestBytesAsn                                  *string                `protobuf:"bytes,176,opt,name=dest_bytes_asn,json=destBytesAsn,proto3,oneof" json:"dest_bytes_asn,omitempty"`
+	DestBytes                                     *int64                 `protobuf:"varint,177,opt,name=dest_bytes,json=destBytes,proto3,oneof" json:"dest_bytes,omitempty"`
+	DestBytesUpTcp                                *int64                 `protobuf:"varint,178,opt,name=dest_bytes_up_tcp,json=destBytesUpTcp,proto3,oneof" json:"dest_bytes_up_tcp,omitempty"`
+	DestBytesDownTcp                              *int64                 `protobuf:"varint,179,opt,name=dest_bytes_down_tcp,json=destBytesDownTcp,proto3,oneof" json:"dest_bytes_down_tcp,omitempty"`
+	DestBytesUpUdp                                *int64                 `protobuf:"varint,180,opt,name=dest_bytes_up_udp,json=destBytesUpUdp,proto3,oneof" json:"dest_bytes_up_udp,omitempty"`
+	DestBytesDownUdp                              *int64                 `protobuf:"varint,181,opt,name=dest_bytes_down_udp,json=destBytesDownUdp,proto3,oneof" json:"dest_bytes_down_udp,omitempty"`
+	RelayedSteeringIp                             *string                `protobuf:"bytes,182,opt,name=relayed_steering_ip,json=relayedSteeringIp,proto3,oneof" json:"relayed_steering_ip,omitempty"`
+	RequestCheckServerEntryTags                   *int64                 `protobuf:"varint,183,opt,name=request_check_server_entry_tags,json=requestCheckServerEntryTags,proto3,oneof" json:"request_check_server_entry_tags,omitempty"`
+	CheckedServerEntryTags                        *int64                 `protobuf:"varint,184,opt,name=checked_server_entry_tags,json=checkedServerEntryTags,proto3,oneof" json:"checked_server_entry_tags,omitempty"`
+	InvalidServerEntryTags                        *int64                 `protobuf:"varint,185,opt,name=invalid_server_entry_tags,json=invalidServerEntryTags,proto3,oneof" json:"invalid_server_entry_tags,omitempty"`
+	SshProtocolBytes                              *int64                 `protobuf:"varint,186,opt,name=ssh_protocol_bytes,json=sshProtocolBytes,proto3,oneof" json:"ssh_protocol_bytes,omitempty"`
+	SshProtocolBytesOverhead                      *int64                 `protobuf:"varint,187,opt,name=ssh_protocol_bytes_overhead,json=sshProtocolBytesOverhead,proto3,oneof" json:"ssh_protocol_bytes_overhead,omitempty"`
+	unknownFields                                 protoimpl.UnknownFields
+	sizeCache                                     protoimpl.SizeCache
 }
 }
 
 
 func (x *ServerTunnel) Reset() {
 func (x *ServerTunnel) Reset() {

+ 1 - 13
psiphon/server/proto/ca.psiphon.psiphond/dial_params.proto

@@ -15,7 +15,6 @@ message DialParams {
     optional string conjure_prefix = 6;
     optional string conjure_prefix = 6;
     optional string conjure_stun = 7;
     optional string conjure_stun = 7;
     optional string conjure_transport = 8;
     optional string conjure_transport = 8;
-
     optional int64 meek_cookie_size = 9;
     optional int64 meek_cookie_size = 9;
     optional string meek_content_type = 10;
     optional string meek_content_type = 10;
     optional string meek_cookie_name = 11;
     optional string meek_cookie_name = 11;
@@ -28,7 +27,6 @@ message DialParams {
     optional string meek_sni_server_name = 18;
     optional string meek_sni_server_name = 18;
     optional int64 meek_tls_padding = 19;
     optional int64 meek_tls_padding = 19;
     optional bool meek_transformed_host_name = 20;
     optional bool meek_transformed_host_name = 20;
-
     optional bool quic_dial_early = 21;
     optional bool quic_dial_early = 21;
     optional string quic_dial_sni_address = 22;
     optional string quic_dial_sni_address = 22;
     optional bool quic_did_resume = 23;
     optional bool quic_did_resume = 23;
@@ -36,9 +34,7 @@ message DialParams {
     optional bool quic_obfuscated_psk = 25;
     optional bool quic_obfuscated_psk = 25;
     optional bool quic_sent_ticket = 26;
     optional bool quic_sent_ticket = 26;
     optional string quic_version = 27;
     optional string quic_version = 27;
-
     optional string shadowsocks_prefix = 28;
     optional string shadowsocks_prefix = 28;
-
     optional bool tls_did_resume = 29;
     optional bool tls_did_resume = 29;
     optional bool tls_fragmented = 30;
     optional bool tls_fragmented = 30;
     optional string tls_ossh_sni_server_name = 31;
     optional string tls_ossh_sni_server_name = 31;
@@ -47,7 +43,6 @@ message DialParams {
     optional string tls_profile = 34;
     optional string tls_profile = 34;
     optional bool tls_sent_ticket = 35;
     optional bool tls_sent_ticket = 35;
     optional string tls_version = 36;
     optional string tls_version = 36;
-
     optional string server_entry_region = 37;
     optional string server_entry_region = 37;
     optional string server_entry_source = 38;
     optional string server_entry_source = 38;
     optional string server_entry_tag = 39;
     optional string server_entry_tag = 39;
@@ -56,7 +51,6 @@ message DialParams {
     optional bool server_replay_fragmentation = 42;
     optional bool server_replay_fragmentation = 42;
     optional bool server_replay_packet_manipulation = 43;
     optional bool server_replay_packet_manipulation = 43;
     optional bool server_entry_valid = 44;
     optional bool server_entry_valid = 44;
-
     optional int64 candidate_number = 45;
     optional int64 candidate_number = 45;
     optional bool is_replay = 46;
     optional bool is_replay = 46;
     optional int64 dial_port_number = 47;
     optional int64 dial_port_number = 47;
@@ -67,21 +61,18 @@ message DialParams {
     optional string ossh_prefix = 52;
     optional string ossh_prefix = 52;
     optional string user_agent = 53;
     optional string user_agent = 53;
     optional string http_transform = 54;
     optional string http_transform = 54;
-
     optional int64 dns_attempt = 55;
     optional int64 dns_attempt = 55;
     optional string dns_preferred = 56;
     optional string dns_preferred = 56;
     optional string dns_preresolved = 57;
     optional string dns_preresolved = 57;
     optional int64 dns_qname_mismatches = 58;
     optional int64 dns_qname_mismatches = 58;
     optional bool dns_qname_random_casing = 59;
     optional bool dns_qname_random_casing = 59;
     optional string dns_transform = 60;
     optional string dns_transform = 60;
-
     optional int64 downstream_bytes_fragmented = 61;
     optional int64 downstream_bytes_fragmented = 61;
     optional int64 downstream_max_bytes_written = 62;
     optional int64 downstream_max_bytes_written = 62;
     optional int64 downstream_max_delayed = 63;
     optional int64 downstream_max_delayed = 63;
     optional int64 downstream_min_bytes_written = 64;
     optional int64 downstream_min_bytes_written = 64;
     optional int64 downstream_min_delayed = 65;
     optional int64 downstream_min_delayed = 65;
     optional int64 downstream_ossh_padding = 66;
     optional int64 downstream_ossh_padding = 66;
-
     optional int64 upstream_bytes_fragmented = 67;
     optional int64 upstream_bytes_fragmented = 67;
     optional int64 upstream_max_bytes_written = 68;
     optional int64 upstream_max_bytes_written = 68;
     optional int64 upstream_max_delayed = 69;
     optional int64 upstream_max_delayed = 69;
@@ -90,16 +81,13 @@ message DialParams {
     optional int64 upstream_ossh_padding = 72;
     optional int64 upstream_ossh_padding = 72;
     repeated string upstream_proxy_custom_header_names = 73;
     repeated string upstream_proxy_custom_header_names = 73;
     optional string upstream_proxy_type = 74;
     optional string upstream_proxy_type = 74;
-
     optional string passthrough_address = 75;
     optional string passthrough_address = 75;
-
     optional int64 pad_response = 76;
     optional int64 pad_response = 76;
     optional int64 padding = 77;
     optional int64 padding = 77;
-
     optional string client_bpf = 78;
     optional string client_bpf = 78;
     optional string server_bpf = 79;
     optional string server_bpf = 79;
-
     optional int64 established_tunnels_count = 80;
     optional int64 established_tunnels_count = 80;
     optional double network_latency_multiplier = 81;
     optional double network_latency_multiplier = 81;
     optional string seed_transform = 82;
     optional string seed_transform = 82;
+    optional int64 server_entry_count = 83;
 }
 }

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

@@ -24,4 +24,5 @@ message IrregularTunnel {
     optional int64 listener_port_number = 110;
     optional int64 listener_port_number = 110;
     optional string listener_protocol = 111;
     optional string listener_protocol = 111;
     optional string tunnel_error = 112;
     optional string tunnel_error = 112;
+    optional string server_entry_tag = 113;
 }
 }

+ 65 - 63
psiphon/server/proto/ca.psiphon.psiphond/server_load.proto

@@ -9,78 +9,80 @@ option go_package = "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/
 message ServerLoadProtocol {
 message ServerLoadProtocol {
   // Fields 1-99 are reserved for field groupings.
   // Fields 1-99 are reserved for field groupings.
 
 
-  optional string region = 100;
-  optional string protocol = 101;
+    optional string region = 100;
+    optional string protocol = 101;
 
 
-  optional int64 accepted_clients = 102;
-  optional int64 established_clients = 103;
+    optional int64 accepted_clients = 102;
+    optional int64 established_clients = 103;
 }
 }
 
 
 message ServerLoadDNS {
 message ServerLoadDNS {
   // Fields 1-99 are reserved for field groupings.
   // Fields 1-99 are reserved for field groupings.
 
 
-  optional string dns_server = 100;
-  optional int64 dns_count = 101; 
-  optional int64 dns_duration = 102;
-  optional int64 dns_failed_count = 103;
-  optional int64 dns_failed_duration = 104;
+    optional string dns_server = 100;
+    optional int64 dns_count = 101; 
+    optional int64 dns_duration = 102;
+    optional int64 dns_failed_count = 103;
+    optional int64 dns_failed_duration = 104;
 }
 }
 
 
 message ServerLoad {
 message ServerLoad {
   // Fields 1-99 are reserved for field groupings.
   // Fields 1-99 are reserved for field groupings.
 
 
-  optional double cpu_percent = 100;
-
-  optional int64 heap_alloc = 101;
-  optional int64 heap_idle = 102;
-  optional int64 heap_inuse = 103;
-  optional int64 heap_objects = 104;
-  optional int64 heap_released = 105;
-  optional int64 heap_sys = 106;
-
-  optional int64 network_bytes_received = 107;
-  optional int64 network_bytes_sent = 108;
-
-  optional bool establish_tunnels = 109;
-  optional int64 establish_tunnels_limited_count = 110;
-
-  optional google.protobuf.Timestamp last_gc = 111;
-  optional int64 num_forced_gc = 112;
-  optional int64 num_gc = 113;
-  optional int64 num_goroutine = 114;
-
-  optional int64 replay_delete_replay_count = 115;
-  optional int64 replay_failed_replay_count = 116;
-  optional int64 replay_get_replay_hit_count = 117;
-  optional int64 replay_get_replay_miss_count = 118;
-  optional int64 replay_max_cache_entries = 119;
-  optional int64 replay_set_replay_count = 120;
-
-  optional int64 server_tactics_cache_hit_count = 121;
-  optional int64 server_tactics_cache_miss_count = 122;
-  optional int64 server_tactics_max_cache_entries = 123;
-  optional int64 server_tactics_max_parameter_references = 124;
-
-  optional int64 dialing_tcp_port_forwards = 125;
-  optional int64 tcp_ipv4_port_forward_dialed_count = 126;
-  optional int64 tcp_ipv4_port_forward_dialed_duration = 127;
-  optional int64 tcp_ipv4_port_forward_failed_count = 128;
-  optional int64 tcp_ipv4_port_forward_failed_duration = 129;
-  optional int64 tcp_ipv6_port_forward_dialed_count = 130;
-  optional int64 tcp_ipv6_port_forward_dialed_duration = 131;
-  optional int64 tcp_ipv6_port_forward_failed_count = 132;
-  optional int64 tcp_ipv6_port_forward_failed_duration = 133;
-  optional int64 tcp_port_forward_dialed_count = 134;
-  optional int64 tcp_port_forward_dialed_duration = 135;
-  optional int64 tcp_port_forward_failed_count = 136;
-  optional int64 tcp_port_forward_failed_duration = 137;
-  optional int64 tcp_port_forward_rejected_dialing_limit_count = 138;
-  optional int64 tcp_port_forward_rejected_disallowed_count = 139;
-  optional int64 tcp_port_forwards = 140;
-
-  optional int64 udp_port_forward_rejected_disallowed_count = 141;
-  optional int64 udp_port_forwards = 142;
-
-  optional int64 total_tcp_port_forwards = 143;
-  optional int64 total_udp_port_forwards = 144;
+    optional double cpu_percent = 100;
+
+    optional int64 heap_alloc = 101;
+    optional int64 heap_idle = 102;
+    optional int64 heap_inuse = 103;
+    optional int64 heap_objects = 104;
+    optional int64 heap_released = 105;
+    optional int64 heap_sys = 106;
+
+    optional int64 network_bytes_received = 107;
+    optional int64 network_bytes_sent = 108;
+
+    optional bool establish_tunnels = 109;
+    optional int64 establish_tunnels_limited_count = 110;
+
+    optional google.protobuf.Timestamp last_gc = 111;
+    optional int64 num_forced_gc = 112;
+    optional int64 num_gc = 113;
+    optional int64 num_goroutine = 114;
+
+    optional int64 replay_delete_replay_count = 115;
+    optional int64 replay_failed_replay_count = 116;
+    optional int64 replay_get_replay_hit_count = 117;
+    optional int64 replay_get_replay_miss_count = 118;
+    optional int64 replay_max_cache_entries = 119;
+    optional int64 replay_set_replay_count = 120;
+
+    optional int64 server_tactics_cache_hit_count = 121;
+    optional int64 server_tactics_cache_miss_count = 122;
+    optional int64 server_tactics_max_cache_entries = 123;
+    optional int64 server_tactics_max_parameter_references = 124;
+
+    optional int64 dialing_tcp_port_forwards = 125;
+    optional int64 tcp_ipv4_port_forward_dialed_count = 126;
+    optional int64 tcp_ipv4_port_forward_dialed_duration = 127;
+    optional int64 tcp_ipv4_port_forward_failed_count = 128;
+    optional int64 tcp_ipv4_port_forward_failed_duration = 129;
+    optional int64 tcp_ipv6_port_forward_dialed_count = 130;
+    optional int64 tcp_ipv6_port_forward_dialed_duration = 131;
+    optional int64 tcp_ipv6_port_forward_failed_count = 132;
+    optional int64 tcp_ipv6_port_forward_failed_duration = 133;
+    optional int64 tcp_port_forward_dialed_count = 134;
+    optional int64 tcp_port_forward_dialed_duration = 135;
+    optional int64 tcp_port_forward_failed_count = 136;
+    optional int64 tcp_port_forward_failed_duration = 137;
+    optional int64 tcp_port_forward_rejected_dialing_limit_count = 138;
+    optional int64 tcp_port_forward_rejected_disallowed_count = 139;
+    optional int64 tcp_port_forwards = 140;
+
+    optional int64 udp_port_forward_rejected_disallowed_count = 141;
+    optional int64 udp_port_forwards = 142;
+
+    optional int64 total_tcp_port_forwards = 143;
+    optional int64 total_udp_port_forwards = 144;
+
+    optional string server_entry_tag = 145;
 }
 }

+ 0 - 18
psiphon/server/proto/ca.psiphon.psiphond/server_tunnel.proto

@@ -50,18 +50,15 @@ message ServerTunnel {
     optional int64 burst_upstream_min_duration = 130;
     optional int64 burst_upstream_min_duration = 130;
     optional int64 burst_upstream_min_offset = 131;
     optional int64 burst_upstream_min_offset = 131;
     optional int64 burst_upstream_min_rate = 132;
     optional int64 burst_upstream_min_rate = 132;
-
     optional int64 bytes = 133;
     optional int64 bytes = 133;
     optional int64 bytes_down_tcp = 134;
     optional int64 bytes_down_tcp = 134;
     optional int64 bytes_down_udp = 135;
     optional int64 bytes_down_udp = 135;
     optional int64 bytes_up_tcp = 136;
     optional int64 bytes_up_tcp = 136;
     optional int64 bytes_up_udp = 137;
     optional int64 bytes_up_udp = 137;
-
     optional int64 duration = 138;
     optional int64 duration = 138;
     optional int64 establishment_duration = 139;
     optional int64 establishment_duration = 139;
     optional bool handshake_completed = 140;
     optional bool handshake_completed = 140;
     optional bool is_first_tunnel_in_session = 141;
     optional bool is_first_tunnel_in_session = 141;
-
     optional int64 meek_cached_response_miss_position = 142;
     optional int64 meek_cached_response_miss_position = 142;
     optional int64 meek_client_retries = 143;
     optional int64 meek_client_retries = 143;
     optional int64 meek_peak_cached_response_hit_size = 144;
     optional int64 meek_peak_cached_response_hit_size = 144;
@@ -69,9 +66,7 @@ message ServerTunnel {
     optional int64 meek_peak_response_size = 146;
     optional int64 meek_peak_response_size = 146;
     optional int64 meek_underlying_connection_count = 147;
     optional int64 meek_underlying_connection_count = 147;
     optional string meek_server_http_version = 148;
     optional string meek_server_http_version = 148;
-
     optional string new_tactics_tag = 149;
     optional string new_tactics_tag = 149;
-
     optional int64 peak_concurrent_dialing_port_forward_count_tcp = 150;
     optional int64 peak_concurrent_dialing_port_forward_count_tcp = 150;
     optional int64 peak_concurrent_port_forward_count_tcp = 151;
     optional int64 peak_concurrent_port_forward_count_tcp = 151;
     optional int64 peak_concurrent_port_forward_count_udp = 152;
     optional int64 peak_concurrent_port_forward_count_udp = 152;
@@ -81,44 +76,33 @@ message ServerTunnel {
     optional int64 peak_dns_failure_rate_sample_size = 156;
     optional int64 peak_dns_failure_rate_sample_size = 156;
     optional double peak_tcp_port_forward_failure_rate = 157;
     optional double peak_tcp_port_forward_failure_rate = 157;
     optional int64 peak_tcp_port_forward_failure_rate_sample_size = 158;
     optional int64 peak_tcp_port_forward_failure_rate_sample_size = 158;
-
     optional int64 pre_handshake_random_stream_count = 159;
     optional int64 pre_handshake_random_stream_count = 159;
     optional int64 pre_handshake_random_stream_downstream_bytes = 160;
     optional int64 pre_handshake_random_stream_downstream_bytes = 160;
     optional int64 pre_handshake_random_stream_received_upstream_bytes = 161;
     optional int64 pre_handshake_random_stream_received_upstream_bytes = 161;
     optional int64 pre_handshake_random_stream_sent_downstream_bytes = 162;
     optional int64 pre_handshake_random_stream_sent_downstream_bytes = 162;
     optional int64 pre_handshake_random_stream_upstream_bytes = 163;
     optional int64 pre_handshake_random_stream_upstream_bytes = 163;
-
     optional bool split_tunnel = 164;
     optional bool split_tunnel = 164;
     optional google.protobuf.Timestamp start_time = 165;
     optional google.protobuf.Timestamp start_time = 165;
     optional string station_ip_address = 166;
     optional string station_ip_address = 166;
-
     optional int64 total_packet_tunnel_channel_count = 167;
     optional int64 total_packet_tunnel_channel_count = 167;
     optional int64 total_port_forward_count_tcp = 168;
     optional int64 total_port_forward_count_tcp = 168;
     optional int64 total_port_forward_count_udp = 169;
     optional int64 total_port_forward_count_udp = 169;
     optional int64 total_udpgw_channel_count = 170;
     optional int64 total_udpgw_channel_count = 170;
-
-    // Post-handshake random stream fields
     optional int64 random_stream_count = 171;
     optional int64 random_stream_count = 171;
     optional int64 random_stream_upstream_bytes = 172;
     optional int64 random_stream_upstream_bytes = 172;
     optional int64 random_stream_received_upstream_bytes = 173;
     optional int64 random_stream_received_upstream_bytes = 173;
     optional int64 random_stream_downstream_bytes = 174;
     optional int64 random_stream_downstream_bytes = 174;
     optional int64 random_stream_sent_downstream_bytes = 175;
     optional int64 random_stream_sent_downstream_bytes = 175;
-
-    // Destination bytes fields (legacy format)
     optional string dest_bytes_asn = 176;
     optional string dest_bytes_asn = 176;
     optional int64 dest_bytes = 177;
     optional int64 dest_bytes = 177;
     optional int64 dest_bytes_up_tcp = 178;
     optional int64 dest_bytes_up_tcp = 178;
     optional int64 dest_bytes_down_tcp = 179;
     optional int64 dest_bytes_down_tcp = 179;
     optional int64 dest_bytes_up_udp = 180;
     optional int64 dest_bytes_up_udp = 180;
     optional int64 dest_bytes_down_udp = 181;
     optional int64 dest_bytes_down_udp = 181;
-
-    // Additional transport and server entry fields
     optional string relayed_steering_ip = 182;
     optional string relayed_steering_ip = 182;
     optional int64 request_check_server_entry_tags = 183;
     optional int64 request_check_server_entry_tags = 183;
     optional int64 checked_server_entry_tags = 184;
     optional int64 checked_server_entry_tags = 184;
     optional int64 invalid_server_entry_tags = 185;
     optional int64 invalid_server_entry_tags = 185;
-
-    // Protocol Overhead
     optional int64 ssh_protocol_bytes = 186;
     optional int64 ssh_protocol_bytes = 186;
     optional int64 ssh_protocol_bytes_overhead = 187;
     optional int64 ssh_protocol_bytes_overhead = 187;
 }
 }
@@ -127,9 +111,7 @@ message ServerTunnelASNDestBytes {
     // Fields 1-99 are reserved for field groupings.
     // Fields 1-99 are reserved for field groupings.
 
 
     optional string tunnel_id = 100;
     optional string tunnel_id = 100;
-
     optional string dest_asn = 101;
     optional string dest_asn = 101;
-
     optional int64 dest_bytes = 102;
     optional int64 dest_bytes = 102;
     optional int64 dest_bytes_up_tcp = 103;
     optional int64 dest_bytes_up_tcp = 103;
     optional int64 dest_bytes_down_tcp = 104;
     optional int64 dest_bytes_down_tcp = 104;

+ 35 - 15
psiphon/server/server_test.go

@@ -847,6 +847,7 @@ var (
 	testClientPlatform                   = "Android_10_com.test.app"
 	testClientPlatform                   = "Android_10_com.test.app"
 	testClientFeatures                   = []string{"feature 1", "feature 2"}
 	testClientFeatures                   = []string{"feature 1", "feature 2"}
 	testDeviceRegion                     = "US"
 	testDeviceRegion                     = "US"
+	testServerRegion                     = "US"
 	testDeviceLocation                   = "gzzzz"
 	testDeviceLocation                   = "gzzzz"
 	testDisallowedTrafficAlertActionURLs = []string{"https://example.org/disallowed"}
 	testDisallowedTrafficAlertActionURLs = []string{"https://example.org/disallowed"}
 	testHostID                           = "example-host-id"
 	testHostID                           = "example-host-id"
@@ -1025,6 +1026,8 @@ func runServer(t *testing.T, runConfig *runServerConfig) {
 		Passthrough:                        runConfig.passthrough,
 		Passthrough:                        runConfig.passthrough,
 		LimitQUICVersions:                  limitQUICVersions,
 		LimitQUICVersions:                  limitQUICVersions,
 		EnableGQUIC:                        !runConfig.limitQUICVersions,
 		EnableGQUIC:                        !runConfig.limitQUICVersions,
+		ProviderID:                         strings.ToUpper(prng.HexString(8)),
+		Region:                             testServerRegion,
 	}
 	}
 
 
 	if doServerTactics {
 	if doServerTactics {
@@ -1126,7 +1129,8 @@ func runServer(t *testing.T, runConfig *runServerConfig) {
 			"classic",
 			"classic",
 			enableDSLFetcher,
 			enableDSLFetcher,
 			inproxyTacticsParametersJSON,
 			inproxyTacticsParametersJSON,
-			runConfig.doRestrictInproxy)
+			runConfig.doRestrictInproxy,
+			generateConfigParams.ProviderID)
 	}
 	}
 
 
 	blocklistFilename := filepath.Join(testDataDirName, "blocklist.csv")
 	blocklistFilename := filepath.Join(testDataDirName, "blocklist.csv")
@@ -1567,7 +1571,8 @@ func runServer(t *testing.T, runConfig *runServerConfig) {
 				"consistent",
 				"consistent",
 				enableDSLFetcher,
 				enableDSLFetcher,
 				inproxyTacticsParametersJSON,
 				inproxyTacticsParametersJSON,
-				runConfig.doRestrictInproxy)
+				runConfig.doRestrictInproxy,
+				generateConfigParams.ProviderID)
 		}
 		}
 
 
 		p, _ := os.FindProcess(os.Getpid())
 		p, _ := os.FindProcess(os.Getpid())
@@ -2156,7 +2161,8 @@ func runServer(t *testing.T, runConfig *runServerConfig) {
 			"consistent",
 			"consistent",
 			enableDSLFetcher,
 			enableDSLFetcher,
 			inproxyTacticsParametersJSON,
 			inproxyTacticsParametersJSON,
-			runConfig.doRestrictInproxy)
+			runConfig.doRestrictInproxy,
+			generateConfigParams.ProviderID)
 
 
 		p, _ := os.FindProcess(os.Getpid())
 		p, _ := os.FindProcess(os.Getpid())
 		p.Signal(syscall.SIGUSR1)
 		p.Signal(syscall.SIGUSR1)
@@ -2340,6 +2346,10 @@ func runServer(t *testing.T, runConfig *runServerConfig) {
 			expectMeekHTTPVersion = "HTTP/1.1"
 			expectMeekHTTPVersion = "HTTP/1.1"
 		}
 		}
 	}
 	}
+	expectServerEntryCount := 0
+	if doDSL || runConfig.doPruneServerEntries {
+		expectServerEntryCount = protocol.ServerEntryCountRoundingIncrement
+	}
 
 
 	// The client still reports zero domain_bytes when no port forwards are
 	// The client still reports zero domain_bytes when no port forwards are
 	// allowed (expectTrafficFailure).
 	// allowed (expectTrafficFailure).
@@ -2375,6 +2385,7 @@ func runServer(t *testing.T, runConfig *runServerConfig) {
 			passthroughAddress,
 			passthroughAddress,
 			expectMeekHTTPVersion,
 			expectMeekHTTPVersion,
 			expectCheckServerEntryPruneCount,
 			expectCheckServerEntryPruneCount,
+			expectServerEntryCount,
 			inproxyTestConfig,
 			inproxyTestConfig,
 			logFields)
 			logFields)
 		if err != nil {
 		if err != nil {
@@ -2968,6 +2979,7 @@ func checkExpectedServerTunnelLogFields(
 	expectPassthroughAddress *string,
 	expectPassthroughAddress *string,
 	expectMeekHTTPVersion string,
 	expectMeekHTTPVersion string,
 	expectCheckServerEntryPruneCount int,
 	expectCheckServerEntryPruneCount int,
+	expectServerEntryCount int,
 	inproxyTestConfig *inproxyTestConfig,
 	inproxyTestConfig *inproxyTestConfig,
 	fields map[string]interface{}) error {
 	fields map[string]interface{}) error {
 
 
@@ -2979,6 +2991,7 @@ func checkExpectedServerTunnelLogFields(
 
 
 	for _, name := range []string{
 	for _, name := range []string{
 		"host_id",
 		"host_id",
+		"server_entry_tag",
 		"tunnel_id",
 		"tunnel_id",
 		"start_time",
 		"start_time",
 		"duration",
 		"duration",
@@ -3008,6 +3021,7 @@ func checkExpectedServerTunnelLogFields(
 		"bytes",
 		"bytes",
 		"ssh_protocol_bytes",
 		"ssh_protocol_bytes",
 		"ssh_protocol_bytes_overhead",
 		"ssh_protocol_bytes_overhead",
+		"server_entry_count",
 
 
 		// The test run ensures that logServerLoad is invoked while the client
 		// The test run ensures that logServerLoad is invoked while the client
 		// is connected, so the following must be logged.
 		// is connected, so the following must be logged.
@@ -3019,14 +3033,6 @@ func checkExpectedServerTunnelLogFields(
 		}
 		}
 	}
 	}
 
 
-	if !(fields["ssh_protocol_bytes"].(float64) > 0) {
-		return fmt.Errorf("unexpected zero ssh_protocol_bytes")
-	}
-
-	if !(fields["ssh_protocol_bytes"].(float64) > fields["bytes"].(float64)) {
-		return fmt.Errorf("unexpected ssh_protocol_bytes < bytes")
-	}
-
 	appliedTacticsTag := len(fields[tactics.APPLIED_TACTICS_TAG_PARAMETER_NAME].(string)) > 0
 	appliedTacticsTag := len(fields[tactics.APPLIED_TACTICS_TAG_PARAMETER_NAME].(string)) > 0
 	if expectAppliedTacticsTag != appliedTacticsTag {
 	if expectAppliedTacticsTag != appliedTacticsTag {
 		return fmt.Errorf("unexpected applied_tactics_tag")
 		return fmt.Errorf("unexpected applied_tactics_tag")
@@ -3082,6 +3088,19 @@ func checkExpectedServerTunnelLogFields(
 		return fmt.Errorf("unexpected network_type '%s'", fields["network_type"])
 		return fmt.Errorf("unexpected network_type '%s'", fields["network_type"])
 	}
 	}
 
 
+	if !(fields["ssh_protocol_bytes"].(float64) > 0) {
+		return fmt.Errorf("unexpected zero ssh_protocol_bytes")
+	}
+
+	if !(fields["ssh_protocol_bytes"].(float64) > fields["bytes"].(float64)) {
+		return fmt.Errorf("unexpected ssh_protocol_bytes < bytes")
+	}
+
+	if fields["server_entry_count"].(float64) != float64(expectServerEntryCount) {
+		return fmt.Errorf("unexpected server_entry_count: '%d'",
+			int(fields["server_entry_count"].(float64)))
+	}
+
 	// With interruptions, timeouts, and retries in some tests, there may be
 	// With interruptions, timeouts, and retries in some tests, there may be
 	// more than one dangling accepted_client.
 	// more than one dangling accepted_client.
 
 
@@ -4371,7 +4390,8 @@ func paveTacticsConfigFile(
 	discoveryStategy string,
 	discoveryStategy string,
 	enableDSLFetcher string,
 	enableDSLFetcher string,
 	inproxyParametersJSON string,
 	inproxyParametersJSON string,
-	doRestrictAllInproxyProviderRegions bool) {
+	doRestrictAllInproxyProviderRegions bool,
+	providerID string) {
 
 
 	// Setting LimitTunnelProtocols passively exercises the
 	// Setting LimitTunnelProtocols passively exercises the
 	// server-side LimitTunnelProtocols enforcement.
 	// server-side LimitTunnelProtocols enforcement.
@@ -4502,10 +4522,10 @@ func paveTacticsConfigFile(
 
 
 	restrictInproxyParameters := ""
 	restrictInproxyParameters := ""
 	if doRestrictAllInproxyProviderRegions {
 	if doRestrictAllInproxyProviderRegions {
-		restrictInproxyParameters = `
-		"RestrictInproxyProviderRegions": {"" : [""]},
+		restrictInproxyParameters = fmt.Sprintf(`
+		"RestrictInproxyProviderRegions": {"%s" : ["%s"]},
 		"RestrictInproxyProviderIDsServerProbability": 1.0,
 		"RestrictInproxyProviderIDsServerProbability": 1.0,
-	`
+	`, providerID, testServerRegion)
 	}
 	}
 
 
 	tacticsConfigJSON := fmt.Sprintf(
 	tacticsConfigJSON := fmt.Sprintf(

+ 14 - 5
psiphon/server/services.go

@@ -513,6 +513,8 @@ func logServerLoad(
 
 
 	serverLoad["event_name"] = "server_load"
 	serverLoad["event_name"] = "server_load"
 
 
+	support.Config.AddServerEntryTag(serverLoad)
+
 	if logNetworkBytes {
 	if logNetworkBytes {
 
 
 		// Negative values, which may occur due to counter wrap arounds, are
 		// Negative values, which may occur due to counter wrap arounds, are
@@ -580,13 +582,20 @@ func logIrregularTunnel(
 	}
 	}
 
 
 	logFields["event_name"] = "irregular_tunnel"
 	logFields["event_name"] = "irregular_tunnel"
-	logFields["listener_protocol"] = listenerTunnelProtocol
-	logFields["listener_port_number"] = listenerPort
+	support.Config.AddServerEntryTag(logFields)
+
 	logFields["tunnel_error"] = tunnelError.Error()
 	logFields["tunnel_error"] = tunnelError.Error()
 
 
-	// Note: logging with the "client_" prefix for legacy compatibility; it
-	// would be more correct to use the prefix "peer_".
-	support.GeoIPService.Lookup(peerIP).SetClientLogFields(logFields)
+	if listenerTunnelProtocol != "" {
+		logFields["listener_protocol"] = listenerTunnelProtocol
+		logFields["listener_port_number"] = listenerPort
+	}
+
+	if peerIP != "" {
+		// Note: logging with the "client_" prefix for legacy compatibility; it
+		// would be more correct to use the prefix "peer_".
+		support.GeoIPService.Lookup(peerIP).SetClientLogFields(logFields)
+	}
 
 
 	log.LogRawFieldsWithTimestamp(logFields)
 	log.LogRawFieldsWithTimestamp(logFields)
 }
 }

+ 10 - 3
psiphon/server/tunnelServer.go

@@ -3671,6 +3671,8 @@ func (sshClient *sshClient) logTunnel(additionalMetrics []LogFields) {
 		sshClient.handshakeState.apiParams,
 		sshClient.handshakeState.apiParams,
 		serverTunnelStatParams)
 		serverTunnelStatParams)
 
 
+	sshClient.sshServer.support.Config.AddServerEntryTag(logFields)
+
 	logFields["tunnel_id"] = base64.RawURLEncoding.EncodeToString(prng.Bytes(protocol.PSIPHON_API_TUNNEL_ID_LENGTH))
 	logFields["tunnel_id"] = base64.RawURLEncoding.EncodeToString(prng.Bytes(protocol.PSIPHON_API_TUNNEL_ID_LENGTH))
 
 
 	if sshClient.isInproxyTunnelProtocol {
 	if sshClient.isInproxyTunnelProtocol {
@@ -4299,8 +4301,6 @@ func (sshClient *sshClient) setHandshakeState(
 		if ok && sessionID != sshClient.sessionID {
 		if ok && sessionID != sshClient.sessionID {
 
 
 			logFields := LogFields{
 			logFields := LogFields{
-				"event_name":                 "irregular_tunnel",
-				"tunnel_error":               "duplicate active authorization",
 				"duplicate_authorization_id": authorizationID,
 				"duplicate_authorization_id": authorizationID,
 			}
 			}
 
 
@@ -4314,7 +4314,14 @@ func (sshClient *sshClient) setHandshakeState(
 			if duplicateClientGeoIPData != sshClient.getClientGeoIPData() {
 			if duplicateClientGeoIPData != sshClient.getClientGeoIPData() {
 				duplicateClientGeoIPData.SetClientLogFieldsWithPrefix("duplicate_authorization_", logFields)
 				duplicateClientGeoIPData.SetClientLogFieldsWithPrefix("duplicate_authorization_", logFields)
 			}
 			}
-			log.LogRawFieldsWithTimestamp(logFields)
+
+			logIrregularTunnel(
+				sshClient.sshServer.support,
+				"", // tunnel protocol is not relevant to authorizations
+				0,
+				"", // GeoIP data is added above
+				errors.TraceNew("duplicate active authorization"),
+				logFields)
 
 
 			// Invoke asynchronously to avoid deadlocks.
 			// Invoke asynchronously to avoid deadlocks.
 			// TODO: invoke only once for each distinct sessionID?
 			// TODO: invoke only once for each distinct sessionID?

+ 5 - 0
psiphon/serverApi.go

@@ -1427,6 +1427,11 @@ func getBaseAPIParameters(
 			}
 			}
 		}
 		}
 
 
+		serverEntryCount := GetLastServerEntryCount()
+		if serverEntryCount >= 0 {
+			params["server_entry_count"] = strconv.Itoa(serverEntryCount)
+		}
+
 	} else if filter == baseParametersOnlyUpstreamFragmentorDialParameters {
 	} else if filter == baseParametersOnlyUpstreamFragmentorDialParameters {
 
 
 		if dialParams.DialConnMetrics != nil {
 		if dialParams.DialConnMetrics != nil {