github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/server/sessionID_test.go (about)

     1  /*
     2   * Copyright (c) 2016, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License as published by
     7   * the Free Software Foundation, either version 3 of the License, or
     8   * (at your option) any later version.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package server
    21  
    22  import (
    23  	"context"
    24  	"encoding/json"
    25  	"fmt"
    26  	"io/ioutil"
    27  	"os"
    28  	"path/filepath"
    29  	"strings"
    30  	"sync"
    31  	"testing"
    32  	"time"
    33  
    34  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
    35  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
    36  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
    37  )
    38  
    39  func TestDuplicateSessionID(t *testing.T) {
    40  
    41  	testDataDirName, err := ioutil.TempDir("", "psiphond-duplicate-session-id-test")
    42  	if err != nil {
    43  		t.Fatalf("TempDir failed: %s", err)
    44  	}
    45  	defer os.RemoveAll(testDataDirName)
    46  
    47  	psiphon.SetEmitDiagnosticNotices(true, true)
    48  
    49  	// Configure server
    50  
    51  	generateConfigParams := &GenerateConfigParams{
    52  		ServerIPAddress:      "127.0.0.1",
    53  		EnableSSHAPIRequests: true,
    54  		WebServerPort:        8000,
    55  		TunnelProtocolPorts:  map[string]int{"OSSH": 4000},
    56  	}
    57  
    58  	serverConfigJSON, _, _, _, encodedServerEntry, err := GenerateConfig(generateConfigParams)
    59  	if err != nil {
    60  		t.Fatalf("error generating server config: %s", err)
    61  	}
    62  
    63  	var serverConfig map[string]interface{}
    64  	json.Unmarshal(serverConfigJSON, &serverConfig)
    65  
    66  	serverConfig["LogFilename"] = filepath.Join(testDataDirName, "psiphond.log")
    67  	serverConfig["LogLevel"] = "debug"
    68  
    69  	serverConfigJSON, _ = json.Marshal(serverConfig)
    70  
    71  	numConcurrentClients := 50
    72  
    73  	stoppingEvent := "stopping existing client with duplicate session ID"
    74  	abortingEvent := "aborting new client with duplicate session ID"
    75  
    76  	// Sufficiently buffer channel so log callback handler doesn't cause server
    77  	// operations to block while handling concurrent clients.
    78  	duplicateSessionIDEvents := make(chan string, numConcurrentClients)
    79  
    80  	setLogCallback(func(log []byte) {
    81  		strLog := string(log)
    82  		var event string
    83  		if strings.Contains(strLog, stoppingEvent) {
    84  			event = stoppingEvent
    85  		} else if strings.Contains(strLog, abortingEvent) {
    86  			event = abortingEvent
    87  		}
    88  		if event != "" {
    89  			select {
    90  			case duplicateSessionIDEvents <- event:
    91  			default:
    92  			}
    93  		}
    94  
    95  	})
    96  
    97  	// Run server
    98  
    99  	serverWaitGroup := new(sync.WaitGroup)
   100  	serverWaitGroup.Add(1)
   101  	go func() {
   102  		defer serverWaitGroup.Done()
   103  		err := RunServices(serverConfigJSON)
   104  		if err != nil {
   105  			t.Errorf("error running server: %s", err)
   106  		}
   107  	}()
   108  
   109  	defer func() {
   110  		p, _ := os.FindProcess(os.Getpid())
   111  		p.Signal(os.Interrupt)
   112  		serverWaitGroup.Wait()
   113  	}()
   114  
   115  	// TODO: monitor logs for more robust wait-until-loaded.
   116  	time.Sleep(1 * time.Second)
   117  
   118  	// Initialize tunnel clients. Bypassing Controller and using Tunnel directly
   119  	// to permit multiple concurrent clients.
   120  	//
   121  	// Limitation: all tunnels still use one singleton datastore and notice
   122  	// handler.
   123  
   124  	psiphon.SetNoticeWriter(ioutil.Discard)
   125  
   126  	clientConfigJSONTemplate := `
   127      {
   128          "DataRootDirectory" : "%s",
   129          "SponsorId" : "0",
   130          "PropagationChannelId" : "0",
   131          "SessionID" : "00000000000000000000000000000000"
   132      }`
   133  
   134  	clientConfigJSON := fmt.Sprintf(
   135  		clientConfigJSONTemplate,
   136  		testDataDirName)
   137  
   138  	clientConfig, err := psiphon.LoadConfig([]byte(clientConfigJSON))
   139  	if err != nil {
   140  		t.Fatalf("LoadConfig failed: %s", err)
   141  	}
   142  	err = clientConfig.Commit(false)
   143  	if err != nil {
   144  		t.Fatalf("Commit failed: %s", err)
   145  	}
   146  
   147  	resolver := psiphon.NewResolver(clientConfig, true)
   148  	defer resolver.Stop()
   149  	clientConfig.SetResolver(resolver)
   150  
   151  	err = psiphon.OpenDataStore(clientConfig)
   152  	if err != nil {
   153  		t.Fatalf("OpenDataStore failed: %s", err)
   154  	}
   155  	defer psiphon.CloseDataStore()
   156  
   157  	serverEntry, err := protocol.DecodeServerEntry(
   158  		string(encodedServerEntry),
   159  		common.GetCurrentTimestamp(),
   160  		protocol.SERVER_ENTRY_SOURCE_EMBEDDED)
   161  	if err != nil {
   162  		t.Fatalf("DecodeServerEntry failed: %s", err)
   163  	}
   164  
   165  	dialTunnel := func(ctx context.Context) *psiphon.Tunnel {
   166  
   167  		dialParams, err := psiphon.MakeDialParameters(
   168  			clientConfig,
   169  			nil,
   170  			func(_ *protocol.ServerEntry, _ string) bool { return false },
   171  			func(_ *protocol.ServerEntry) (string, bool) { return "OSSH", true },
   172  			serverEntry,
   173  			false,
   174  			0,
   175  			0)
   176  		if err != nil {
   177  			t.Fatalf("MakeDialParameters failed: %s", err)
   178  		}
   179  
   180  		tunnel, err := psiphon.ConnectTunnel(
   181  			ctx,
   182  			clientConfig,
   183  			time.Now(),
   184  			dialParams)
   185  		if err != nil {
   186  			t.Fatalf("ConnectTunnel failed: %s", err)
   187  		}
   188  
   189  		return tunnel
   190  	}
   191  
   192  	handshakeTunnel := func(tunnel *psiphon.Tunnel, expectSuccess bool) {
   193  
   194  		_, err = psiphon.NewServerContext(tunnel)
   195  
   196  		if expectSuccess && err != nil || (!expectSuccess && err == nil) {
   197  			t.Fatalf("Unexpected handshake result: %s", err)
   198  		}
   199  	}
   200  
   201  	ctx, cancelFunc := context.WithCancel(context.Background())
   202  	defer cancelFunc()
   203  
   204  	// Test: normal case
   205  	//
   206  	// First tunnel, t1, fully establishes and then is superceded by new tunnel, t2.
   207  
   208  	t1 := dialTunnel(ctx)
   209  
   210  	handshakeTunnel(t1, true)
   211  
   212  	t2 := dialTunnel(ctx)
   213  
   214  	expectEvent := <-duplicateSessionIDEvents
   215  
   216  	if expectEvent != stoppingEvent {
   217  		t.Fatalf("Unexpected duplicate session ID event")
   218  	}
   219  
   220  	handshakeTunnel(t2, true)
   221  
   222  	t1.Close(true)
   223  	t2.Close(true)
   224  
   225  	// Test: simultaneous/interleaved case
   226  	//
   227  	// First tunnel connects but then tries to handshake after second tunnel has
   228  	// connected.
   229  
   230  	t1 = dialTunnel(ctx)
   231  
   232  	// TODO: await log confirmation that t1 completed registerEstablishedClient?
   233  	// Otherwise, there's some small chance that t2 is the "first" tunnel and the
   234  	// test could fail (false negative).
   235  
   236  	t2 = dialTunnel(ctx)
   237  
   238  	expectEvent = <-duplicateSessionIDEvents
   239  
   240  	if expectEvent != stoppingEvent {
   241  		t.Fatalf("Unexpected duplicate session ID event")
   242  	}
   243  
   244  	handshakeTunnel(t1, false)
   245  
   246  	handshakeTunnel(t2, true)
   247  
   248  	t1.Close(true)
   249  	t2.Close(true)
   250  
   251  	// Test: 50 concurrent clients, all with the same session ID.
   252  	//
   253  	// This should be enough concurrent clients to trigger both the "stopping"
   254  	// and "aborting" duplicate session ID cases.
   255  
   256  	tunnels := make([]*psiphon.Tunnel, numConcurrentClients)
   257  
   258  	waitGroup := new(sync.WaitGroup)
   259  	for i := 0; i < numConcurrentClients; i++ {
   260  		waitGroup.Add(1)
   261  		go func(i int) {
   262  			defer waitGroup.Done()
   263  			tunnels[i] = dialTunnel(ctx)
   264  		}(i)
   265  	}
   266  	waitGroup.Wait()
   267  
   268  	for _, t := range tunnels {
   269  		if t == nil {
   270  			continue
   271  		}
   272  		t.Close(true)
   273  	}
   274  
   275  	receivedEvents := make(map[string]int)
   276  	for i := 0; i < numConcurrentClients-1; i++ {
   277  		receivedEvents[<-duplicateSessionIDEvents] += 1
   278  	}
   279  
   280  	if receivedEvents[stoppingEvent] < 1 {
   281  		t.Fatalf("No stopping events received")
   282  	}
   283  
   284  	if receivedEvents[abortingEvent] < 1 {
   285  		t.Fatalf("No aborting events received")
   286  	}
   287  }