|
@@ -57,6 +57,7 @@ type Controller struct {
|
|
|
nextTunnel int
|
|
nextTunnel int
|
|
|
startedConnectedReporter bool
|
|
startedConnectedReporter bool
|
|
|
isEstablishing bool
|
|
isEstablishing bool
|
|
|
|
|
+ establishLimitTunnelProtocolsState *limitTunnelProtocolsState
|
|
|
concurrentEstablishTunnelsMutex sync.Mutex
|
|
concurrentEstablishTunnelsMutex sync.Mutex
|
|
|
concurrentEstablishTunnels int
|
|
concurrentEstablishTunnels int
|
|
|
concurrentIntensiveEstablishTunnels int
|
|
concurrentIntensiveEstablishTunnels int
|
|
@@ -77,13 +78,6 @@ type Controller struct {
|
|
|
packetTunnelTransport *PacketTunnelTransport
|
|
packetTunnelTransport *PacketTunnelTransport
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-type candidateServerEntry struct {
|
|
|
|
|
- serverEntry *protocol.ServerEntry
|
|
|
|
|
- isServerAffinityCandidate bool
|
|
|
|
|
- usePriorityProtocol bool
|
|
|
|
|
- adjustedEstablishStartTime monotime.Time
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
// NewController initializes a new controller.
|
|
// NewController initializes a new controller.
|
|
|
func NewController(config *Config) (controller *Controller, err error) {
|
|
func NewController(config *Config) (controller *Controller, err error) {
|
|
|
|
|
|
|
@@ -970,6 +964,70 @@ func (controller *Controller) DirectDial(remoteAddr string) (conn net.Conn, err
|
|
|
return DialTCP(controller.runCtx, remoteAddr, controller.untunneledDialConfig)
|
|
return DialTCP(controller.runCtx, remoteAddr, controller.untunneledDialConfig)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+type limitTunnelProtocolsState struct {
|
|
|
|
|
+ useUpstreamProxy bool
|
|
|
|
|
+ initialProtocols protocol.TunnelProtocols
|
|
|
|
|
+ initialCandidateCount int
|
|
|
|
|
+ protocols protocol.TunnelProtocols
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (l *limitTunnelProtocolsState) isInitialCandidate(
|
|
|
|
|
+ excludeIntensive bool, serverEntry *protocol.ServerEntry) bool {
|
|
|
|
|
+
|
|
|
|
|
+ return len(l.initialProtocols) > 0 && l.initialCandidateCount > 0 &&
|
|
|
|
|
+ len(serverEntry.GetSupportedProtocols(l.useUpstreamProxy, l.initialProtocols, excludeIntensive)) > 0
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (l *limitTunnelProtocolsState) isCandidate(
|
|
|
|
|
+ excludeIntensive bool, serverEntry *protocol.ServerEntry) bool {
|
|
|
|
|
+
|
|
|
|
|
+ return len(l.protocols) == 0 ||
|
|
|
|
|
+ len(serverEntry.GetSupportedProtocols(l.useUpstreamProxy, l.protocols, excludeIntensive)) > 0
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+var errNoProtocolSupported = errors.New("server does not support any required protocol(s)")
|
|
|
|
|
+
|
|
|
|
|
+func (l *limitTunnelProtocolsState) selectProtocol(
|
|
|
|
|
+ candidateIndex int, excludeIntensive bool, serverEntry *protocol.ServerEntry) (string, error) {
|
|
|
|
|
+
|
|
|
|
|
+ limitProtocols := l.protocols
|
|
|
|
|
+
|
|
|
|
|
+ if len(l.initialProtocols) > 0 && l.initialCandidateCount > candidateIndex {
|
|
|
|
|
+ limitProtocols = l.initialProtocols
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ candidateProtocols := serverEntry.GetSupportedProtocols(
|
|
|
|
|
+ l.useUpstreamProxy,
|
|
|
|
|
+ limitProtocols,
|
|
|
|
|
+ excludeIntensive)
|
|
|
|
|
+
|
|
|
|
|
+ if len(candidateProtocols) == 0 {
|
|
|
|
|
+ return "", errNoProtocolSupported
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Pick at random from the supported protocols. This ensures that we'll
|
|
|
|
|
+ // eventually try all possible protocols. Depending on network
|
|
|
|
|
+ // configuration, it may be the case that some protocol is only available
|
|
|
|
|
+ // through multi-capability servers, and a simpler ranked preference of
|
|
|
|
|
+ // protocols could lead to that protocol never being selected.
|
|
|
|
|
+
|
|
|
|
|
+ index, err := common.MakeSecureRandomInt(len(candidateProtocols))
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return "", common.ContextError(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ selectedProtocol := candidateProtocols[index]
|
|
|
|
|
+
|
|
|
|
|
+ return selectedProtocol, nil
|
|
|
|
|
+
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type candidateServerEntry struct {
|
|
|
|
|
+ serverEntry *protocol.ServerEntry
|
|
|
|
|
+ isServerAffinityCandidate bool
|
|
|
|
|
+ candidateIndex int
|
|
|
|
|
+ adjustedEstablishStartTime monotime.Time
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// startEstablishing creates a pool of worker goroutines which will
|
|
// startEstablishing creates a pool of worker goroutines which will
|
|
|
// attempt to establish tunnels to candidate servers. The candidates
|
|
// attempt to establish tunnels to candidate servers. The candidates
|
|
|
// are generated by another goroutine.
|
|
// are generated by another goroutine.
|
|
@@ -1074,25 +1132,43 @@ func (controller *Controller) launchEstablishing() {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Unconditionally report available egress regions. After a fresh install,
|
|
|
|
|
- // the outer client may not have a list of regions to display, so we
|
|
|
|
|
- // always report here. Other events that trigger ReportAvailableRegions,
|
|
|
|
|
- // are not guaranteed to occur.
|
|
|
|
|
- //
|
|
|
|
|
- // This report is delayed until after tactics are likely to be applied, as
|
|
|
|
|
- // tactics can impact the list of available regions; this avoids a
|
|
|
|
|
- // ReportAvailableRegions reporting too many regions, followed shortly by
|
|
|
|
|
- // a ReportAvailableRegions reporting fewer regions. That sequence could
|
|
|
|
|
- // cause issues in the outer client UI.
|
|
|
|
|
|
|
+ // LimitTunnelProtocols and ConnectionWorkerPoolSize may be set by
|
|
|
|
|
+ // tactics.
|
|
|
|
|
|
|
|
- ReportAvailableRegions(controller.config)
|
|
|
|
|
|
|
+ // Initial- and LimitTunnelProtocols are set once per establishment, for
|
|
|
|
|
+ // consistent application of related probabilities (applied by
|
|
|
|
|
+ // ClientParametersSnapshot.TunnelProtocols). The
|
|
|
|
|
+ // establishLimitTunnelProtocolsState field must be read-only after this
|
|
|
|
|
+ // point, allowing concurrent reads by establishment workers.
|
|
|
|
|
|
|
|
- // The ConnectionWorkerPoolSize may be set by tactics.
|
|
|
|
|
|
|
+ p := controller.config.clientParameters.Get()
|
|
|
|
|
|
|
|
- size := controller.config.clientParameters.Get().Int(
|
|
|
|
|
|
|
+ controller.establishLimitTunnelProtocolsState = &limitTunnelProtocolsState{
|
|
|
|
|
+ useUpstreamProxy: controller.config.UseUpstreamProxy(),
|
|
|
|
|
+ initialProtocols: p.TunnelProtocols(parameters.InitialLimitTunnelProtocols),
|
|
|
|
|
+ initialCandidateCount: p.Int(parameters.InitialLimitTunnelProtocolsCandidateCount),
|
|
|
|
|
+ protocols: p.TunnelProtocols(parameters.LimitTunnelProtocols),
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ workerPoolSize := controller.config.clientParameters.Get().Int(
|
|
|
parameters.ConnectionWorkerPoolSize)
|
|
parameters.ConnectionWorkerPoolSize)
|
|
|
|
|
|
|
|
- for i := 0; i < size; i++ {
|
|
|
|
|
|
|
+ p = nil
|
|
|
|
|
+
|
|
|
|
|
+ // Report available egress regions. After a fresh install, the outer
|
|
|
|
|
+ // client may not have a list of regions to display; and
|
|
|
|
|
+ // LimitTunnelProtocols may reduce the number of available regions.
|
|
|
|
|
+ //
|
|
|
|
|
+ // This report is delayed until after tactics are likely to be applied;
|
|
|
|
|
+ // this avoids a ReportAvailableRegions reporting too many regions,
|
|
|
|
|
+ // followed shortly by a ReportAvailableRegions reporting fewer regions.
|
|
|
|
|
+ // That sequence could cause issues in the outer client UI.
|
|
|
|
|
+
|
|
|
|
|
+ ReportAvailableRegions(
|
|
|
|
|
+ controller.config,
|
|
|
|
|
+ controller.establishLimitTunnelProtocolsState)
|
|
|
|
|
+
|
|
|
|
|
+ for i := 0; i < workerPoolSize; i++ {
|
|
|
controller.establishWaitGroup.Add(1)
|
|
controller.establishWaitGroup.Add(1)
|
|
|
go controller.establishTunnelWorker()
|
|
go controller.establishTunnelWorker()
|
|
|
}
|
|
}
|
|
@@ -1370,11 +1446,11 @@ func (controller *Controller) establishCandidateGenerator() {
|
|
|
close(controller.serverAffinityDoneBroadcast)
|
|
close(controller.serverAffinityDoneBroadcast)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- candidateCount := 0
|
|
|
|
|
|
|
+ candidateIndex := 0
|
|
|
|
|
|
|
|
loop:
|
|
loop:
|
|
|
// Repeat until stopped
|
|
// Repeat until stopped
|
|
|
- for i := 0; ; i++ {
|
|
|
|
|
|
|
+ for {
|
|
|
|
|
|
|
|
networkWaitStartTime := monotime.Now()
|
|
networkWaitStartTime := monotime.Now()
|
|
|
|
|
|
|
@@ -1386,6 +1462,22 @@ loop:
|
|
|
|
|
|
|
|
networkWaitDuration += monotime.Since(networkWaitStartTime)
|
|
networkWaitDuration += monotime.Since(networkWaitStartTime)
|
|
|
|
|
|
|
|
|
|
+ // For diagnostics, emits counts of the number of known server
|
|
|
|
|
+ // entries that satisfy both the egress region and tunnel protocol
|
|
|
|
|
+ // requirements (excluding excludeIntensive logic).
|
|
|
|
|
+ // Counts may change during establishment due to remote server
|
|
|
|
|
+ // list fetches, etc.
|
|
|
|
|
+
|
|
|
|
|
+ initialCount, count := CountServerEntriesWithLimits(
|
|
|
|
|
+ controller.config.UseUpstreamProxy(),
|
|
|
|
|
+ controller.config.EgressRegion,
|
|
|
|
|
+ controller.establishLimitTunnelProtocolsState)
|
|
|
|
|
+ NoticeCandidateServers(
|
|
|
|
|
+ controller.config.EgressRegion,
|
|
|
|
|
+ controller.establishLimitTunnelProtocolsState,
|
|
|
|
|
+ initialCount,
|
|
|
|
|
+ count)
|
|
|
|
|
+
|
|
|
// Send each iterator server entry to the establish workers
|
|
// Send each iterator server entry to the establish workers
|
|
|
startTime := monotime.Now()
|
|
startTime := monotime.Now()
|
|
|
for {
|
|
for {
|
|
@@ -1405,15 +1497,6 @@ loop:
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Use a prioritized tunnel protocol for the first
|
|
|
|
|
- // PrioritizeTunnelProtocolsCandidateCount candidates.
|
|
|
|
|
- // This facility can be used to favor otherwise slower
|
|
|
|
|
- // protocols.
|
|
|
|
|
-
|
|
|
|
|
- prioritizeCandidateCount := controller.config.clientParameters.Get().Int(
|
|
|
|
|
- parameters.PrioritizeTunnelProtocolsCandidateCount)
|
|
|
|
|
- usePriorityProtocol := candidateCount < prioritizeCandidateCount
|
|
|
|
|
-
|
|
|
|
|
// adjustedEstablishStartTime is establishStartTime shifted
|
|
// adjustedEstablishStartTime is establishStartTime shifted
|
|
|
// to exclude time spent waiting for network connectivity.
|
|
// to exclude time spent waiting for network connectivity.
|
|
|
|
|
|
|
@@ -1422,7 +1505,7 @@ loop:
|
|
|
candidate := &candidateServerEntry{
|
|
candidate := &candidateServerEntry{
|
|
|
serverEntry: serverEntry,
|
|
serverEntry: serverEntry,
|
|
|
isServerAffinityCandidate: isServerAffinityCandidate,
|
|
isServerAffinityCandidate: isServerAffinityCandidate,
|
|
|
- usePriorityProtocol: usePriorityProtocol,
|
|
|
|
|
|
|
+ candidateIndex: candidateIndex,
|
|
|
adjustedEstablishStartTime: adjustedEstablishStartTime,
|
|
adjustedEstablishStartTime: adjustedEstablishStartTime,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1435,7 +1518,7 @@ loop:
|
|
|
// TODO: here we could generate multiple candidates from the
|
|
// TODO: here we could generate multiple candidates from the
|
|
|
// server entry when there are many MeekFrontingAddresses.
|
|
// server entry when there are many MeekFrontingAddresses.
|
|
|
|
|
|
|
|
- candidateCount++
|
|
|
|
|
|
|
+ candidateIndex++
|
|
|
|
|
|
|
|
select {
|
|
select {
|
|
|
case controller.candidateServerEntries <- candidate:
|
|
case controller.candidateServerEntries <- candidate:
|
|
@@ -1601,11 +1684,10 @@ loop:
|
|
|
}
|
|
}
|
|
|
controller.concurrentEstablishTunnelsMutex.Unlock()
|
|
controller.concurrentEstablishTunnelsMutex.Unlock()
|
|
|
|
|
|
|
|
- selectedProtocol, err := selectProtocol(
|
|
|
|
|
- controller.config,
|
|
|
|
|
- candidateServerEntry.serverEntry,
|
|
|
|
|
|
|
+ selectedProtocol, err := controller.establishLimitTunnelProtocolsState.selectProtocol(
|
|
|
|
|
+ candidateServerEntry.candidateIndex,
|
|
|
excludeIntensive,
|
|
excludeIntensive,
|
|
|
- candidateServerEntry.usePriorityProtocol)
|
|
|
|
|
|
|
+ candidateServerEntry.serverEntry)
|
|
|
|
|
|
|
|
if err == errNoProtocolSupported {
|
|
if err == errNoProtocolSupported {
|
|
|
// selectProtocol returns errNoProtocolSupported when the server
|
|
// selectProtocol returns errNoProtocolSupported when the server
|