/* * Copyright (c) 2020, 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 . * */ package server import ( "bytes" "context" "crypto/tls" "crypto/x509" "encoding/json" "errors" "fmt" "io/ioutil" "net" "net/http" "os" "path/filepath" "sync" "sync/atomic" "testing" "time" "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon" "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common" "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng" "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol" ) func TestPassthrough(t *testing.T) { testPassthrough(t, false) } func TestLegacyPassthrough(t *testing.T) { testPassthrough(t, true) } func testPassthrough(t *testing.T, legacy bool) { psiphon.SetEmitDiagnosticNotices(true, true) // Run passthrough web server webServerCertificate, webServerPrivateKey, err := common.GenerateWebServerCertificate("example.org") if err != nil { t.Fatalf("common.GenerateWebServerCertificate failed: %s", err) } webListener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("net.Listen failed: %s", err) } defer webListener.Close() webCertificate, err := tls.X509KeyPair( []byte(webServerCertificate), []byte(webServerPrivateKey)) if err != nil { t.Fatalf("tls.X509KeyPair failed: %s", err) } webListener = tls.NewListener(webListener, &tls.Config{ Certificates: []tls.Certificate{webCertificate}, }) webServerAddress := webListener.Addr().String() webResponseBody := []byte(prng.HexString(32)) webServer := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.Write(webResponseBody) }) go func() { http.Serve(webListener, webServer) }() // Run Psiphon server tunnelProtocol := protocol.TUNNEL_PROTOCOL_UNFRONTED_MEEK_SESSION_TICKET generateConfigParams := &GenerateConfigParams{ ServerIPAddress: "127.0.0.1", EnableSSHAPIRequests: true, WebServerPort: 8000, TunnelProtocolPorts: map[string]int{tunnelProtocol: 4000}, Passthrough: true, LegacyPassthrough: legacy, } serverConfigJSON, _, _, _, encodedServerEntry, err := GenerateConfig(generateConfigParams) if err != nil { t.Fatalf("error generating server config: %s", err) } var serverConfig map[string]interface{} json.Unmarshal(serverConfigJSON, &serverConfig) serverConfig["LogFilename"] = filepath.Join(testDataDirName, "psiphond.log") serverConfig["LogLevel"] = "debug" serverConfig["TunnelProtocolPassthroughAddresses"] = map[string]string{tunnelProtocol: webServerAddress} serverConfigJSON, _ = json.Marshal(serverConfig) serverWaitGroup := new(sync.WaitGroup) serverWaitGroup.Add(1) go func() { defer serverWaitGroup.Done() err := RunServices(serverConfigJSON) if err != nil { t.Errorf("error running server: %s", err) } }() defer func() { p, _ := os.FindProcess(os.Getpid()) p.Signal(os.Interrupt) serverWaitGroup.Wait() }() // TODO: monitor logs for more robust wait-until-loaded. time.Sleep(1 * time.Second) // Test: normal client connects successfully clientConfigJSON := fmt.Sprintf(` { "DataRootDirectory" : "%s", "ClientPlatform" : "Windows", "ClientVersion" : "0", "SponsorId" : "0", "PropagationChannelId" : "0", "TargetServerEntry" : "%s" }`, testDataDirName, string(encodedServerEntry)) clientConfig, err := psiphon.LoadConfig([]byte(clientConfigJSON)) if err != nil { t.Fatalf("error processing configuration file: %s", err) } err = clientConfig.Commit(false) if err != nil { t.Fatalf("error committing configuration file: %s", err) } err = psiphon.OpenDataStore(clientConfig) if err != nil { t.Fatalf("error initializing client datastore: %s", err) } defer psiphon.CloseDataStore() controller, err := psiphon.NewController(clientConfig) if err != nil { t.Fatalf("error creating client controller: %s", err) } tunnelEstablished := make(chan struct{}, 1) psiphon.SetNoticeWriter(psiphon.NewNoticeReceiver( func(notice []byte) { noticeType, payload, err := psiphon.GetNotice(notice) if err != nil { return } if noticeType == "Tunnels" { count := int(payload["count"].(float64)) if count >= 1 { tunnelEstablished <- struct{}{} } } })) ctx, cancelFunc := context.WithCancel(context.Background()) controllerWaitGroup := new(sync.WaitGroup) controllerWaitGroup.Add(1) go func() { defer controllerWaitGroup.Done() controller.Run(ctx) }() <-tunnelEstablished cancelFunc() controllerWaitGroup.Wait() // Test: passthrough // Non-psiphon HTTPS request routed to passthrough web server verifiedCertificate := int32(0) httpClient := &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*x509.Certificate) error { if len(rawCerts) < 1 { return errors.New("no certificate to verify") } if !bytes.Equal(rawCerts[0], []byte(webCertificate.Certificate[0])) { return errors.New("unexpected certificate") } atomic.StoreInt32(&verifiedCertificate, 1) return nil }, }, }, } response, err := httpClient.Get("https://" + webServerAddress) if err != nil { t.Fatalf("http.Get failed: %s", err) } defer response.Body.Close() if atomic.LoadInt32(&verifiedCertificate) != 1 { t.Fatalf("certificate not verified") } if response.StatusCode != http.StatusOK { t.Fatalf("unexpected response.StatusCode: %d", response.StatusCode) } responseBody, err := ioutil.ReadAll(response.Body) if err != nil { t.Fatalf("ioutil.ReadAll failed: %s", err) } if !bytes.Equal(responseBody, webResponseBody) { t.Fatalf("unexpected responseBody: %s", string(responseBody)) } }