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

Add more memory management changes

- Change server affinity logic: instead of starting
  all candidates and allowing more time for affinity
  candidate to succeed, start only the affinity at
  first

- More explicit garbage collection when tunnels are
  failed and discarded

- Do garbage collection and memory metrics logging
  unconditionally; remove LimitedMemoryEnvironment
  config flag

- Add limit on number of concurrent meek workers

- Wrap gomobile cgo go calls in a mutex to prevent
  excessive OS threads from spawning

- Remove ineffective LimitedMemorySingleConnectionWorkerThreshold
  option

- Set SO_NOSIGPIPE on sockets created in tcpDial
Rod Hynes 8 лет назад
Родитель
Сommit
b49746a5e2

+ 53 - 0
MobileLibrary/psi/psi.go

@@ -62,6 +62,14 @@ func Start(
 		return fmt.Errorf("already started")
 	}
 
+	// Wrap the provider in a layer that locks a mutex before calling a provider function.
+	// The the provider callbacks are Java/Obj-C via gomobile, they are cgo calls that
+	// can cause OS threads to be spawned. The mutex prevents many calling goroutines from
+	// causing unbounded numbers of OS threads to be spawned.
+	// TODO: replace the mutex with a semaphore, to allow a larger but still bounded concurrent
+	// number of calls to the provider?
+	provider = newMutexPsiphonProvider(provider)
+
 	config, err := psiphon.LoadConfig([]byte(configJson))
 	if err != nil {
 		return fmt.Errorf("error loading configuration file: %s", err)
@@ -198,3 +206,48 @@ func storeServerEntries(embeddedServerEntryListPath, embeddedServerEntryList str
 
 	return nil
 }
+
+type mutexPsiphonProvider struct {
+	sync.Mutex
+	p PsiphonProvider
+}
+
+func newMutexPsiphonProvider(p PsiphonProvider) *mutexPsiphonProvider {
+	return &mutexPsiphonProvider{p: p}
+}
+
+func (p *mutexPsiphonProvider) Notice(noticeJSON string) {
+	p.Lock()
+	defer p.Unlock()
+	p.p.Notice(noticeJSON)
+}
+
+func (p *mutexPsiphonProvider) HasNetworkConnectivity() int {
+	p.Lock()
+	defer p.Unlock()
+	return p.p.HasNetworkConnectivity()
+}
+
+func (p *mutexPsiphonProvider) BindToDevice(fileDescriptor int) error {
+	p.Lock()
+	defer p.Unlock()
+	return p.p.BindToDevice(fileDescriptor)
+}
+
+func (p *mutexPsiphonProvider) IPv6Synthesize(IPv4Addr string) string {
+	p.Lock()
+	defer p.Unlock()
+	return p.p.IPv6Synthesize(IPv4Addr)
+}
+
+func (p *mutexPsiphonProvider) GetPrimaryDnsServer() string {
+	p.Lock()
+	defer p.Unlock()
+	return p.p.GetPrimaryDnsServer()
+}
+
+func (p *mutexPsiphonProvider) GetSecondaryDnsServer() string {
+	p.Lock()
+	defer p.Unlock()
+	return p.p.GetSecondaryDnsServer()
+}

+ 2 - 0
psiphon/TCPConn_bind.go

@@ -124,6 +124,8 @@ func tcpDial(addr string, config *DialConfig) (net.Conn, error) {
 			continue
 		}
 
+		syscall.SetsockoptInt(socketFd, syscall.SOL_SOCKET, syscall.SO_NOSIGPIPE, 1)
+
 		if config.DeviceBinder != nil {
 			// WARNING: this potentially violates the direction to not call into
 			// external components after the Controller may have been stopped.

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

@@ -84,6 +84,11 @@ func TunnelProtocolUsesObfuscatedSSH(protocol string) bool {
 	return protocol != TUNNEL_PROTOCOL_SSH
 }
 
+func TunnelProtocolUsesMeek(protocol string) bool {
+	return TunnelProtocolUsesMeekHTTP(protocol) ||
+		TunnelProtocolUsesMeekHTTPS(protocol)
+}
+
 func TunnelProtocolUsesMeekHTTP(protocol string) bool {
 	return protocol == TUNNEL_PROTOCOL_UNFRONTED_MEEK ||
 		protocol == TUNNEL_PROTOCOL_FRONTED_MEEK_HTTP

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

@@ -82,9 +82,12 @@ func (serverEntry *ServerEntry) SupportsProtocol(protocol string) bool {
 
 // GetSupportedProtocols returns a list of tunnel protocols supported
 // by the ServerEntry's capabilities.
-func (serverEntry *ServerEntry) GetSupportedProtocols() []string {
+func (serverEntry *ServerEntry) GetSupportedProtocols(excludeMeek bool) []string {
 	supportedProtocols := make([]string, 0)
 	for _, protocol := range SupportedTunnelProtocols {
+		if excludeMeek && TunnelProtocolUsesMeek(protocol) {
+			continue
+		}
 		if serverEntry.SupportsProtocol(protocol) {
 			supportedProtocols = append(supportedProtocols, protocol)
 		}

+ 12 - 17
psiphon/config.go

@@ -476,23 +476,18 @@ type Config struct {
 	// When PacketTunnelTunDeviceFileDescriptor is set, TunnelPoolSize must be 1.
 	PacketTunnelTunFileDescriptor int
 
-	// LimitedMemoryEnvironment enables memory usage metrics logging, to track
-	// memory usage, and selective aggressively garbage collection at high memory
-	// pressure phases of operation.
-	LimitedMemoryEnvironment bool
-
-	// LimitedMemorySingleConnectionWorkerThreshold limits the number of concurrent
-	// connection workers to 1 when the total memory allocation exceeds the specified
-	// value.
-	// This option is enabled when LimitedMemoryEnvironment is true and when
-	// LimitedMemorySingleConnectionWorkerThreshold > 0.
-	LimitedMemorySingleConnectionWorkerThreshold int
-
-	// LimitedMemoryStaggerConnectionWorkersMilliseconds adds a specified delay
-	// before making each server candidate available to connection workers.
-	// This option is enabled when LimitedMemoryEnvironment is true and when
-	// LimitedMemorySingleConnectionWorkersThreshold > 0.
-	LimitedMemoryStaggerConnectionWorkersMilliseconds int
+	// StaggerConnectionWorkersMilliseconds adds a specified delay before making each
+	// server candidate available to connection workers. This option is enabled when
+	// StaggerConnectionWorkersMilliseconds > 0.
+	StaggerConnectionWorkersMilliseconds int
+
+	// LimitMeekConnectionWorkers limits the number of concurrent connection workers
+	// attempting connections with meek protocols.
+	// This option is enabled when LimitMeekConnectionWorkers > 0.
+	LimitMeekConnectionWorkers int
+
+	// LimitMeekBufferSizes selects smaller buffers for meek protocols.
+	LimitMeekBufferSizes bool
 
 	// IgnoreHandshakeStatsRegexps skips compiling and using stats regexes.
 	IgnoreHandshakeStatsRegexps bool

+ 173 - 103
psiphon/controller.go

@@ -41,38 +41,40 @@ import (
 // connect to; establishes and monitors tunnels; and runs local proxies which
 // route traffic through the tunnels.
 type Controller struct {
-	config                            *Config
-	sessionId                         string
-	componentFailureSignal            chan struct{}
-	shutdownBroadcast                 chan struct{}
-	runWaitGroup                      *sync.WaitGroup
-	establishedTunnels                chan *Tunnel
-	failedTunnels                     chan *Tunnel
-	tunnelMutex                       sync.Mutex
-	establishedOnce                   bool
-	tunnels                           []*Tunnel
-	nextTunnel                        int
-	startedConnectedReporter          bool
-	isEstablishing                    bool
-	concurrentEstablishTunnelsMutex   sync.Mutex
-	concurrentEstablishTunnels        int32
-	peakConcurrentEstablishTunnels    int32
-	establishWaitGroup                *sync.WaitGroup
-	stopEstablishingBroadcast         chan struct{}
-	candidateServerEntries            chan *candidateServerEntry
-	establishPendingConns             *common.Conns
-	untunneledPendingConns            *common.Conns
-	untunneledDialConfig              *DialConfig
-	splitTunnelClassifier             *SplitTunnelClassifier
-	signalFetchCommonRemoteServerList chan struct{}
-	signalFetchObfuscatedServerLists  chan struct{}
-	signalDownloadUpgrade             chan string
-	impairedProtocolClassification    map[string]int
-	signalReportConnected             chan struct{}
-	serverAffinityDoneBroadcast       chan struct{}
-	newClientVerificationPayload      chan string
-	packetTunnelClient                *tun.Client
-	packetTunnelTransport             *PacketTunnelTransport
+	config                             *Config
+	sessionId                          string
+	componentFailureSignal             chan struct{}
+	shutdownBroadcast                  chan struct{}
+	runWaitGroup                       *sync.WaitGroup
+	establishedTunnels                 chan *Tunnel
+	failedTunnels                      chan *Tunnel
+	tunnelMutex                        sync.Mutex
+	establishedOnce                    bool
+	tunnels                            []*Tunnel
+	nextTunnel                         int
+	startedConnectedReporter           bool
+	isEstablishing                     bool
+	concurrentEstablishTunnelsMutex    sync.Mutex
+	concurrentEstablishTunnels         int
+	concurrentMeekEstablishTunnels     int
+	peakConcurrentEstablishTunnels     int
+	peakConcurrentMeekEstablishTunnels int
+	establishWaitGroup                 *sync.WaitGroup
+	stopEstablishingBroadcast          chan struct{}
+	candidateServerEntries             chan *candidateServerEntry
+	establishPendingConns              *common.Conns
+	untunneledPendingConns             *common.Conns
+	untunneledDialConfig               *DialConfig
+	splitTunnelClassifier              *SplitTunnelClassifier
+	signalFetchCommonRemoteServerList  chan struct{}
+	signalFetchObfuscatedServerLists   chan struct{}
+	signalDownloadUpgrade              chan string
+	impairedProtocolClassification     map[string]int
+	signalReportConnected              chan struct{}
+	serverAffinityDoneBroadcast        chan struct{}
+	newClientVerificationPayload       chan string
+	packetTunnelClient                 *tun.Client
+	packetTunnelTransport              *PacketTunnelTransport
 }
 
 type candidateServerEntry struct {
@@ -629,6 +631,10 @@ loop:
 
 			controller.classifyImpairedProtocol(failedTunnel)
 
+			// Clear the reference to this tunnel before calling startEstablishing,
+			// which will invoke a garbage collection.
+			failedTunnel = nil
+
 			// Concurrency note: only this goroutine may call startEstablishing/stopEstablishing
 			// and access isEstablishing.
 			if !controller.isEstablishing {
@@ -654,6 +660,12 @@ loop:
 			if !registered {
 				// Already fully established, so discard.
 				controller.discardTunnel(establishedTunnel)
+
+				// Clear the reference to this discarded tunnel and immediately run
+				// a garbage collection to reclaim its memory.
+				establishedTunnel = nil
+				aggressiveGarbageCollection()
+
 				break
 			}
 
@@ -1022,35 +1034,13 @@ func (controller *Controller) startEstablishing() {
 
 	controller.concurrentEstablishTunnelsMutex.Lock()
 	controller.concurrentEstablishTunnels = 0
+	controller.concurrentMeekEstablishTunnels = 0
 	controller.peakConcurrentEstablishTunnels = 0
+	controller.peakConcurrentMeekEstablishTunnels = 0
 	controller.concurrentEstablishTunnelsMutex.Unlock()
 
-	workerCount := controller.config.ConnectionWorkerPoolSize
-
-	if controller.config.LimitedMemoryEnvironment {
-		aggressiveGarbageCollection()
-		totalMemory := emitMemoryMetrics()
-
-		// When total memory size exceeds the threshold, minimize
-		// the number of concurrent connection workers.
-		//
-		// Limitations:
-		// - totalMemory is, at this time, runtime.MemStats.Sys,
-		//   which is virtual memory, not RSS; and which may not
-		//   shrink; so this trigger could be premature and
-		//   permanent.
-		// - Only 1 concurrent worker means a candidate that is
-		//   slow to fail will severely delay the establishment;
-		//   and that it may take significant time to cycle through
-		//   all protocols to find one that works when network
-		//   conditions change.
-
-		if controller.config.LimitedMemorySingleConnectionWorkerThreshold > 0 &&
-			totalMemory >= uint64(controller.config.LimitedMemorySingleConnectionWorkerThreshold) {
-
-			workerCount = 1
-		}
-	}
+	aggressiveGarbageCollection()
+	emitMemoryMetrics()
 
 	controller.isEstablishing = true
 	controller.establishWaitGroup = new(sync.WaitGroup)
@@ -1085,7 +1075,7 @@ func (controller *Controller) startEstablishing() {
 	// TODO: should not favor the first server in this case
 	controller.serverAffinityDoneBroadcast = make(chan struct{})
 
-	for i := 0; i < workerCount; i++ {
+	for i := 0; i < controller.config.ConnectionWorkerPoolSize; i++ {
 		controller.establishWaitGroup.Add(1)
 		go controller.establishTunnelWorker()
 	}
@@ -1119,15 +1109,17 @@ func (controller *Controller) stopEstablishing() {
 
 	controller.concurrentEstablishTunnelsMutex.Lock()
 	peakConcurrent := controller.peakConcurrentEstablishTunnels
+	peakConcurrentMeek := controller.peakConcurrentMeekEstablishTunnels
 	controller.concurrentEstablishTunnels = 0
+	controller.concurrentMeekEstablishTunnels = 0
 	controller.peakConcurrentEstablishTunnels = 0
+	controller.peakConcurrentMeekEstablishTunnels = 0
 	controller.concurrentEstablishTunnelsMutex.Unlock()
 	NoticeInfo("peak concurrent establish tunnels: %d", peakConcurrent)
+	NoticeInfo("peak concurrent meek establish tunnels: %d", peakConcurrentMeek)
 
-	if controller.config.LimitedMemoryEnvironment {
-		emitMemoryMetrics()
-		standardGarbageCollection()
-	}
+	emitMemoryMetrics()
+	standardGarbageCollection()
 }
 
 // establishCandidateGenerator populates the candidate queue with server entries
@@ -1206,7 +1198,7 @@ loop:
 			// stored or reused.
 			if i == 0 {
 				serverEntry.DisableImpairedProtocols(impairedProtocols)
-				if len(serverEntry.GetSupportedProtocols()) == 0 {
+				if len(serverEntry.GetSupportedProtocols(false)) == 0 {
 					// Skip this server entry, as it has no supported
 					// protocols after disabling the impaired ones
 					// TODO: modify ServerEntryIterator to skip these?
@@ -1223,6 +1215,8 @@ loop:
 				adjustedEstablishStartTime: establishStartTime.Add(networkWaitDuration),
 			}
 
+			wasServerAffinityCandidate := isServerAffinityCandidate
+
 			// Note: there must be only one server affinity candidate, as it
 			// closes the serverAffinityDoneBroadcast channel.
 			isServerAffinityCandidate = false
@@ -1244,12 +1238,27 @@ loop:
 				break
 			}
 
-			if controller.config.LimitedMemoryEnvironment &&
-				controller.config.LimitedMemoryStaggerConnectionWorkersMilliseconds != 0 {
+			if wasServerAffinityCandidate {
+
+				// Don't start the next candidate until either the server affinity
+				// candidate has completed (success or failure) or is still working
+				// and the grace period has elapsed.
+
+				timer := time.NewTimer(ESTABLISH_TUNNEL_SERVER_AFFINITY_GRACE_PERIOD)
+				select {
+				case <-timer.C:
+				case <-controller.serverAffinityDoneBroadcast:
+				case <-controller.stopEstablishingBroadcast:
+					break loop
+				case <-controller.shutdownBroadcast:
+					break loop
+				}
+			} else if controller.config.StaggerConnectionWorkersMilliseconds != 0 {
+
+				// Stagger concurrent connection workers.
 
-				timer := time.NewTimer(
-					time.Duration(
-						controller.config.LimitedMemoryStaggerConnectionWorkersMilliseconds) * time.Millisecond)
+				timer := time.NewTimer(time.Millisecond * time.Duration(
+					controller.config.StaggerConnectionWorkersMilliseconds))
 				select {
 				case <-timer.C:
 				case <-controller.stopEstablishingBroadcast:
@@ -1332,39 +1341,104 @@ loop:
 
 		// EstablishTunnel will allocate significant memory, so first attempt to
 		// reclaim as much as possible.
-		if controller.config.LimitedMemoryEnvironment && !controller.isStopEstablishingBroadcast() {
-			emitMemoryMetrics()
-			aggressiveGarbageCollection()
-		}
+		aggressiveGarbageCollection()
+
+		// Select the tunnel protocol. Unless config.TunnelProtocol is set, the
+		// selection will be made at random from protocols supported by the
+		// server entry.
+		//
+		// When limiting concurrent meek connection workers, and at the limit,
+		// do not select meek since otherwise the candidate must be skipped.
+		//
+		// If at the limit and unabled to select a non-meek protocol, skip the
+		// candidate entirely and move on to the next. Since candidates are shuffled
+		// it's probable that the next candidate is not meek. In this case, a
+		// StaggerConnectionWorkersMilliseconds delay may still be incurred.
 
+		excludeMeek := false
 		controller.concurrentEstablishTunnelsMutex.Lock()
-		controller.concurrentEstablishTunnels += 1
-		if controller.concurrentEstablishTunnels > controller.peakConcurrentEstablishTunnels {
-			controller.peakConcurrentEstablishTunnels = controller.concurrentEstablishTunnels
+		if controller.config.LimitMeekConnectionWorkers > 0 &&
+			controller.concurrentMeekEstablishTunnels >=
+				controller.config.LimitMeekConnectionWorkers {
+			excludeMeek = true
 		}
 		controller.concurrentEstablishTunnelsMutex.Unlock()
 
-		tunnel, err := EstablishTunnel(
-			controller.config,
-			controller.untunneledDialConfig,
-			controller.sessionId,
-			controller.establishPendingConns,
-			candidateServerEntry.serverEntry,
-			candidateServerEntry.adjustedEstablishStartTime,
-			controller) // TunnelOwner
+		selectedProtocol, err := selectProtocol(
+			controller.config, candidateServerEntry.serverEntry, excludeMeek)
 
-		controller.concurrentEstablishTunnelsMutex.Lock()
-		controller.concurrentEstablishTunnels -= 1
-		controller.concurrentEstablishTunnelsMutex.Unlock()
+		if err == errProtocolNotSupported {
+			// selectProtocol returns errProtocolNotSupported when excludeMeek
+			// is set and the server entry only supports meek protocols.
+			// Skip this candidate.
+			continue
+		}
 
-		if err != nil {
+		var tunnel *Tunnel
+		if err == nil {
 
-			// Immediately reclaim memory allocated by the failed establishment.
-			if controller.config.LimitedMemoryEnvironment && !controller.isStopEstablishingBroadcast() {
-				tunnel = nil
-				emitMemoryMetrics()
-				aggressiveGarbageCollection()
+			isMeek := protocol.TunnelProtocolUsesMeek(selectedProtocol) ||
+				protocol.TunnelProtocolUsesMeek(selectedProtocol)
+
+			controller.concurrentEstablishTunnelsMutex.Lock()
+			if isMeek {
+
+				// Recheck the limit now that we know we're selecting meek and
+				// adjusting concurrentMeekEstablishTunnels.
+				if controller.config.LimitMeekConnectionWorkers > 0 &&
+					controller.concurrentMeekEstablishTunnels >=
+						controller.config.LimitMeekConnectionWorkers {
+
+					// Skip this candidate.
+					controller.concurrentEstablishTunnelsMutex.Unlock()
+					continue
+				}
+				controller.concurrentMeekEstablishTunnels += 1
+				if controller.concurrentMeekEstablishTunnels > controller.peakConcurrentMeekEstablishTunnels {
+					controller.peakConcurrentMeekEstablishTunnels = controller.concurrentMeekEstablishTunnels
+				}
+			}
+			controller.concurrentEstablishTunnels += 1
+			if controller.concurrentEstablishTunnels > controller.peakConcurrentEstablishTunnels {
+				controller.peakConcurrentEstablishTunnels = controller.concurrentEstablishTunnels
+			}
+			controller.concurrentEstablishTunnelsMutex.Unlock()
+
+			tunnel, err = EstablishTunnel(
+				controller.config,
+				controller.untunneledDialConfig,
+				controller.sessionId,
+				controller.establishPendingConns,
+				candidateServerEntry.serverEntry,
+				selectedProtocol,
+				candidateServerEntry.adjustedEstablishStartTime,
+				controller) // TunnelOwner
+
+			controller.concurrentEstablishTunnelsMutex.Lock()
+			if isMeek {
+				controller.concurrentMeekEstablishTunnels -= 1
 			}
+			controller.concurrentEstablishTunnels -= 1
+			controller.concurrentEstablishTunnelsMutex.Unlock()
+		}
+
+		// Periodically emit memory metrics during the establishment cycle.
+		if !controller.isStopEstablishingBroadcast() {
+			emitMemoryMetrics()
+		}
+
+		// Immediately reclaim memory allocated by the establishment. In the case
+		// of failure, first clear the reference to the tunnel. In the case of
+		// success, the garbage collection may still be effective as the initial
+		// phases of some protocols involve significant memory allocation that
+		// could now be reclaimed.
+		if err != nil {
+			tunnel = nil
+		}
+
+		aggressiveGarbageCollection()
+
+		if err != nil {
 
 			// Unblock other candidates immediately when
 			// server affinity candidate fails.
@@ -1377,20 +1451,11 @@ loop:
 			if controller.isStopEstablishingBroadcast() {
 				break loop
 			}
+
 			NoticeInfo("failed to connect to %s: %s", candidateServerEntry.serverEntry.IpAddress, err)
 			continue
 		}
 
-		// Block for server affinity grace period before delivering.
-		if !candidateServerEntry.isServerAffinityCandidate {
-			timer := time.NewTimer(ESTABLISH_TUNNEL_SERVER_AFFINITY_GRACE_PERIOD)
-			select {
-			case <-timer.C:
-			case <-controller.serverAffinityDoneBroadcast:
-			case <-controller.stopEstablishingBroadcast:
-			}
-		}
-
 		// Deliver established tunnel.
 		// Don't block. Assumes the receiver has a buffer large enough for
 		// the number of desired tunnels. If there's no room, the tunnel must
@@ -1399,6 +1464,11 @@ loop:
 		case controller.establishedTunnels <- tunnel:
 		default:
 			controller.discardTunnel(tunnel)
+
+			// Clear the reference to this discarded tunnel and immediately run
+			// a garbage collection to reclaim its memory.
+			tunnel = nil
+			aggressiveGarbageCollection()
 		}
 
 		// Unblock other candidates only after delivering when

+ 4 - 4
psiphon/meekConn.go

@@ -71,9 +71,9 @@ const (
 // MeekConfig specifies the behavior of a MeekConn
 type MeekConfig struct {
 
-	// LimitedMemoryEnvironment indicates whether to use smaller
-	// buffers to conserve memory.
-	LimitedMemoryEnvironment bool
+	// LimitBufferSizes indicates whether to use smaller buffers to
+	// conserve memory.
+	LimitBufferSizes bool
 
 	// DialAddress is the actual network address to dial to establish a
 	// connection to the meek server. This may be either a fronted or
@@ -357,7 +357,7 @@ func DialMeek(
 	meek.emptySendBuffer <- new(bytes.Buffer)
 	meek.relayWaitGroup.Add(1)
 
-	if meekConfig.LimitedMemoryEnvironment {
+	if meekConfig.LimitBufferSizes {
 		meek.fullReceiveBufferLength = LIMITED_FULL_RECEIVE_BUFFER_LENGTH
 		meek.readPayloadChunkLength = LIMITED_READ_PAYLOAD_CHUNK_LENGTH
 	}

+ 14 - 9
psiphon/tunnel.go

@@ -127,12 +127,12 @@ func EstablishTunnel(
 	sessionId string,
 	pendingConns *common.Conns,
 	serverEntry *protocol.ServerEntry,
+	selectedProtocol string,
 	adjustedEstablishStartTime monotime.Time,
 	tunnelOwner TunnelOwner) (tunnel *Tunnel, err error) {
 
-	selectedProtocol, err := selectProtocol(config, serverEntry)
-	if err != nil {
-		return nil, common.ContextError(err)
+	if !serverEntry.SupportsProtocol(selectedProtocol) {
+		return nil, common.ContextError(fmt.Errorf("server does not support selected protocol"))
 	}
 
 	// Build transport layers and establish SSH connection. Note that
@@ -416,15 +416,20 @@ func (conn *TunneledConn) Close() error {
 	return conn.Conn.Close()
 }
 
+var errProtocolNotSupported = errors.New("server does not support required protocol(s)")
+
 // selectProtocol is a helper that picks the tunnel protocol
 func selectProtocol(
-	config *Config, serverEntry *protocol.ServerEntry) (selectedProtocol string, err error) {
+	config *Config,
+	serverEntry *protocol.ServerEntry,
+	excludeMeek bool) (selectedProtocol string, err error) {
 
 	// TODO: properly handle protocols (e.g. FRONTED-MEEK-OSSH) vs. capabilities (e.g., {FRONTED-MEEK, OSSH})
 	// for now, the code is simply assuming that MEEK capabilities imply OSSH capability.
 	if config.TunnelProtocol != "" {
-		if !serverEntry.SupportsProtocol(config.TunnelProtocol) {
-			return "", common.ContextError(fmt.Errorf("server does not have required capability"))
+		if !serverEntry.SupportsProtocol(config.TunnelProtocol) ||
+			(excludeMeek && protocol.TunnelProtocolUsesMeek(config.TunnelProtocol)) {
+			return "", errProtocolNotSupported
 		}
 		selectedProtocol = config.TunnelProtocol
 	} else {
@@ -434,9 +439,9 @@ func selectProtocol(
 		// and a simpler ranked preference of protocols could lead to that protocol never
 		// being selected.
 
-		candidateProtocols := serverEntry.GetSupportedProtocols()
+		candidateProtocols := serverEntry.GetSupportedProtocols(excludeMeek)
 		if len(candidateProtocols) == 0 {
-			return "", common.ContextError(fmt.Errorf("server does not have any supported capabilities"))
+			return "", errProtocolNotSupported
 		}
 
 		index, err := common.MakeSecureRandomInt(len(candidateProtocols))
@@ -595,7 +600,7 @@ func initMeekConfig(
 		config.TrustedCACertificatesFilename != "")
 
 	return &MeekConfig{
-		LimitedMemoryEnvironment:      config.LimitedMemoryEnvironment,
+		LimitBufferSizes:              config.LimitMeekBufferSizes,
 		DialAddress:                   dialAddress,
 		UseHTTPS:                      useHTTPS,
 		TLSProfile:                    selectedTLSProfile,

+ 1 - 2
psiphon/utils.go

@@ -200,7 +200,7 @@ func byteCountFormatter(bytes uint64) string {
 		"%.1f%c", float64(bytes)/math.Pow(float64(base), float64(exp)), "KMGTPEZ"[exp-1])
 }
 
-func emitMemoryMetrics() uint64 {
+func emitMemoryMetrics() {
 	var memStats runtime.MemStats
 	runtime.ReadMemStats(&memStats)
 	NoticeInfo("Memory metrics at %s: goroutines %d | total alloc %s | sys %s | heap alloc/sys/idle/inuse/released/objects %s/%s/%s/%s/%s/%d | stack inuse/sys %s/%s | mspan inuse/sys %s/%s | mcached inuse/sys %s/%s | buckhash/gc/other sys %s/%s/%s | nextgc %s",
@@ -224,7 +224,6 @@ func emitMemoryMetrics() uint64 {
 		byteCountFormatter(memStats.GCSys),
 		byteCountFormatter(memStats.OtherSys),
 		byteCountFormatter(memStats.NextGC))
-	return memStats.Sys
 }
 
 func aggressiveGarbageCollection() {