Bläddra i källkod

Fix staticcheck issues

- Issues within internal forks remain are untouched

- Fixes web API support for client verification; see 9a0fc18b

- Add additional config parameters to setDialParametersHash,
  which will invalidate existing replay values
Rod Hynes 6 år sedan
förälder
incheckning
dfc850abda
60 ändrade filer med 305 tillägg och 281 borttagningar
  1. 3 3
      ClientLibrary/PsiphonTunnel.go
  2. 2 1
      ConsoleClient/main.go
  3. 5 7
      Server/logging/analysis/analysis.go
  4. 3 3
      psiphon/common/accesscontrol/accesscontrol_test.go
  5. 3 3
      psiphon/common/authPackage.go
  6. 3 3
      psiphon/common/marionette/marionette_test.go
  7. 1 1
      psiphon/common/net_test.go
  8. 1 1
      psiphon/common/osl/osl_test.go
  9. 5 4
      psiphon/common/prng/prng_test.go
  10. 0 4
      psiphon/common/profiles_test.go
  11. 1 1
      psiphon/common/protocol/customTLSProfiles_test.go
  12. 2 2
      psiphon/common/protocol/serverEntry.go
  13. 2 5
      psiphon/common/quic/quic.go
  14. 1 1
      psiphon/common/quic/quic_test.go
  15. 3 3
      psiphon/common/reloader_test.go
  16. 64 66
      psiphon/common/tactics/tactics.go
  17. 4 0
      psiphon/common/tactics/tactics_test.go
  18. 11 6
      psiphon/common/tun/nonblock_test.go
  19. 2 2
      psiphon/common/tun/tun.go
  20. 2 2
      psiphon/common/tun/tun_darwin.go
  21. 2 2
      psiphon/common/tun/tun_test.go
  22. 12 12
      psiphon/common/tun/tun_unsupported.go
  23. 1 1
      psiphon/common/tun/utils.go
  24. 1 1
      psiphon/common/utils_test.go
  25. 41 20
      psiphon/config.go
  26. 6 6
      psiphon/controller.go
  27. 4 4
      psiphon/dataStore.go
  28. 4 6
      psiphon/dialParameters.go
  29. 1 1
      psiphon/dialParameters_test.go
  30. 14 16
      psiphon/httpProxy.go
  31. 2 2
      psiphon/httpProxy_test.go
  32. 2 2
      psiphon/interrupt_dials_test.go
  33. 1 1
      psiphon/limitProtocols_test.go
  34. 1 0
      psiphon/meekConn.go
  35. 2 2
      psiphon/net.go
  36. 1 0
      psiphon/notice.go
  37. 1 1
      psiphon/remoteServerList_test.go
  38. 3 3
      psiphon/server/api.go
  39. 1 1
      psiphon/server/log.go
  40. 2 2
      psiphon/server/meek.go
  41. 10 7
      psiphon/server/meek_test.go
  42. 1 1
      psiphon/server/psinet/psinet.go
  43. 3 1
      psiphon/server/psinet/psinet_test.go
  44. 4 4
      psiphon/server/server_test.go
  45. 1 1
      psiphon/server/services.go
  46. 1 1
      psiphon/server/sessionID_test.go
  47. 5 5
      psiphon/server/trafficRules.go
  48. 2 2
      psiphon/server/tunnelServer.go
  49. 1 1
      psiphon/server/udp.go
  50. 9 9
      psiphon/server/webServer.go
  51. 1 1
      psiphon/transferstats/conn.go
  52. 2 9
      psiphon/transferstats/transferstats_test.go
  53. 1 1
      psiphon/tunnel.go
  54. 3 0
      psiphon/upgradeDownload.go
  55. 1 1
      psiphon/upstreamproxy/auth_basic.go
  56. 8 8
      psiphon/upstreamproxy/auth_digest.go
  57. 9 5
      psiphon/upstreamproxy/auth_ntlm.go
  58. 4 4
      psiphon/upstreamproxy/http_authenticator.go
  59. 18 18
      psiphon/upstreamproxy/proxy_http.go
  60. 1 1
      psiphon/utils.go

+ 3 - 3
ClientLibrary/PsiphonTunnel.go

@@ -73,8 +73,8 @@ type startResultCode int
 
 const (
 	startResultCodeSuccess    startResultCode = 0
-	startResultCodeTimeout                    = 1
-	startResultCodeOtherError                 = 2
+	startResultCodeTimeout    startResultCode = 1
+	startResultCodeOtherError startResultCode = 2
 )
 
 type startResult struct {
@@ -236,7 +236,7 @@ func PsiphonTunnelStart(cConfigJSON, cEmbeddedServerEntryList *C.char, cParams *
 	// Success
 	managedStartResult = marshalStartResult(startResult{
 		Code:           startResultCodeSuccess,
-		ConnectTimeMS:  int64(time.Now().Sub(startTime) / time.Millisecond),
+		ConnectTimeMS:  int64(time.Since(startTime) / time.Millisecond),
 		HTTPProxyPort:  tunnel.HTTPProxyPort,
 		SOCKSProxyPort: tunnel.SOCKSProxyPort,
 	})

+ 2 - 1
ConsoleClient/main.go

@@ -31,6 +31,7 @@ import (
 	"os/signal"
 	"sort"
 	"sync"
+	"syscall"
 
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
@@ -290,7 +291,7 @@ func main() {
 	}()
 
 	systemStopSignal := make(chan os.Signal, 1)
-	signal.Notify(systemStopSignal, os.Interrupt, os.Kill)
+	signal.Notify(systemStopSignal, os.Interrupt, syscall.SIGTERM)
 
 	// writeProfilesSignal is nil and non-functional on Windows
 	writeProfilesSignal := makeSIGUSR2Channel()

+ 5 - 7
Server/logging/analysis/analysis.go

@@ -176,7 +176,7 @@ func (a *node) equal(b node) bool {
 					return false
 				}
 				for i := range m {
-					if vNode[i].equal(m[i]) != true {
+					if !vNode[i].equal(m[i]) {
 						return false
 					}
 				}
@@ -393,9 +393,7 @@ func (a *MetricsLogStats) Sort() []MetricsLogModelStats {
 
 func (a *UnknownLogStats) Sort() []UnknownLogModelStats {
 	var s []UnknownLogModelStats
-	for _, v := range a.modelStats {
-		s = append(s, v)
-	}
+	s = append(s, a.modelStats...)
 
 	sort.Slice(s, func(i, j int) bool {
 		return s[j].Count > s[i].Count
@@ -506,7 +504,7 @@ func (l *LogStats) ParseLogLine(log string) error {
 			l.UnknownLogModels.modelStats = append(l.UnknownLogModels.modelStats, UnknownLogModelStats{LogModelStats{1}, *v})
 		}
 	default:
-		return fmt.Errorf("Unexpected model type of %v\n", reflect.TypeOf(v))
+		return fmt.Errorf("unexpected model type of %v", reflect.TypeOf(v))
 	}
 
 	return nil
@@ -523,7 +521,7 @@ func parseLogModel(s string) (LogModel, error) {
 	var m LogFields
 	err := json.Unmarshal([]byte(s), &m)
 	if err != nil {
-		return nil, fmt.Errorf("Failed to parse log line into JSON: %s", err)
+		return nil, fmt.Errorf("failed to parse log line into JSON: %s", err)
 	}
 
 	var l LogModel
@@ -548,7 +546,7 @@ func parseLogModel(s string) (LogModel, error) {
 		case "error":
 			level = LOG_LEVEL_ERROR
 		default:
-			return nil, fmt.Errorf("Unexpected log level: %s\n", m["level"].(string))
+			return nil, fmt.Errorf("unexpected log level: %s", m["level"].(string))
 		}
 
 		var context *MessageLogContext

+ 3 - 3
psiphon/common/accesscontrol/accesscontrol_test.go

@@ -114,7 +114,7 @@ func TestAuthorization(t *testing.T) {
 		t.Fatalf("IssueAuthorization failed: %s", err)
 	}
 
-	verifiedAuth, err = VerifyAuthorization(keyRing, auth)
+	_, err = VerifyAuthorization(keyRing, auth)
 	// TODO: check error message?
 	if err == nil {
 		t.Fatalf("VerifyAuthorization unexpected success")
@@ -129,7 +129,7 @@ func TestAuthorization(t *testing.T) {
 		t.Fatalf("IssueAuthorization failed: %s", err)
 	}
 
-	verifiedAuth, err = VerifyAuthorization(keyRing, auth)
+	_, err = VerifyAuthorization(keyRing, auth)
 	// TODO: check error message?
 	if err == nil {
 		t.Fatalf("VerifyAuthorization unexpected success")
@@ -177,7 +177,7 @@ func TestAuthorization(t *testing.T) {
 
 	encodedSignedAuth := base64.StdEncoding.EncodeToString(marshaledSignedAuth)
 
-	verifiedAuth, err = VerifyAuthorization(keyRing, encodedSignedAuth)
+	_, err = VerifyAuthorization(keyRing, encodedSignedAuth)
 	// TODO: check error message?
 	if err == nil {
 		t.Fatalf("VerifyAuthorization unexpected success")

+ 3 - 3
psiphon/common/authPackage.go

@@ -150,7 +150,7 @@ func ReadAuthenticatedDataPackage(
 		return "", errors.TraceNew("unexpected signing public key type")
 	}
 
-	if 0 != bytes.Compare(
+	if !bytes.Equal(
 		authenticatedDataPackage.SigningPublicKeyDigest,
 		sha256sum(signingPublicKey)) {
 
@@ -294,7 +294,7 @@ func NewAuthenticatedDataPackageReader(
 				return nil, errors.TraceNew("unexpected signing public key type")
 			}
 
-			if 0 != bytes.Compare(jsonSigningPublicKey, sha256sum(signingPublicKey)) {
+			if !bytes.Equal(jsonSigningPublicKey, sha256sum(signingPublicKey)) {
 				return nil, errors.TraceNew("unexpected signing public key digest")
 			}
 
@@ -410,7 +410,7 @@ func (streamer *limitedJSONStreamer) Stream() error {
 				if b == '"' {
 					state = stateJSONSeekingStringValueEnd
 
-					key := string(keyBuffer.Bytes())
+					key := keyBuffer.String()
 
 					// Wrap the main reader in a reader that will read up to the end
 					// of the value and then EOF. The handler is expected to consume

+ 3 - 3
psiphon/common/marionette/marionette_test.go

@@ -75,7 +75,7 @@ func TestMarionette(t *testing.T) {
 					fmt.Printf("Start server conn.Close\n")
 					start := time.Now()
 					conn.Close()
-					fmt.Printf("Done server conn.Close: %s\n", time.Now().Sub(start))
+					fmt.Printf("Done server conn.Close: %s\n", time.Since(start))
 				}()
 				bytesFromClient := 0
 				b := make([]byte, 1024)
@@ -125,7 +125,7 @@ func TestMarionette(t *testing.T) {
 					fmt.Printf("Start client conn.Close\n")
 					start := time.Now()
 					conn.Close()
-					fmt.Printf("Done client conn.Close: %s\n", time.Now().Sub(start))
+					fmt.Printf("Done client conn.Close: %s\n", time.Since(start))
 				}()
 				b := make([]byte, 1024)
 				bytesRead := 0
@@ -167,7 +167,7 @@ func TestMarionette(t *testing.T) {
 	fmt.Printf("Start listener.Close\n")
 	start := time.Now()
 	listener.Close()
-	fmt.Printf("Done listener.Close: %s\n", time.Now().Sub(start))
+	fmt.Printf("Done listener.Close: %s\n", time.Since(start))
 
 	err = testGroup.Wait()
 	if err != nil {

+ 1 - 1
psiphon/common/net_test.go

@@ -77,7 +77,7 @@ func (c *dummyConn) RemoteAddr() net.Addr {
 }
 
 func (c *dummyConn) SetDeadline(t time.Time) error {
-	duration := t.Sub(time.Now())
+	duration := time.Until(t)
 	if c.timeout == nil {
 		c.timeout = time.NewTimer(duration)
 	} else {

+ 1 - 1
psiphon/common/osl/osl_test.go

@@ -401,7 +401,7 @@ func TestOSL(t *testing.T) {
 				if paveFile.Name != firstPaveFiles[index].Name {
 					t.Fatalf("Pave name mismatch")
 				}
-				if bytes.Compare(paveFile.Contents, firstPaveFiles[index].Contents) != 0 {
+				if !bytes.Equal(paveFile.Contents, firstPaveFiles[index].Contents) {
 					t.Fatalf("Pave content mismatch")
 				}
 			}

+ 5 - 4
psiphon/common/prng/prng_test.go

@@ -50,11 +50,11 @@ func TestSeed(t *testing.T) {
 		prng2.Read(bytes2)
 
 		zeroes := make([]byte, i)
-		if 0 == bytes.Compare(zeroes, bytes1) {
+		if bytes.Equal(zeroes, bytes1) {
 			t.Fatalf("unexpected zero bytes")
 		}
 
-		if 0 != bytes.Compare(bytes1, bytes2) {
+		if !bytes.Equal(bytes1, bytes2) {
 			t.Fatalf("unexpected different bytes")
 		}
 	}
@@ -82,11 +82,11 @@ func TestSeed(t *testing.T) {
 		bytes4 := make([]byte, i)
 		prng4.Read(bytes4)
 
-		if 0 == bytes.Compare(bytes1, bytes3) {
+		if bytes.Equal(bytes1, bytes3) {
 			t.Fatalf("unexpected identical bytes")
 		}
 
-		if 0 == bytes.Compare(bytes3, bytes4) {
+		if bytes.Equal(bytes3, bytes4) {
 			t.Fatalf("unexpected identical bytes")
 		}
 	}
@@ -373,6 +373,7 @@ func TestExpFloat64Range(t *testing.T) {
 	}
 }
 
+//lint:ignore U1000 intentionally unused
 func Disabled_TestRandomStreamLimit(t *testing.T) {
 
 	// This test takes up to ~2 minute to complete, so it's disabled by default.

+ 0 - 4
psiphon/common/profiles_test.go

@@ -41,10 +41,6 @@ func TestWriteRuntimeProfiles(t *testing.T) {
 type testLogger struct {
 }
 
-func (logger *testLogger) panic() {
-	panic("unexpected log call")
-}
-
 func (logger *testLogger) WithTrace() LogTrace {
 	return &testLoggerTrace{}
 }

+ 1 - 1
psiphon/common/protocol/customTLSProfiles_test.go

@@ -183,7 +183,7 @@ func TestCustomTLSProfiles(t *testing.T) {
 		t.Fatalf("Missing raw ClientHello")
 	}
 
-	if bytes.Compare(conn1.HandshakeState.Hello.Raw, conn2.HandshakeState.Hello.Raw) != 0 {
+	if !bytes.Equal(conn1.HandshakeState.Hello.Raw, conn2.HandshakeState.Hello.Raw) {
 		t.Fatalf("Unidentical raw ClientHellos")
 	}
 }

+ 2 - 2
psiphon/common/protocol/serverEntry.go

@@ -376,7 +376,7 @@ func (fields ServerEntryFields) VerifySignature(publicKey string) error {
 	publicKeyDigest := sha256.Sum256(decodedPublicKey)
 	expectedPublicKeyID := publicKeyDigest[:signaturePublicKeyDigestSize]
 
-	if bytes.Compare(expectedPublicKeyID, publicKeyID) != 0 {
+	if !bytes.Equal(expectedPublicKeyID, publicKeyID) {
 		return errors.TraceNew("unexpected public key ID")
 	}
 
@@ -404,7 +404,7 @@ func (fields ServerEntryFields) RemoveUnsignedFields() {
 	delete(fields, "localTimestamp")
 
 	// Only non-local, explicit tags are part of the signature
-	isLocalDerivedTag, _ := fields["isLocalDerivedTag"]
+	isLocalDerivedTag := fields["isLocalDerivedTag"]
 	isLocalDerivedTagBool, ok := isLocalDerivedTag.(bool)
 	if ok && isLocalDerivedTagBool {
 		delete(fields, "tag")

+ 2 - 5
psiphon/common/quic/quic.go

@@ -72,7 +72,6 @@ var serverIdleTimeout = SERVER_IDLE_TIMEOUT
 // Listener is a net.Listener.
 type Listener struct {
 	gquic.Listener
-	logger common.Logger
 }
 
 // Listen creates a new Listener.
@@ -137,9 +136,7 @@ func Listen(
 		return nil, errors.Trace(err)
 	}
 
-	return &Listener{
-		Listener: quicListener,
-	}, nil
+	return &Listener{Listener: quicListener}, nil
 }
 
 // Accept returns a net.Conn that wraps a single QUIC session and stream. The
@@ -203,7 +200,7 @@ func Dial(
 
 	deadline, ok := ctx.Deadline()
 	if ok {
-		quicConfig.HandshakeTimeout = deadline.Sub(time.Now())
+		quicConfig.HandshakeTimeout = time.Until(deadline)
 	}
 
 	if negotiateQUICVersion == protocol.QUIC_VERSION_OBFUSCATED {

+ 1 - 1
psiphon/common/quic/quic_test.go

@@ -33,7 +33,7 @@ import (
 )
 
 func TestQUIC(t *testing.T) {
-	for negotiateQUICVersion, _ := range supportedVersionNumbers {
+	for negotiateQUICVersion := range supportedVersionNumbers {
 		t.Run(negotiateQUICVersion, func(t *testing.T) {
 			runQUIC(t, negotiateQUICVersion)
 		})

+ 3 - 3
psiphon/common/reloader_test.go

@@ -70,7 +70,7 @@ func TestReloader(t *testing.T) {
 		t.Fatalf("Unexpected non-reload")
 	}
 
-	if bytes.Compare(file.contents, initialContents) != 0 {
+	if !bytes.Equal(file.contents, initialContents) {
 		t.Fatalf("Unexpected contents")
 	}
 
@@ -85,7 +85,7 @@ func TestReloader(t *testing.T) {
 		t.Fatalf("Unexpected reload")
 	}
 
-	if bytes.Compare(file.contents, initialContents) != 0 {
+	if !bytes.Equal(file.contents, initialContents) {
 		t.Fatalf("Unexpected contents")
 	}
 
@@ -105,7 +105,7 @@ func TestReloader(t *testing.T) {
 		t.Fatalf("Unexpected non-reload")
 	}
 
-	if bytes.Compare(file.contents, modifiedContents) != 0 {
+	if !bytes.Equal(file.contents, modifiedContents) {
 		t.Fatalf("Unexpected contents")
 	}
 }

+ 64 - 66
psiphon/common/tactics/tactics.go

@@ -1103,86 +1103,84 @@ func NewListener(
 // For retained connections, fragmentation is applied when specified by
 // tactics.
 func (listener *Listener) Accept() (net.Conn, error) {
-	for {
 
-		conn, err := listener.Listener.Accept()
-		if err != nil {
-			// Don't modify error from net.Listener
-			return nil, err
-		}
+	conn, err := listener.Listener.Accept()
+	if err != nil {
+		// Don't modify error from net.Listener
+		return nil, err
+	}
 
-		geoIPData := listener.geoIPLookup(common.IPAddressFromAddr(conn.RemoteAddr()))
+	geoIPData := listener.geoIPLookup(common.IPAddressFromAddr(conn.RemoteAddr()))
 
-		tactics, err := listener.server.getTactics(true, geoIPData, make(common.APIParameters))
-		if err != nil {
-			listener.server.logger.WithTraceFields(
-				common.LogFields{"error": err}).Warning("failed to get tactics for connection")
-			// If tactics is somehow misconfigured, keep handling connections.
-			// Other error cases that follow below take the same approach.
-			return conn, nil
-		}
+	tactics, err := listener.server.getTactics(true, geoIPData, make(common.APIParameters))
+	if err != nil {
+		listener.server.logger.WithTraceFields(
+			common.LogFields{"error": err}).Warning("failed to get tactics for connection")
+		// If tactics is somehow misconfigured, keep handling connections.
+		// Other error cases that follow below take the same approach.
+		return conn, nil
+	}
 
-		if tactics == nil {
-			// This server isn't configured with tactics.
-			return conn, nil
-		}
+	if tactics == nil {
+		// This server isn't configured with tactics.
+		return conn, nil
+	}
 
-		if !prng.FlipWeightedCoin(tactics.Probability) {
-			// Skip tactics with the configured probability.
-			return conn, nil
-		}
+	if !prng.FlipWeightedCoin(tactics.Probability) {
+		// Skip tactics with the configured probability.
+		return conn, nil
+	}
 
-		clientParameters, err := parameters.NewClientParameters(nil)
-		if err != nil {
-			return conn, nil
-		}
+	clientParameters, err := parameters.NewClientParameters(nil)
+	if err != nil {
+		return conn, nil
+	}
 
-		_, err = clientParameters.Set("", false, tactics.Parameters)
+	_, err = clientParameters.Set("", false, tactics.Parameters)
+	if err != nil {
+		return conn, nil
+	}
+
+	p := clientParameters.Get()
+
+	// Wrap the conn in a fragmentor.Conn, subject to tactics parameters.
+	//
+	// Limitation: this server-side fragmentation is not synchronized with
+	// client-side; where client-side will make a single coin flip to fragment
+	// or not fragment all TCP connections for a one meek session, the server
+	// will make a coin flip per connection.
+	//
+	// Delay seeding the fragmentor PRNG when we can derive a seed from the
+	// client's initial obfuscation message. This enables server-side replay
+	// of fragmentation when initiated by the client. Currently this is only
+	// supported for OSSH: SSH lacks the initial obfuscation message, and
+	// meek and other protocols transmit downstream data before the initial
+	// obfuscation message arrives.
+
+	var seed *prng.Seed
+	if listener.tunnelProtocol != protocol.TUNNEL_PROTOCOL_OBFUSCATED_SSH {
+		seed, err = prng.NewSeed()
 		if err != nil {
+			listener.server.logger.WithTraceFields(
+				common.LogFields{"error": err}).Warning("failed to seed fragmentor PRNG")
 			return conn, nil
 		}
+	}
 
-		p := clientParameters.Get()
+	fragmentorConfig := fragmentor.NewDownstreamConfig(
+		p, listener.tunnelProtocol, seed)
 
-		// Wrap the conn in a fragmentor.Conn, subject to tactics parameters.
-		//
-		// Limitation: this server-side fragmentation is not synchronized with
-		// client-side; where client-side will make a single coin flip to fragment
-		// or not fragment all TCP connections for a one meek session, the server
-		// will make a coin flip per connection.
-		//
-		// Delay seeding the fragmentor PRNG when we can derive a seed from the
-		// client's initial obfuscation message. This enables server-side replay
-		// of fragmentation when initiated by the client. Currently this is only
-		// supported for OSSH: SSH lacks the initial obfuscation message, and
-		// meek and other protocols transmit downstream data before the initial
-		// obfuscation message arrives.
-
-		var seed *prng.Seed
-		if listener.tunnelProtocol != protocol.TUNNEL_PROTOCOL_OBFUSCATED_SSH {
-			seed, err = prng.NewSeed()
-			if err != nil {
+	if fragmentorConfig.MayFragment() {
+		conn = fragmentor.NewConn(
+			fragmentorConfig,
+			func(message string) {
 				listener.server.logger.WithTraceFields(
-					common.LogFields{"error": err}).Warning("failed to seed fragmentor PRNG")
-				return conn, nil
-			}
-		}
-
-		fragmentorConfig := fragmentor.NewDownstreamConfig(
-			p, listener.tunnelProtocol, seed)
-
-		if fragmentorConfig.MayFragment() {
-			conn = fragmentor.NewConn(
-				fragmentorConfig,
-				func(message string) {
-					listener.server.logger.WithTraceFields(
-						common.LogFields{"message": message}).Debug("Fragmentor")
-				},
-				conn)
-		}
-
-		return conn, nil
+					common.LogFields{"message": message}).Debug("Fragmentor")
+			},
+			conn)
 	}
+
+	return conn, nil
 }
 
 // RoundTripper performs a round trip to the specified endpoint, sending the

+ 4 - 0
psiphon/common/tactics/tactics_test.go

@@ -739,6 +739,10 @@ func TestTactics(t *testing.T) {
 		t.Fatalf("Reload failed: %s", err)
 	}
 
+	if !reloaded {
+		t.Fatalf("Server config failed to reload")
+	}
+
 	listenerTestCases := []struct {
 		description      string
 		geoIPLookup      func(string) common.GeoIPData

+ 11 - 6
psiphon/common/tun/nonblock_test.go

@@ -102,10 +102,12 @@ func TestNonblockingIO(t *testing.T) {
 					if isClosed() {
 						return
 					}
-					t.Fatalf("io.ReadFull failed: %s", err)
+					t.Errorf("io.ReadFull failed: %s", err)
+					return
 				}
-				if bytes.Compare(expectedData[:n], data[:n]) != 0 {
-					t.Fatalf("bytes.Compare failed")
+				if !bytes.Equal(expectedData[:n], data[:n]) {
+					t.Errorf("bytes.Equal failed")
+					return
 				}
 			}
 
@@ -116,7 +118,8 @@ func TestNonblockingIO(t *testing.T) {
 				n, err = r.Read(data)
 			}
 			if n != 0 || err != io.EOF {
-				t.Fatalf("expected io.EOF failed")
+				t.Errorf("expected io.EOF failed")
+				return
 			}
 		}
 
@@ -135,10 +138,12 @@ func TestNonblockingIO(t *testing.T) {
 					if isClosed() {
 						return
 					}
-					t.Fatalf("w.Write failed: %s", err)
+					t.Errorf("w.Write failed: %s", err)
+					return
 				}
 				if m != n {
-					t.Fatalf("w.Write failed: unexpected number of bytes written")
+					t.Errorf("w.Write failed: unexpected number of bytes written")
+					return
 				}
 			}
 		}

+ 2 - 2
psiphon/common/tun/tun.go

@@ -1286,6 +1286,7 @@ func (session *session) startTrackingFlow(
 	}
 
 	var hostname string
+	//lint:ignore SA9003 intentionally empty branch
 	if ID.protocol == internetProtocolTCP {
 		// TODO: implement
 		// hostname = common.ExtractHostnameFromTCPFlow(applicationData)
@@ -2534,8 +2535,7 @@ func checksumAccumulate(data []byte, newData bool, accumulator *int32) {
 	// Assumes length of data is factor of 4.
 
 	for i := 0; i < len(data); i += 4 {
-		var word uint32
-		word = uint32(data[i+0])<<24 | uint32(data[i+1])<<16 | uint32(data[i+2])<<8 | uint32(data[i+3])
+		word := uint32(data[i+0])<<24 | uint32(data[i+1])<<16 | uint32(data[i+2])<<8 | uint32(data[i+3])
 		if newData {
 			*accumulator -= int32(word & 0xFFFF)
 			*accumulator -= int32(word >> 16)

+ 2 - 2
psiphon/common/tun/tun_darwin.go

@@ -263,7 +263,7 @@ func configureServerInterface(
 	tunDeviceName string) error {
 
 	// TODO: fix or remove the following broken code
-	return errors.Trace(unsupportedError)
+	return errors.Trace(errUnsupported)
 
 	// Set tun device network addresses and MTU
 
@@ -385,7 +385,7 @@ func configureClientInterface(
 	tunDeviceName string) error {
 
 	// TODO: fix or remove the following broken code
-	return errors.Trace(unsupportedError)
+	return errors.Trace(errUnsupported)
 
 	// Set tun device network addresses and MTU
 

+ 2 - 2
psiphon/common/tun/tun_test.go

@@ -186,8 +186,8 @@ func testTunneledTCP(t *testing.T, useIPv6 bool) {
 					return
 				}
 
-				if 0 != bytes.Compare(sendChunk, receiveChunk) {
-					results <- fmt.Errorf("bytes.Compare failed")
+				if !bytes.Equal(sendChunk, receiveChunk) {
+					results <- fmt.Errorf("bytes.Equal failed")
 					return
 				}
 			}

+ 12 - 12
psiphon/common/tun/tun_unsupported.go

@@ -46,35 +46,35 @@ func makeDeviceOutboundBuffer(_ int) []byte {
 }
 
 func OpenTunDevice(_ string) (*os.File, string, error) {
-	return nil, "", errors.Trace(unsupportedError)
+	return nil, "", errors.Trace(errUnsupportedor)
 }
 
 func (device *Device) readTunPacket() (int, int, error) {
-	return 0, 0, errors.Trace(unsupportedError)
+	return 0, 0, errors.Trace(errUnsupportedor)
 }
 
 func (device *Device) writeTunPacket(_ []byte) error {
-	return errors.Trace(unsupportedError)
+	return errors.Trace(errUnsupportedor)
 }
 
 func configureNetworkConfigSubprocessCapabilities() error {
-	return errors.Trace(unsupportedError)
+	return errors.Trace(errUnsupportedor)
 }
 
 func resetNATTables(_ *ServerConfig, _ net.IP) error {
-	return errors.Trace(unsupportedError)
+	return errors.Trace(errUnsupportedor)
 }
 
 func configureServerInterface(_ *ServerConfig, _ string) error {
-	return errors.Trace(unsupportedError)
+	return errors.Trace(errUnsupportedor)
 }
 
 func configureClientInterface(_ *ClientConfig, _ string) error {
-	return errors.Trace(unsupportedError)
+	return errors.Trace(errUnsupportedor)
 }
 
 func BindToDevice(_ int, _ string) error {
-	return errors.Trace(unsupportedError)
+	return errors.Trace(errUnsupportedor)
 }
 
 func fixBindToDevice(_ common.Logger, _ bool, _ string) error {
@@ -86,15 +86,15 @@ type NonblockingIO struct {
 }
 
 func NewNonblockingIO(ioFD int) (*NonblockingIO, error) {
-	return nil, errors.Trace(unsupportedError)
+	return nil, errors.Trace(errUnsupportedor)
 }
 
 func (nio *NonblockingIO) Read(p []byte) (int, error) {
-	return 0, errors.Trace(unsupportedError)
+	return 0, errors.Trace(errUnsupportedor)
 }
 
 func (nio *NonblockingIO) Write(p []byte) (int, error) {
-	return 0, errors.Trace(unsupportedError)
+	return 0, errors.Trace(errUnsupportedor)
 }
 
 func (nio *NonblockingIO) IsClosed() bool {
@@ -102,5 +102,5 @@ func (nio *NonblockingIO) IsClosed() bool {
 }
 
 func (nio *NonblockingIO) Close() error {
-	return errors.Trace(unsupportedError)
+	return errors.Trace(errUnsupportedor)
 }

+ 1 - 1
psiphon/common/tun/utils.go

@@ -30,7 +30,7 @@ import (
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
 )
 
-var unsupportedError = std_errors.New("operation unsupported on this platform")
+var errUnsupported = std_errors.New("operation unsupported on this platform")
 
 // runNetworkConfigCommand execs a network config command, such as "ifconfig"
 // or "iptables". On platforms that support capabilities, the network config

+ 1 - 1
psiphon/common/utils_test.go

@@ -63,7 +63,7 @@ func TestCompress(t *testing.T) {
 		t.Errorf("Uncompress failed: %s", err)
 	}
 
-	if bytes.Compare(originalData, decompressedData) != 0 {
+	if !bytes.Equal(originalData, decompressedData) {
 		t.Error("decompressed data doesn't match original data")
 	}
 }

+ 41 - 20
psiphon/config.go

@@ -791,7 +791,7 @@ func (config *Config) Commit() error {
 	// config.networkIDGetter and not the input/exported fields.
 
 	if config.DeviceBinder != nil {
-		config.deviceBinder = &loggingDeviceBinder{config.DeviceBinder}
+		config.deviceBinder = newLoggingDeviceBinder(config.DeviceBinder)
 	}
 
 	networkIDGetter := config.NetworkIDGetter
@@ -807,7 +807,7 @@ func (config *Config) Commit() error {
 		}
 	}
 
-	config.networkIDGetter = &loggingNetworkIDGetter{networkIDGetter}
+	config.networkIDGetter = newLoggingNetworkIDGetter(networkIDGetter)
 
 	config.committed = true
 
@@ -1155,11 +1155,11 @@ func (config *Config) setDialParametersHash() {
 
 	// Calculate and store a hash of the config values that may impact
 	// dial parameters. This hash is used as part of the dial parameters
-	// replay mechanism to detect when persisted dial parameters must
+	// replay mechanism to detect when persisted dial parameters should
 	// be discarded due to conflicting config changes.
 	//
 	// MD5 hash is used solely as a data checksum and not for any security
-	// purpose.
+	// purpose; serialization is not strictly unambiguous.
 
 	hash := md5.New()
 
@@ -1173,7 +1173,7 @@ func (config *Config) setDialParametersHash() {
 		for _, protocol := range config.InitialLimitTunnelProtocols {
 			hash.Write([]byte(protocol))
 		}
-		binary.Write(hash, binary.LittleEndian, config.InitialLimitTunnelProtocolsCandidateCount)
+		binary.Write(hash, binary.LittleEndian, int64(config.InitialLimitTunnelProtocolsCandidateCount))
 	}
 
 	if len(config.LimitTLSProfiles) > 0 {
@@ -1214,31 +1214,31 @@ func (config *Config) setDialParametersHash() {
 	}
 
 	if config.FragmentorMinTotalBytes != nil {
-		binary.Write(hash, binary.LittleEndian, *config.FragmentorMinTotalBytes)
+		binary.Write(hash, binary.LittleEndian, int64(*config.FragmentorMinTotalBytes))
 	}
 
 	if config.FragmentorMaxTotalBytes != nil {
-		binary.Write(hash, binary.LittleEndian, *config.FragmentorMaxTotalBytes)
+		binary.Write(hash, binary.LittleEndian, int64(*config.FragmentorMaxTotalBytes))
 	}
 
 	if config.FragmentorMinWriteBytes != nil {
-		binary.Write(hash, binary.LittleEndian, *config.FragmentorMinWriteBytes)
+		binary.Write(hash, binary.LittleEndian, int64(*config.FragmentorMinWriteBytes))
 	}
 
 	if config.FragmentorMaxWriteBytes != nil {
-		binary.Write(hash, binary.LittleEndian, *config.FragmentorMaxWriteBytes)
+		binary.Write(hash, binary.LittleEndian, int64(*config.FragmentorMaxWriteBytes))
 	}
 
 	if config.FragmentorMinDelayMicroseconds != nil {
-		binary.Write(hash, binary.LittleEndian, *config.FragmentorMinDelayMicroseconds)
+		binary.Write(hash, binary.LittleEndian, int64(*config.FragmentorMinDelayMicroseconds))
 	}
 
 	if config.FragmentorMaxDelayMicroseconds != nil {
-		binary.Write(hash, binary.LittleEndian, *config.FragmentorMaxDelayMicroseconds)
+		binary.Write(hash, binary.LittleEndian, int64(*config.FragmentorMaxDelayMicroseconds))
 	}
 
 	if config.MeekTrafficShapingProbability != nil {
-		binary.Write(hash, binary.LittleEndian, *config.MeekTrafficShapingProbability)
+		binary.Write(hash, binary.LittleEndian, int64(*config.MeekTrafficShapingProbability))
 	}
 
 	if len(config.MeekTrafficShapingLimitProtocols) > 0 {
@@ -1248,11 +1248,11 @@ func (config *Config) setDialParametersHash() {
 	}
 
 	if config.MeekMinLimitRequestPayloadLength != nil {
-		binary.Write(hash, binary.LittleEndian, *config.MeekMinLimitRequestPayloadLength)
+		binary.Write(hash, binary.LittleEndian, int64(*config.MeekMinLimitRequestPayloadLength))
 	}
 
 	if config.MeekMaxLimitRequestPayloadLength != nil {
-		binary.Write(hash, binary.LittleEndian, *config.MeekMaxLimitRequestPayloadLength)
+		binary.Write(hash, binary.LittleEndian, int64(*config.MeekMaxLimitRequestPayloadLength))
 	}
 
 	if config.MeekRedialTLSProbability != nil {
@@ -1260,27 +1260,48 @@ func (config *Config) setDialParametersHash() {
 	}
 
 	if config.ObfuscatedSSHMinPadding != nil {
-		binary.Write(hash, binary.LittleEndian, *config.ObfuscatedSSHMinPadding)
+		binary.Write(hash, binary.LittleEndian, int64(*config.ObfuscatedSSHMinPadding))
 	}
 
 	if config.ObfuscatedSSHMaxPadding != nil {
-		binary.Write(hash, binary.LittleEndian, *config.ObfuscatedSSHMaxPadding)
+		binary.Write(hash, binary.LittleEndian, int64(*config.ObfuscatedSSHMaxPadding))
 	}
 
 	if config.LivenessTestMinUpstreamBytes != nil {
-		binary.Write(hash, binary.LittleEndian, *config.LivenessTestMinUpstreamBytes)
+		binary.Write(hash, binary.LittleEndian, int64(*config.LivenessTestMinUpstreamBytes))
 	}
 
 	if config.LivenessTestMaxUpstreamBytes != nil {
-		binary.Write(hash, binary.LittleEndian, *config.LivenessTestMaxUpstreamBytes)
+		binary.Write(hash, binary.LittleEndian, int64(*config.LivenessTestMaxUpstreamBytes))
 	}
 
 	if config.LivenessTestMinDownstreamBytes != nil {
-		binary.Write(hash, binary.LittleEndian, *config.LivenessTestMinDownstreamBytes)
+		binary.Write(hash, binary.LittleEndian, int64(*config.LivenessTestMinDownstreamBytes))
 	}
 
 	if config.LivenessTestMaxDownstreamBytes != nil {
-		binary.Write(hash, binary.LittleEndian, *config.LivenessTestMaxDownstreamBytes)
+		binary.Write(hash, binary.LittleEndian, int64(*config.LivenessTestMaxDownstreamBytes))
+	}
+
+	binary.Write(hash, binary.LittleEndian, config.NetworkLatencyMultiplierMin)
+	binary.Write(hash, binary.LittleEndian, config.NetworkLatencyMultiplierMax)
+	binary.Write(hash, binary.LittleEndian, config.NetworkLatencyMultiplierLambda)
+
+	if config.UseOnlyCustomTLSProfiles != nil {
+		binary.Write(hash, binary.LittleEndian, *config.UseOnlyCustomTLSProfiles)
+	}
+
+	for _, customTLSProfile := range config.CustomTLSProfiles {
+		// Assumes consistent definition for a given profile name
+		hash.Write([]byte(customTLSProfile.Name))
+	}
+
+	if config.SelectRandomizedTLSProfileProbability != nil {
+		binary.Write(hash, binary.LittleEndian, *config.SelectRandomizedTLSProfileProbability)
+	}
+
+	if config.NoDefaultTLSSessionIDProbability != nil {
+		binary.Write(hash, binary.LittleEndian, *config.NoDefaultTLSSessionIDProbability)
 	}
 
 	config.dialParametersHash = hash.Sum(nil)

+ 6 - 6
psiphon/controller.go

@@ -964,13 +964,13 @@ func (controller *Controller) terminateAllTunnels() {
 func (controller *Controller) getNextActiveTunnel() (tunnel *Tunnel) {
 	controller.tunnelMutex.Lock()
 	defer controller.tunnelMutex.Unlock()
-	for i := len(controller.tunnels); i > 0; i-- {
-		tunnel = controller.tunnels[controller.nextTunnel]
-		controller.nextTunnel =
-			(controller.nextTunnel + 1) % len(controller.tunnels)
-		return tunnel
+	if len(controller.tunnels) == 0 {
+		return nil
 	}
-	return nil
+	tunnel = controller.tunnels[controller.nextTunnel]
+	controller.nextTunnel =
+		(controller.nextTunnel + 1) % len(controller.tunnels)
+	return tunnel
 }
 
 // isActiveTunnelServerEntry is used to check if there's already

+ 4 - 4
psiphon/dataStore.go

@@ -371,7 +371,7 @@ func hasServerEntryFilterChanged(config *Config) (bool, error) {
 		// When not found, previousFilter will be nil; ensures this
 		// results in "changed", even if currentFilter is len(0).
 		if previousFilter == nil ||
-			bytes.Compare(previousFilter, currentFilter) != 0 {
+			!bytes.Equal(previousFilter, currentFilter) {
 			changed = true
 		}
 		return nil
@@ -892,7 +892,7 @@ func pruneServerEntry(config *Config, serverEntryTag string) error {
 			}
 
 			affinityServerEntryID := keyValues.get(datastoreAffinityServerEntryIDKey)
-			if 0 == bytes.Compare(affinityServerEntryID, serverEntryID) {
+			if bytes.Equal(affinityServerEntryID, serverEntryID) {
 				err = keyValues.delete(datastoreAffinityServerEntryIDKey)
 				if err != nil {
 					return errors.Trace(err)
@@ -1288,7 +1288,7 @@ func CountUnreportedPersistentStats() int {
 			bucket := tx.bucket([]byte(statType))
 			cursor := bucket.cursor()
 			for key, value := cursor.first(); key != nil; key, value = cursor.next() {
-				if 0 == bytes.Compare(value, persistentStatStateUnreported) {
+				if bytes.Equal(value, persistentStatStateUnreported) {
 					unreported++
 				}
 			}
@@ -1340,7 +1340,7 @@ func TakeOutUnreportedPersistentStats(config *Config) (map[string][][]byte, erro
 					continue
 				}
 
-				if 0 == bytes.Compare(value, persistentStatStateUnreported) {
+				if bytes.Equal(value, persistentStatStateUnreported) {
 					// Must make a copy as slice is only valid within transaction.
 					data := make([]byte, len(key))
 					copy(data, key)

+ 4 - 6
psiphon/dialParameters.go

@@ -196,7 +196,7 @@ func MakeDialParameters(
 	if dialParams != nil &&
 		(ttl <= 0 ||
 			dialParams.LastUsedTimestamp.Before(currentTimestamp.Add(-ttl)) ||
-			bytes.Compare(dialParams.LastUsedConfigStateHash, configStateHash) != 0 ||
+			!bytes.Equal(dialParams.LastUsedConfigStateHash, configStateHash) ||
 			(dialParams.TLSProfile != "" &&
 				!common.Contains(protocol.SupportedTLSProfiles, dialParams.TLSProfile)) ||
 			(dialParams.QUICVersion != "" &&
@@ -928,11 +928,9 @@ func makeDialCustomHeaders(
 	}
 
 	additionalCustomHeaders := p.HTTPHeaders(parameters.AdditionalCustomHeaders)
-	if additionalCustomHeaders != nil {
-		for k, v := range additionalCustomHeaders {
-			dialCustomHeaders[k] = make([]string, len(v))
-			copy(dialCustomHeaders[k], v)
-		}
+	for k, v := range additionalCustomHeaders {
+		dialCustomHeaders[k] = make([]string, len(v))
+		copy(dialCustomHeaders[k], v)
 	}
 	return dialCustomHeaders
 }

+ 1 - 1
psiphon/dialParameters_test.go

@@ -263,7 +263,7 @@ func runDialParametersAndReplay(t *testing.T, tunnelProtocol string) {
 		if seed1 == nil {
 			return seed2 == nil
 		}
-		return bytes.Compare(seed1[:], seed2[:]) == 0
+		return bytes.Equal(seed1[:], seed2[:])
 	}
 
 	if replayDialParams.SelectedSSHClientVersion != dialParams.SelectedSSHClientVersion ||

+ 14 - 16
psiphon/httpProxy.go

@@ -367,7 +367,7 @@ func (conn *rewriteICYConn) Read(b []byte) (int, error) {
 		return n, err
 	}
 
-	if bytes.Compare(b[:3], []byte("ICY")) == 0 {
+	if bytes.Equal(b[:3], []byte("ICY")) {
 		atomic.StoreInt32(conn.isICY, 1)
 		protocol := "HTTP/1.0"
 		copy(b, []byte(protocol))
@@ -504,24 +504,22 @@ func (proxy *HttpProxy) relayHTTPRequest(
 
 	defer response.Body.Close()
 
-	if rewrites != nil {
-		// NOTE: Rewrite functions are responsible for leaving response.Body in
-		// a valid, readable state if there's no error.
+	// Note: Rewrite functions are responsible for leaving response.Body in
+	// a valid, readable state if there's no error.
 
-		for key := range rewrites {
-			var err error
+	for key := range rewrites {
+		var err error
 
-			switch key {
-			case "m3u8":
-				err = rewriteM3U8(proxy.listenIP, proxy.listenPort, response)
-			}
+		switch key {
+		case "m3u8":
+			err = rewriteM3U8(proxy.listenIP, proxy.listenPort, response)
+		}
 
-			if err != nil {
-				NoticeAlert("URL proxy rewrite failed for %s: %s", key, errors.Trace(err))
-				forceClose(responseWriter)
-				response.Body.Close()
-				return
-			}
+		if err != nil {
+			NoticeAlert("URL proxy rewrite failed for %s: %s", key, errors.Trace(err))
+			forceClose(responseWriter)
+			response.Body.Close()
+			return
 		}
 	}
 

+ 2 - 2
psiphon/httpProxy_test.go

@@ -140,11 +140,11 @@ func TestRewriteM3U8(t *testing.T) {
 
 		expectedBody, _ := ioutil.ReadFile(tt.expectedFilename)
 
-		if bytes.Compare(rewrittenBody, expectedBody) != 0 {
+		if !bytes.Equal(rewrittenBody, expectedBody) {
 			t.Errorf("rewriteM3U8 body mismatch for test %d", i)
 		}
 
-		if tt.expectedContentType != "" && strings.ToLower(response.Header.Get("Content-Type")) != strings.ToLower(tt.expectedContentType) {
+		if tt.expectedContentType != "" && !strings.EqualFold(response.Header.Get("Content-Type"), tt.expectedContentType) {
 			t.Errorf("rewriteM3U8 Content-Type mismatch for test %d: %s %s", i, tt.expectedContentType, response.Header.Get("Content-Type"))
 		}
 

+ 2 - 2
psiphon/interrupt_dials_test.go

@@ -190,7 +190,7 @@ func runInterruptDials(
 
 	startWaiting := monotime.Now()
 
-	for _ = range addrs {
+	for range addrs {
 		<-dialTerminated
 	}
 
@@ -212,7 +212,7 @@ func runInterruptDials(
 func findGoroutines(t *testing.T, targets []string) bool {
 	n, _ := runtime.GoroutineProfile(nil)
 	r := make([]runtime.StackRecord, n)
-	n, _ = runtime.GoroutineProfile(r)
+	runtime.GoroutineProfile(r)
 	found := false
 	for _, g := range r {
 		stack := g.Stack()

+ 1 - 1
psiphon/limitProtocols_test.go

@@ -60,7 +60,7 @@ func TestLimitTunnelProtocols(t *testing.T) {
 
 				connectingCount += 1
 
-				protocolField, _ := payload["protocol"]
+				protocolField := payload["protocol"]
 				protocol := protocolField.(string)
 
 				if common.Contains(initialLimitTunnelProtocols, protocol) {

+ 1 - 0
psiphon/meekConn.go

@@ -1132,6 +1132,7 @@ func (meek *MeekConn) relayRoundTrip(sendBuffer *bytes.Buffer) (int64, error) {
 		}
 
 		request, cancelFunc, err := meek.newRequest(
+			//lint:ignore SA1012 meek.newRequest expects/handles nil context
 			nil,
 			nil,
 			requestBody,

+ 2 - 2
psiphon/net.go

@@ -233,7 +233,7 @@ func LocalProxyRelay(proxyType string, localConn, remoteConn net.Conn) {
 func WaitForNetworkConnectivity(
 	ctx context.Context, connectivityChecker NetworkConnectivityChecker) bool {
 
-	if connectivityChecker == nil || 1 == connectivityChecker.HasNetworkConnectivity() {
+	if connectivityChecker == nil || connectivityChecker.HasNetworkConnectivity() == 1 {
 		return true
 	}
 
@@ -243,7 +243,7 @@ func WaitForNetworkConnectivity(
 	defer ticker.Stop()
 
 	for {
-		if 1 == connectivityChecker.HasNetworkConnectivity() {
+		if connectivityChecker.HasNetworkConnectivity() == 1 {
 			return true
 		}
 

+ 1 - 0
psiphon/notice.go

@@ -162,6 +162,7 @@ func SetNoticeFiles(
 		if err != nil {
 			return errors.Trace(err)
 		}
+		singletonNoticeLogger.homepageFilename = homepageFilename
 	}
 
 	if rotatingFilename != "" {

+ 1 - 1
psiphon/remoteServerList_test.go

@@ -303,7 +303,7 @@ func testObfuscatedRemoteServerLists(t *testing.T, omitMD5Sums bool) {
 		err := server.RunServices(serverConfigJSON)
 		if err != nil {
 			// TODO: wrong goroutine for t.FatalNow()
-			t.Fatalf("error running server: %s", err)
+			t.Errorf("error running server: %s", err)
 		}
 	}()
 

+ 3 - 3
psiphon/server/api.go

@@ -620,7 +620,7 @@ func statusAPIRequestHandler(
 	if len(invalidServerEntryTags) > 0 {
 		statusResponse.InvalidServerEntryTags = make([]string, len(invalidServerEntryTags))
 		i := 0
-		for tag, _ := range invalidServerEntryTags {
+		for tag := range invalidServerEntryTags {
 			statusResponse.InvalidServerEntryTags[i] = tag
 			i++
 		}
@@ -1261,7 +1261,7 @@ func isIPAddress(_ *Config, value string) bool {
 	return net.ParseIP(value) != nil
 }
 
-var isDomainRegex = regexp.MustCompile("[a-zA-Z\\d-]{1,63}$")
+var isDomainRegex = regexp.MustCompile(`[a-zA-Z\d-]{1,63}$`)
 
 func isDomain(_ *Config, value string) bool {
 
@@ -1303,7 +1303,7 @@ func isServerEntrySource(_ *Config, value string) bool {
 }
 
 var isISO8601DateRegex = regexp.MustCompile(
-	"(?P<year>[0-9]{4})-(?P<month>[0-9]{1,2})-(?P<day>[0-9]{1,2})T(?P<hour>[0-9]{2}):(?P<minute>[0-9]{2}):(?P<second>[0-9]{2})(\\.(?P<fraction>[0-9]+))?(?P<timezone>Z|(([-+])([0-9]{2}):([0-9]{2})))")
+	`(?P<year>[0-9]{4})-(?P<month>[0-9]{1,2})-(?P<day>[0-9]{1,2})T(?P<hour>[0-9]{2}):(?P<minute>[0-9]{2}):(?P<second>[0-9]{2})(\.(?P<fraction>[0-9]+))?(?P<timezone>Z|(([-+])([0-9]{2}):([0-9]{2})))`)
 
 func isISO8601Date(_ *Config, value string) bool {
 	return isISO8601DateRegex.Match([]byte(value))

+ 1 - 1
psiphon/server/log.go

@@ -215,7 +215,7 @@ func (f *CustomJSONFormatter) Format(entry *logrus.Entry) ([]byte, error) {
 
 	serialized, err := json.Marshal(data)
 	if err != nil {
-		return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
+		return nil, fmt.Errorf("failed to marshal fields to JSON, %v", err)
 	}
 
 	if atomic.LoadInt32(&useLogCallback) == 1 {

+ 2 - 2
psiphon/server/meek.go

@@ -374,7 +374,7 @@ func (server *MeekServer) ServeHTTP(responseWriter http.ResponseWriter, request
 
 	// Set cookie before writing the response.
 
-	if session.meekProtocolVersion >= MEEK_PROTOCOL_VERSION_2 && session.sessionIDSent == false {
+	if session.meekProtocolVersion >= MEEK_PROTOCOL_VERSION_2 && !session.sessionIDSent {
 		// Replace the meek cookie with the session ID.
 		// SetCookie for the the session ID cookie is only set once, to reduce overhead. This
 		// session ID value replaces the original meek cookie value.
@@ -1324,7 +1324,7 @@ func (conn *meekConn) Write(buffer []byte) (int, error) {
 
 		// Wait for the buffer to be processed.
 		select {
-		case _ = <-conn.writeResult:
+		case <-conn.writeResult:
 			// The err from conn.writeResult comes from the
 			// io.MultiWriter used in pumpWrites, which writes
 			// to both the cached response and the HTTP response.

+ 10 - 7
psiphon/server/meek_test.go

@@ -145,7 +145,7 @@ func TestCachedResponse(t *testing.T) {
 						if n != cachedResponseData.Len() || n > response.Available() {
 							t.Fatalf("cached response size mismatch for response %d", i)
 						}
-						if bytes.Compare(responseData[testCase.copyPosition:], cachedResponseData.Bytes()) != 0 {
+						if !bytes.Equal(responseData[testCase.copyPosition:], cachedResponseData.Bytes()) {
 							t.Fatalf("cached response data mismatch for response %d", i)
 						}
 					} else {
@@ -185,7 +185,8 @@ func TestMeekResiliency(t *testing.T) {
 			writeLen = min(writeLen, len(data)-sent)
 			_, err := conn.Write(data[sent : sent+writeLen])
 			if err != nil {
-				t.Fatalf("conn.Write failed: %s", err)
+				t.Errorf("conn.Write failed: %s", err)
+				return
 			}
 			sent += writeLen
 			fmt.Printf("%s sent %d/%d...\n", name, sent, len(data))
@@ -202,10 +203,11 @@ func TestMeekResiliency(t *testing.T) {
 			readLen = min(readLen, len(data)-received)
 			n, err := conn.Read(data[received : received+readLen])
 			if err != nil {
-				t.Fatalf("conn.Read failed: %s", err)
+				t.Errorf("conn.Read failed: %s", err)
+				return
 			}
 			received += n
-			if bytes.Compare(data[0:received], expectedData[0:received]) != 0 {
+			if !bytes.Equal(data[0:received], expectedData[0:received]) {
 				fmt.Printf("%s data check has failed...\n", name)
 				additionalInfo := ""
 				index := bytes.Index(expectedData, data[received-n:received])
@@ -214,8 +216,9 @@ func TestMeekResiliency(t *testing.T) {
 					additionalInfo = fmt.Sprintf(
 						" (last read of %d appears at %d)", n, index)
 				}
-				t.Fatalf("%s got unexpected data with %d/%d%s",
+				t.Errorf("%s got unexpected data with %d/%d%s",
 					name, received, len(expectedData), additionalInfo)
+				return
 			}
 			fmt.Printf("%s received %d/%d...\n", name, received, len(expectedData))
 		}
@@ -294,7 +297,7 @@ func TestMeekResiliency(t *testing.T) {
 		default:
 		}
 		if err != nil {
-			t.Fatalf("MeekServer.Run failed: %s", err)
+			t.Errorf("MeekServer.Run failed: %s", err)
 		}
 	}()
 
@@ -462,7 +465,7 @@ func TestMeekRateLimiter(t *testing.T) {
 		default:
 		}
 		if err != nil {
-			t.Fatalf("MeekServer.Run failed: %s", err)
+			t.Errorf("MeekServer.Run failed: %s", err)
 		}
 	}()
 

+ 1 - 1
psiphon/server/psinet/psinet.go

@@ -229,7 +229,7 @@ func (db *Database) GetHttpsRequestRegexes(sponsorID string) []map[string]string
 
 	sponsor, ok := db.Sponsors[sponsorID]
 	if !ok {
-		sponsor, _ = db.Sponsors[db.DefaultSponsorID]
+		sponsor = db.Sponsors[db.DefaultSponsorID]
 	}
 
 	if sponsor == nil {

+ 3 - 1
psiphon/server/psinet/psinet_test.go

@@ -168,7 +168,9 @@ func TestDatabase(t *testing.T) {
 			ok := false
 			if len(regexes) == 1 && len(regexes[0]) == 2 {
 				regexValue, ok = regexes[0]["regex"]
-				replaceValue, ok = regexes[0]["replace"]
+				if ok {
+					replaceValue, ok = regexes[0]["replace"]
+				}
 			}
 			if !ok || regexValue != testCase.expectedRegexValue || replaceValue != testCase.expectedReplaceValue {
 				t.Fatalf("unexpected regexes: %+v", regexes)

+ 4 - 4
psiphon/server/server_test.go

@@ -707,7 +707,7 @@ func runServer(t *testing.T, runConfig *runServerConfig) {
 		err := RunServices(serverConfigJSON)
 		if err != nil {
 			// TODO: wrong goroutine for t.FatalNow()
-			t.Fatalf("error running server: %s", err)
+			t.Errorf("error running server: %s", err)
 		}
 	}()
 
@@ -729,7 +729,7 @@ func runServer(t *testing.T, runConfig *runServerConfig) {
 		select {
 		case <-shutdownOk:
 		case <-shutdownTimeout.C:
-			t.Fatalf("server shutdown timeout exceeded")
+			t.Errorf("server shutdown timeout exceeded")
 		}
 	}
 
@@ -935,7 +935,7 @@ func runServer(t *testing.T, runConfig *runServerConfig) {
 				homepageURL := payload["url"].(string)
 				if homepageURL != expectedHomepageURL {
 					// TODO: wrong goroutine for t.FatalNow()
-					t.Fatalf("unexpected homepage: %s", homepageURL)
+					t.Errorf("unexpected homepage: %s", homepageURL)
 				}
 				sendNotificationReceived(homepageReceived)
 
@@ -980,7 +980,7 @@ func runServer(t *testing.T, runConfig *runServerConfig) {
 		select {
 		case <-shutdownOk:
 		case <-shutdownTimeout.C:
-			t.Fatalf("controller shutdown timeout exceeded")
+			t.Errorf("controller shutdown timeout exceeded")
 		}
 	}
 

+ 1 - 1
psiphon/server/services.go

@@ -195,7 +195,7 @@ func RunServices(configJSON []byte) error {
 
 	// An OS signal triggers an orderly shutdown
 	systemStopSignal := make(chan os.Signal, 1)
-	signal.Notify(systemStopSignal, os.Interrupt, os.Kill, syscall.SIGTERM)
+	signal.Notify(systemStopSignal, os.Interrupt, syscall.SIGTERM)
 
 	// SIGUSR1 triggers a reload of support services
 	reloadSupportServicesSignal := make(chan os.Signal, 1)

+ 1 - 1
psiphon/server/sessionID_test.go

@@ -103,7 +103,7 @@ func TestDuplicateSessionID(t *testing.T) {
 		defer serverWaitGroup.Done()
 		err := RunServices(serverConfigJSON)
 		if err != nil {
-			t.Fatalf("error running server: %s", err)
+			t.Errorf("error running server: %s", err)
 		}
 	}()
 

+ 5 - 5
psiphon/server/trafficRules.go

@@ -436,7 +436,7 @@ func (set *TrafficRulesSet) initLookups() {
 
 	initTrafficRulesLookups(&set.DefaultRules)
 
-	for i, _ := range set.FilteredRules {
+	for i := range set.FilteredRules {
 		initTrafficRulesFilterLookups(&set.FilteredRules[i].Filter)
 		initTrafficRulesLookups(&set.FilteredRules[i].Rules)
 	}
@@ -722,7 +722,7 @@ func (rules *TrafficRules) AllowTCPPort(remoteIP net.IP, port int) bool {
 
 	if len(rules.DisallowTCPPorts) > 0 {
 		if rules.disallowTCPPortsLookup != nil {
-			if rules.disallowTCPPortsLookup[port] == true {
+			if rules.disallowTCPPortsLookup[port] {
 				return false
 			}
 		} else {
@@ -739,7 +739,7 @@ func (rules *TrafficRules) AllowTCPPort(remoteIP net.IP, port int) bool {
 	}
 
 	if rules.allowTCPPortsLookup != nil {
-		if rules.allowTCPPortsLookup[port] == true {
+		if rules.allowTCPPortsLookup[port] {
 			return true
 		}
 	} else {
@@ -757,7 +757,7 @@ func (rules *TrafficRules) AllowUDPPort(remoteIP net.IP, port int) bool {
 
 	if len(rules.DisallowUDPPorts) > 0 {
 		if rules.disallowUDPPortsLookup != nil {
-			if rules.disallowUDPPortsLookup[port] == true {
+			if rules.disallowUDPPortsLookup[port] {
 				return false
 			}
 		} else {
@@ -774,7 +774,7 @@ func (rules *TrafficRules) AllowUDPPort(remoteIP net.IP, port int) bool {
 	}
 
 	if rules.allowUDPPortsLookup != nil {
-		if rules.allowUDPPortsLookup[port] == true {
+		if rules.allowUDPPortsLookup[port] {
 			return true
 		}
 	} else {

+ 2 - 2
psiphon/server/tunnelServer.go

@@ -2391,7 +2391,7 @@ func (sshClient *sshClient) setHandshakeState(
 		}
 
 		sshClient.stopTimer = time.AfterFunc(
-			stopTime.Sub(time.Now()),
+			time.Until(stopTime),
 			func() {
 				sshClient.stop()
 			})
@@ -2429,7 +2429,7 @@ func (sshClient *sshClient) getHandshaked() (bool, bool) {
 	//   could have changed in a hot reload since the handshake.
 
 	if completed &&
-		*sshClient.trafficRules.RateLimits.CloseAfterExhausted == true &&
+		*sshClient.trafficRules.RateLimits.CloseAfterExhausted &&
 		(*sshClient.trafficRules.RateLimits.ReadUnthrottledBytes == 0 ||
 			*sshClient.trafficRules.RateLimits.WriteUnthrottledBytes == 0) {
 

+ 1 - 1
psiphon/server/udp.go

@@ -131,7 +131,7 @@ func (mux *udpPortForwardMultiplexer) run() {
 
 			// Verify that portForward remote address matches latest message
 
-			if 0 != bytes.Compare(portForward.remoteIP, message.remoteIP) ||
+			if !bytes.Equal(portForward.remoteIP, message.remoteIP) ||
 				portForward.remotePort != message.remotePort {
 
 				log.WithTrace().Warning("UDP port forward remote address mismatch")

+ 9 - 9
psiphon/server/webServer.go

@@ -39,9 +39,7 @@ import (
 const WEB_SERVER_IO_TIMEOUT = 10 * time.Second
 
 type webServer struct {
-	support      *SupportServices
-	tunnelServer *TunnelServer
-	serveMux     *http.ServeMux
+	support *SupportServices
 }
 
 // RunWebServer runs a web server which supports tunneled and untunneled
@@ -70,6 +68,7 @@ func RunWebServer(
 	serveMux.HandleFunc("/handshake", webServer.handshakeHandler)
 	serveMux.HandleFunc("/connected", webServer.connectedHandler)
 	serveMux.HandleFunc("/status", webServer.statusHandler)
+	serveMux.HandleFunc("/client_verification", webServer.clientVerificationHandler)
 
 	certificate, err := tris.X509KeyPair(
 		[]byte(support.Config.WebServerCertificate),
@@ -168,12 +167,14 @@ func convertHTTPRequestToAPIRequest(
 	params := make(common.APIParameters)
 
 	for name, values := range r.URL.Query() {
-		for _, value := range values {
 
-			// Limitations:
-			// - This is intended only to support params sent by legacy
-			//   clients; non-base array-type params are not converted.
-			// - Multiple values per name are ignored.
+		// Limitations:
+		// - This is intended only to support params sent by legacy
+		//   clients; non-base array-type params are not converted.
+		// - Only the first values per name is used.
+
+		if len(values) > 0 {
+			value := values[0]
 
 			// TODO: faster lookup?
 			isArray := false
@@ -196,7 +197,6 @@ func convertHTTPRequestToAPIRequest(
 				// All other query parameters are simple strings
 				params[name] = value
 			}
-			break
 		}
 	}
 

+ 1 - 1
psiphon/transferstats/conn.go

@@ -111,7 +111,7 @@ func (conn *Conn) Read(buffer []byte) (n int, err error) {
 	n, err = conn.Conn.Read(buffer)
 
 	var hostname string
-	if 1 == atomic.LoadInt32(&conn.hostnameParsed) {
+	if atomic.LoadInt32(&conn.hostnameParsed) == 1 {
 		hostname = conn.hostname
 	} else {
 		hostname = ""

+ 2 - 9
psiphon/transferstats/transferstats_test.go

@@ -20,7 +20,6 @@
 package transferstats
 
 import (
-	"encoding/json"
 	"errors"
 	"fmt"
 	"net"
@@ -40,9 +39,8 @@ var nextServerID = 0
 
 type StatsTestSuite struct {
 	suite.Suite
-	serverID            string
-	httpClient          *http.Client
-	httpClientNoRegexes *http.Client
+	serverID   string
+	httpClient *http.Client
 }
 
 func TestStatsTestSuite(t *testing.T) {
@@ -113,11 +111,6 @@ func (suite *StatsTestSuite) Test_TakeOutStatsForServer() {
 	payload = TakeOutStatsForServer(suite.serverID)
 	suite.NotNil(payload, "should receive valid payload for valid server ID")
 
-	payloadJSON, err := json.Marshal(payload)
-	var parsedJSON interface{}
-	err = json.Unmarshal(payloadJSON, &parsedJSON)
-	suite.Nil(err, "payload JSON should parse successfully")
-
 	// After we retrieve the stats for a server, they should be cleared out of the tracked stats
 	payload = TakeOutStatsForServer(suite.serverID)
 	suite.Equal(payload, zeroPayload, "after retrieving stats for a server, there should be zero stats (until more data goes through)")

+ 1 - 1
psiphon/tunnel.go

@@ -908,7 +908,7 @@ func performLivenessTest(
 	metrics := new(livenessTestMetrics)
 
 	defer func(startTime monotime.Time) {
-		metrics.Duration = fmt.Sprintf("%s", monotime.Since(startTime))
+		metrics.Duration = monotime.Since(startTime).String()
 	}(monotime.Now())
 
 	PRNG := prng.NewPRNGWithSeed(livenessTestPRNGSeed)

+ 3 - 0
psiphon/upgradeDownload.go

@@ -93,6 +93,9 @@ func DownloadUpgrade(
 		tunnel,
 		untunneledDialConfig,
 		skipVerify)
+	if err != nil {
+		return errors.Trace(err)
+	}
 
 	// If no handshake version is supplied, make an initial HEAD request
 	// to get the current version from the version header.

+ 1 - 1
psiphon/upstreamproxy/auth_basic.go

@@ -51,7 +51,7 @@ func (a *BasicHttpAuthenticator) Authenticate(req *http.Request, resp *http.Resp
 		a.state = BASIC_HTTP_AUTH_STATE_RESPONSE_GENERATED
 		return a.PreAuthenticate(req)
 	}
-	return proxyError(fmt.Errorf("Authorization is not accepted by the proxy server"))
+	return proxyError(fmt.Errorf("authorization is not accepted by the proxy server"))
 }
 
 func (a *BasicHttpAuthenticator) IsConnectionBased() bool {

+ 8 - 8
psiphon/upstreamproxy/auth_digest.go

@@ -87,7 +87,7 @@ func (d *DigestHeaders) digestChecksum() {
 	var A1 string
 	switch d.Algorithm {
 	case "MD5":
-		//HA1=MD5(username:realm:password)
+		// HA1=MD5(username:realm:password)
 		A1 = fmt.Sprintf("%s:%s:%s", d.Username, d.Realm, d.Password)
 
 	case "MD5-sess":
@@ -95,12 +95,12 @@ func (d *DigestHeaders) digestChecksum() {
 		str := fmt.Sprintf("%s:%s:%s", d.Username, d.Realm, d.Password)
 		A1 = fmt.Sprintf("%s:%s:%s", h(str), d.Nonce, d.Cnonce)
 	default:
-		//token
+		// Token
 	}
 	if A1 == "" {
 		return
 	}
-	//HA1
+	// HA1
 	d.HA1 = h(A1)
 	// HA2
 	A2 := fmt.Sprintf("%s:%s", d.Method, d.Uri)
@@ -132,18 +132,18 @@ func h(data string) string {
 
 func (a *DigestHttpAuthenticator) Authenticate(req *http.Request, resp *http.Response) error {
 	if a.state != DIGEST_HTTP_AUTH_STATE_CHALLENGE_RECEIVED {
-		return proxyError(fmt.Errorf("Authorization is not accepted by the proxy server"))
+		return proxyError(fmt.Errorf("authorization is not accepted by the proxy server"))
 	}
 	challenges, err := parseAuthChallenge(resp)
 	if err != nil {
-		//already wrapped in proxyError
+		// Already wrapped in proxyError
 		return err
 	}
 	challenge := challenges["Digest"]
 	if len(challenge) == 0 {
-		return proxyError(fmt.Errorf("Digest authentication challenge is empty"))
+		return proxyError(fmt.Errorf("digest authentication challenge is empty"))
 	}
-	//parse challenge
+	// Parse challenge
 	digestParams := map[string]string{}
 	for _, keyval := range strings.Split(challenge, ",") {
 		param := strings.SplitN(keyval, "=", 2)
@@ -153,7 +153,7 @@ func (a *DigestHttpAuthenticator) Authenticate(req *http.Request, resp *http.Res
 		digestParams[strings.Trim(param[0], "\" ")] = strings.Trim(param[1], "\" ")
 	}
 	if len(digestParams) == 0 {
-		return proxyError(fmt.Errorf("Digest authentication challenge is malformed"))
+		return proxyError(fmt.Errorf("digest authentication challenge is malformed"))
 	}
 
 	algorithm := digestParams["algorithm"]

+ 9 - 5
psiphon/upstreamproxy/auth_ntlm.go

@@ -51,9 +51,13 @@ func newNTLMAuthenticator(username, password string) *NTLMHttpAuthenticator {
 
 func (a *NTLMHttpAuthenticator) Authenticate(req *http.Request, resp *http.Response) error {
 	if a.state == NTLM_HTTP_AUTH_STATE_RESPONSE_TYPE3_GENERATED {
-		return proxyError(fmt.Errorf("Authorization is not accepted by the proxy server"))
+		return proxyError(fmt.Errorf("authorization is not accepted by the proxy server"))
 	}
 	challenges, err := parseAuthChallenge(resp)
+	if err != nil {
+		// Already wrapped in proxyError
+		return err
+	}
 
 	challenge, ok := challenges["NTLM"]
 	if challenge == "" {
@@ -62,7 +66,7 @@ func (a *NTLMHttpAuthenticator) Authenticate(req *http.Request, resp *http.Respo
 		a.state = NTLM_HTTP_AUTH_STATE_RESPONSE_TYPE1_GENERATED
 	}
 	if !ok {
-		return proxyError(fmt.Errorf("Bad proxy response, no NTLM challenge for NTLMHttpAuthenticator"))
+		return proxyError(fmt.Errorf("bad proxy response, no NTLM challenge for NTLMHttpAuthenticator"))
 	}
 
 	var ntlmMsg []byte
@@ -72,7 +76,7 @@ func (a *NTLMHttpAuthenticator) Authenticate(req *http.Request, resp *http.Respo
 		return proxyError(err)
 	}
 	if a.state == NTLM_HTTP_AUTH_STATE_CHALLENGE_RECEIVED {
-		//generate TYPE 1 message
+		// Generate TYPE 1 message
 		negotiate, err := session.GenerateNegotiateMessage()
 		if err != nil {
 			return proxyError(err)
@@ -94,7 +98,7 @@ func (a *NTLMHttpAuthenticator) Authenticate(req *http.Request, resp *http.Respo
 		}
 		challengeBytes, err := base64.StdEncoding.DecodeString(challenge)
 		if err != nil {
-			return proxyError(fmt.Errorf("NTLM challeenge base 64 decoding: %v", err))
+			return proxyError(fmt.Errorf("NTLM challenge base 64 decoding: %v", err))
 		}
 		session.SetUserInfo(NTUser, a.password, NTDomain)
 		ntlmChallenge, err := ntlm.ParseChallengeMessage(challengeBytes)
@@ -112,7 +116,7 @@ func (a *NTLMHttpAuthenticator) Authenticate(req *http.Request, resp *http.Respo
 		return nil
 	}
 
-	return proxyError(fmt.Errorf("Authorization is not accepted by the proxy server"))
+	return proxyError(fmt.Errorf("authorization is not accepted by the proxy server"))
 }
 
 func (a *NTLMHttpAuthenticator) IsConnectionBased() bool {

+ 4 - 4
psiphon/upstreamproxy/http_authenticator.go

@@ -56,7 +56,7 @@ func parseAuthChallenge(resp *http.Response) (map[string]string, error) {
 		}
 	}
 	if len(challenges) == 0 {
-		return nil, proxyError(fmt.Errorf("No valid challenges in the Proxy-Authenticate header"))
+		return nil, proxyError(fmt.Errorf("no valid challenges in the Proxy-Authenticate header"))
 	}
 	return challenges, nil
 }
@@ -65,7 +65,7 @@ func NewHttpAuthenticator(resp *http.Response, username, password string) (HttpA
 
 	challenges, err := parseAuthChallenge(resp)
 	if err != nil {
-		//Already wrapped in proxyError
+		// Already wrapped in proxyError
 		return nil, err
 	}
 
@@ -78,10 +78,10 @@ func NewHttpAuthenticator(resp *http.Response, username, password string) (HttpA
 		return newBasicAuthenticator(username, password), nil
 	}
 
-	//Unsupported scheme
+	// Unsupported scheme
 	schemes := make([]string, 0, len(challenges))
 	for scheme := range challenges {
 		schemes = append(schemes, scheme)
 	}
-	return nil, proxyError(fmt.Errorf("Unsupported proxy authentication scheme in %v", schemes))
+	return nil, proxyError(fmt.Errorf("unsupported proxy authentication scheme in %v", schemes))
 }

+ 18 - 18
psiphon/upstreamproxy/proxy_http.go

@@ -92,7 +92,7 @@ func (hp *httpProxy) Dial(network, addr string) (net.Conn, error) {
 	}
 	err := pc.makeNewClientConn()
 	if err != nil {
-		//Already wrapped in proxyError
+		// Already wrapped in proxyError
 		return nil, err
 	}
 
@@ -100,31 +100,27 @@ handshakeLoop:
 	for {
 		err := pc.handshake(addr, hp.username, hp.password)
 		if err != nil {
-			//already wrapped in proxyError
+			// Already wrapped in proxyError
 			return nil, err
 		}
 		switch pc.authState {
 		case HTTP_AUTH_STATE_SUCCESS:
 			pc.hijackedConn, pc.staleReader = pc.httpClientConn.Hijack()
 			return pc, nil
-		case HTTP_AUTH_STATE_FAILURE:
-			//err already wrapped in proxyError
-			return nil, err
 		case HTTP_AUTH_STATE_CHALLENGED:
 			continue
 		default:
 			break handshakeLoop
 		}
 	}
-	return nil, proxyError(fmt.Errorf("Unknown handshake error"))
-
+	return nil, proxyError(fmt.Errorf("unknown handshake error in state %v", pc.authState))
 }
 
 type proxyConn struct {
 	dialFn         DialFunc
 	proxyAddr      string
 	customHeaders  http.Header
-	httpClientConn *httputil.ClientConn
+	httpClientConn *httputil.ClientConn //lint:ignore SA1019 httputil.ClientConn used for client-side hijack
 	hijackedConn   net.Conn
 	staleReader    *bufio.Reader
 	authResponse   *http.Response
@@ -139,7 +135,7 @@ func (pc *proxyConn) handshake(addr, username, password string) error {
 	if err != nil {
 		pc.httpClientConn.Close()
 		pc.authState = HTTP_AUTH_STATE_FAILURE
-		return proxyError(fmt.Errorf("Failed to parse proxy address: %v", err))
+		return proxyError(fmt.Errorf("failed to parse proxy address: %v", err))
 	}
 	reqURL.Scheme = ""
 
@@ -147,7 +143,7 @@ func (pc *proxyConn) handshake(addr, username, password string) error {
 	if err != nil {
 		pc.httpClientConn.Close()
 		pc.authState = HTTP_AUTH_STATE_FAILURE
-		return proxyError(fmt.Errorf("Create proxy request: %v", err))
+		return proxyError(fmt.Errorf("create proxy request: %v", err))
 	}
 	req.Close = false
 	req.Header.Set("User-Agent", "")
@@ -172,14 +168,17 @@ func (pc *proxyConn) handshake(addr, username, password string) error {
 		err := pc.authenticator.Authenticate(req, pc.authResponse)
 		if err != nil {
 			pc.authState = HTTP_AUTH_STATE_FAILURE
-			//Already wrapped in proxyError
+			// Already wrapped in proxyError
 			return err
 		}
 	}
 
 	resp, err := pc.httpClientConn.Do(req)
 
-	if err != nil && err != httputil.ErrPersistEOF {
+	//lint:ignore SA1019 httputil.ClientConn used for client-side hijack
+	errPersistEOF := httputil.ErrPersistEOF
+
+	if err != nil && err != errPersistEOF {
 		pc.httpClientConn.Close()
 		pc.authState = HTTP_AUTH_STATE_FAILURE
 		return proxyError(fmt.Errorf("making proxy request: %v", err))
@@ -197,7 +196,7 @@ func (pc *proxyConn) handshake(addr, username, password string) error {
 			if authErr != nil {
 				pc.httpClientConn.Close()
 				pc.authState = HTTP_AUTH_STATE_FAILURE
-				//Already wrapped in proxyError
+				// Already wrapped in proxyError
 				return authErr
 			}
 		}
@@ -207,22 +206,22 @@ func (pc *proxyConn) handshake(addr, username, password string) error {
 		if username == "" {
 			pc.httpClientConn.Close()
 			pc.authState = HTTP_AUTH_STATE_FAILURE
-			return proxyError(fmt.Errorf("No username credentials provided for proxy auth"))
+			return proxyError(fmt.Errorf("ho username credentials provided for proxy auth"))
 		}
-		if err == httputil.ErrPersistEOF {
-			// the server may send Connection: close,
+		if err == errPersistEOF {
+			// The server may send Connection: close,
 			// at this point we just going to create a new
 			// ClientConn and continue the handshake
 			err = pc.makeNewClientConn()
 			if err != nil {
-				//Already wrapped in proxyError
+				// Already wrapped in proxyError
 				return err
 			}
 		}
 		return nil
 	}
 	pc.authState = HTTP_AUTH_STATE_FAILURE
-	return proxyError(fmt.Errorf("Handshake error: %v, response status: %s", err, resp.Status))
+	return proxyError(fmt.Errorf("handshake error: %v, response status: %s", err, resp.Status))
 }
 
 func (pc *proxyConn) makeNewClientConn() error {
@@ -233,6 +232,7 @@ func (pc *proxyConn) makeNewClientConn() error {
 	if err != nil {
 		return proxyError(fmt.Errorf("makeNewClientConn: %v", err))
 	}
+	//lint:ignore SA1019 httputil.ClientConn used for client-side hijack
 	pc.httpClientConn = httputil.NewClientConn(c, nil)
 	return nil
 }

+ 1 - 1
psiphon/utils.go

@@ -106,7 +106,7 @@ func IsAddressInUseError(err error) bool {
 			}
 			// Special case for Windows (WSAEADDRINUSE = 10048)
 			if errno, ok := err.Err.(syscall.Errno); ok {
-				if 10048 == int(errno) {
+				if int(errno) == 10048 {
 					return true
 				}
 			}