Skip to content

Commit

Permalink
Merge pull request #178 from rod-hynes/master
Browse files Browse the repository at this point in the history
Initial automated testing for server
  • Loading branch information
rod-hynes committed May 25, 2016
2 parents eb1a53d + 09418ca commit 77fab8f
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 8 deletions.
4 changes: 2 additions & 2 deletions psiphon/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion psiphon/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}

Expand Down
17 changes: 12 additions & 5 deletions psiphon/notice.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,22 @@ 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 {
atomic.StoreInt32(&noticeLogDiagnostics, 0)
}
}

func getEmitDiagnoticNotices() bool {
// GetEmitDiagnoticNotices returns the current state
// of emitting diagnostic notices.
func GetEmitDiagnoticNotices() bool {
return atomic.LoadInt32(&noticeLogDiagnostics) == 1
}

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
194 changes: 194 additions & 0 deletions psiphon/server/server_test.go
Original file line number Diff line number Diff line change
@@ -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()
}

0 comments on commit 77fab8f

Please sign in to comment.