فهرست منبع

Initial automated testing for server

Rod Hynes 9 سال پیش
والد
کامیت
09418ca59d
4فایلهای تغییر یافته به همراه209 افزوده شده و 8 حذف شده
  1. 2 2
      psiphon/config.go
  2. 1 1
      psiphon/controller_test.go
  3. 12 5
      psiphon/notice.go
  4. 194 0
      psiphon/server/server_test.go

+ 2 - 2
psiphon/config.go

@@ -387,9 +387,9 @@ func LoadConfig(configJson []byte) (*Config, error) {
 		return nil, ContextError(err)
 	}
 
-	// Do setEmitDiagnosticNotices first, to ensure config file errors are emitted.
+	// Do SetEmitDiagnosticNotices first, to ensure config file errors are emitted.
 	if config.EmitDiagnosticNotices {
-		setEmitDiagnosticNotices(true)
+		SetEmitDiagnosticNotices(true)
 	}
 
 	// These fields are required; the rest are optional

+ 1 - 1
psiphon/controller_test.go

@@ -41,7 +41,7 @@ func TestMain(m *testing.M) {
 	flag.Parse()
 	os.Remove(DATA_STORE_FILENAME)
 	initDisruptor()
-	setEmitDiagnosticNotices(true)
+	SetEmitDiagnosticNotices(true)
 	os.Exit(m.Run())
 }
 

+ 12 - 5
psiphon/notice.go

@@ -37,7 +37,12 @@ var noticeLoggerMutex sync.Mutex
 var noticeLogger = log.New(os.Stderr, "", 0)
 var noticeLogDiagnostics = int32(0)
 
-func setEmitDiagnosticNotices(enable bool) {
+// SetEmitDiagnosticNotices toggles whether diagnostic notices
+// are emitted. Diagnostic notices contain potentially sensitive
+// circumvention network information; only enable this in environments
+// where notices are handled securely (for example, don't include these
+// notices in log files which users could post to public forums).
+func SetEmitDiagnosticNotices(enable bool) {
 	if enable {
 		atomic.StoreInt32(&noticeLogDiagnostics, 1)
 	} else {
@@ -45,7 +50,9 @@ func setEmitDiagnosticNotices(enable bool) {
 	}
 }
 
-func getEmitDiagnoticNotices() bool {
+// GetEmitDiagnoticNotices returns the current state
+// of emitting diagnostic notices.
+func GetEmitDiagnoticNotices() bool {
 	return atomic.LoadInt32(&noticeLogDiagnostics) == 1
 }
 
@@ -76,7 +83,7 @@ func SetNoticeOutput(output io.Writer) {
 // outputNotice encodes a notice in JSON and writes it to the output writer.
 func outputNotice(noticeType string, isDiagnostic, showUser bool, args ...interface{}) {
 
-	if isDiagnostic && !getEmitDiagnoticNotices() {
+	if isDiagnostic && !GetEmitDiagnoticNotices() {
 		return
 	}
 
@@ -266,7 +273,7 @@ func NoticeClientUpgradeDownloaded(filename string) {
 // transferred since the last NoticeBytesTransferred, for the tunnel
 // to the server at ipAddress.
 func NoticeBytesTransferred(ipAddress string, sent, received int64) {
-	if getEmitDiagnoticNotices() {
+	if GetEmitDiagnoticNotices() {
 		outputNotice("BytesTransferred", true, false, "ipAddress", ipAddress, "sent", sent, "received", received)
 	} else {
 		// This case keeps the EmitBytesTransferred and EmitDiagnosticNotices config options independent
@@ -278,7 +285,7 @@ func NoticeBytesTransferred(ipAddress string, sent, received int64) {
 // transferred in total up to this point, for the tunnel to the server
 // at ipAddress.
 func NoticeTotalBytesTransferred(ipAddress string, sent, received int64) {
-	if getEmitDiagnoticNotices() {
+	if GetEmitDiagnoticNotices() {
 		outputNotice("TotalBytesTransferred", true, false, "ipAddress", ipAddress, "sent", sent, "received", received)
 	} else {
 		// This case keeps the EmitBytesTransferred and EmitDiagnosticNotices config options independent

+ 194 - 0
psiphon/server/server_test.go

@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2016, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package server
+
+import (
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"os"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
+)
+
+func TestMain(m *testing.M) {
+	flag.Parse()
+	os.Remove(psiphon.DATA_STORE_FILENAME)
+	psiphon.SetEmitDiagnosticNotices(true)
+	os.Exit(m.Run())
+}
+
+func TestServer(t *testing.T) {
+
+	// create a server
+
+	serverConfigFileContents, serverEntryFileContents, err := GenerateConfig(
+		&GenerateConfigParams{})
+	if err != nil {
+		t.Fatalf("error generating server config: %s", err)
+	}
+
+	// customize server config
+
+	var serverConfig interface{}
+	json.Unmarshal(serverConfigFileContents, &serverConfig)
+	serverConfig.(map[string]interface{})["GeoIPDatabaseFilename"] = ""
+	serverConfigFileContents, _ = json.Marshal(serverConfig)
+
+	// run server
+
+	serverWaitGroup := new(sync.WaitGroup)
+	serverWaitGroup.Add(1)
+	go func() {
+		defer serverWaitGroup.Done()
+		err := RunServices([][]byte{serverConfigFileContents})
+		if err != nil {
+			// TODO: wrong goroutine for t.FatalNow()
+			t.Fatalf("error running server: %s", err)
+		}
+	}()
+	defer func() {
+
+		// Test: orderly server shutdown
+
+		p, _ := os.FindProcess(os.Getpid())
+		p.Signal(os.Interrupt)
+
+		shutdownTimeout := time.NewTimer(5 * time.Second)
+
+		shutdownOk := make(chan struct{}, 1)
+		go func() {
+			serverWaitGroup.Wait()
+			shutdownOk <- *new(struct{})
+		}()
+
+		select {
+		case <-shutdownOk:
+		case <-shutdownTimeout.C:
+			t.Fatalf("server shutdown timeout exceeded")
+		}
+	}()
+
+	// connect to server with client
+
+	// TODO: currently, TargetServerEntry only works with one tunnel
+	numTunnels := 1
+	localHTTPProxyPort := 8080
+	establishTunnelPausePeriodSeconds := 1
+
+	// Note: calling LoadConfig ensures all *int config fields are initialized
+	configJson := `
+	{
+	"ClientVersion":                     "0",
+	"PropagationChannelId":              "0",
+	"SponsorId":                         "0"
+	}`
+	clientConfig, _ := psiphon.LoadConfig([]byte(configJson))
+
+	clientConfig.ConnectionWorkerPoolSize = numTunnels
+	clientConfig.TunnelPoolSize = numTunnels
+	clientConfig.DisableRemoteServerListFetcher = true
+	clientConfig.EstablishTunnelPausePeriodSeconds = &establishTunnelPausePeriodSeconds
+	clientConfig.TargetServerEntry = string(serverEntryFileContents)
+	clientConfig.TunnelProtocol = "OSSH"
+	clientConfig.LocalHttpProxyPort = localHTTPProxyPort
+
+	err = psiphon.InitDataStore(clientConfig)
+	if err != nil {
+		t.Fatalf("error initializing client datastore: %s", err)
+	}
+
+	controller, err := psiphon.NewController(clientConfig)
+	if err != nil {
+		t.Fatalf("error creating client controller: %s", err)
+	}
+
+	tunnelsEstablished := make(chan struct{}, 1)
+
+	psiphon.SetNoticeOutput(psiphon.NewNoticeReceiver(
+		func(notice []byte) {
+
+			fmt.Printf("%s\n", string(notice))
+
+			noticeType, payload, err := psiphon.GetNotice(notice)
+			if err != nil {
+				return
+			}
+
+			switch noticeType {
+			case "Tunnels":
+				count := int(payload["count"].(float64))
+				if count >= numTunnels {
+					select {
+					case tunnelsEstablished <- *new(struct{}):
+					default:
+					}
+				}
+			}
+		}))
+
+	go func() {
+		shutdownBroadcast := make(chan struct{})
+		controller.Run(shutdownBroadcast)
+	}()
+
+	// Test: tunnels must be established within 30 seconds
+
+	establishTimeout := time.NewTimer(30 * time.Second)
+	select {
+	case <-tunnelsEstablished:
+	case <-establishTimeout.C:
+		t.Fatalf("tunnel establish timeout exceeded")
+	}
+
+	// Test: tunneled web site fetch
+
+	testUrl := "https://psiphon.ca"
+	roundTripTimeout := 30 * time.Second
+
+	proxyUrl, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", localHTTPProxyPort))
+	if err != nil {
+		t.Fatalf("error initializing proxied HTTP request: %s", err)
+	}
+
+	httpClient := &http.Client{
+		Transport: &http.Transport{
+			Proxy: http.ProxyURL(proxyUrl),
+		},
+		Timeout: roundTripTimeout,
+	}
+
+	response, err := httpClient.Get(testUrl)
+	if err != nil {
+		t.Fatalf("error sending proxied HTTP request: %s", err)
+	}
+
+	_, err = ioutil.ReadAll(response.Body)
+	if err != nil {
+		t.Fatalf("error reading proxied HTTP response: %s", err)
+	}
+	response.Body.Close()
+}