func Start( configJson, embeddedServerEntryList string, provider PsiphonProvider, useDeviceBinder bool) error { if controller != nil { return fmt.Errorf("already started") } config, err := psiphon.LoadConfig([]byte(configJson)) if err != nil { return fmt.Errorf("error loading configuration file: %s", err) } config.NetworkConnectivityChecker = provider if useDeviceBinder { config.DeviceBinder = provider config.DnsServerGetter = provider } psiphon.SetNoticeOutput(psiphon.NewNoticeReceiver( func(notice []byte) { provider.Notice(string(notice)) })) // TODO: should following errors be Notices? err = psiphon.InitDataStore(config) if err != nil { return fmt.Errorf("error initializing datastore: %s", err) } serverEntries, err := psiphon.DecodeAndValidateServerEntryList( embeddedServerEntryList, psiphon.GetCurrentTimestamp(), psiphon.SERVER_ENTRY_SOURCE_EMBEDDED) if err != nil { return fmt.Errorf("error decoding embedded server entry list: %s", err) } err = psiphon.StoreServerEntries(serverEntries, false) if err != nil { return fmt.Errorf("error storing embedded server entry list: %s", err) } controller, err = psiphon.NewController(config) if err != nil { return fmt.Errorf("error initializing controller: %s", err) } shutdownBroadcast = make(chan struct{}) controllerWaitGroup = new(sync.WaitGroup) controllerWaitGroup.Add(1) go func() { defer controllerWaitGroup.Done() controller.Run(shutdownBroadcast) }() return nil }
// {{{ Run Psiphon func runPsiphon() { // Initialize default Notice output psiphon.SetNoticeOutput(psiphonPipe.w) //TODO: Embedded server entries // {{{ Load and parse config file config, err := psiphon.LoadConfig(PSIPHON_CONFIG) if err != nil { psiphon.NoticeError("error processing configuration file: %s", err) os.Exit(1) } // }}} // {{{ Initialize data store err = psiphon.InitDataStore(config) if err != nil { psiphon.NoticeError("error initializing datastore: %s", err) os.Exit(1) } // }}} controller, err := psiphon.NewController(config) if err != nil { psiphon.NoticeError("error creating controller: %s", err) os.Exit(1) } shutdownBroadcast = make(chan struct{}) controllerWaitGroup = new(sync.WaitGroup) controllerWaitGroup.Add(1) go func() { defer controllerWaitGroup.Done() controller.Run(shutdownBroadcast) }() }
func main() { // Define command-line parameters var configFilename string flag.StringVar(&configFilename, "config", "", "configuration input file") var embeddedServerEntryListFilename string flag.StringVar(&embeddedServerEntryListFilename, "serverList", "", "embedded server entry list input file") var formatNotices bool flag.BoolVar(&formatNotices, "formatNotices", false, "emit notices in human-readable format") var profileFilename string flag.StringVar(&profileFilename, "profile", "", "CPU profile output file") var interfaceName string flag.StringVar(&interfaceName, "listenInterface", "", "Interface Name") flag.Parse() // Initialize default Notice output (stderr) var noticeWriter io.Writer noticeWriter = os.Stderr if formatNotices { noticeWriter = psiphon.NewNoticeConsoleRewriter(noticeWriter) } psiphon.SetNoticeOutput(noticeWriter) // Handle required config file parameter if configFilename == "" { psiphon.NoticeError("configuration file is required") os.Exit(1) } configFileContents, err := ioutil.ReadFile(configFilename) if err != nil { psiphon.NoticeError("error loading configuration file: %s", err) os.Exit(1) } config, err := psiphon.LoadConfig(configFileContents) if err != nil { psiphon.NoticeError("error processing configuration file: %s", err) os.Exit(1) } // When a logfile is configured, reinitialize Notice output if config.LogFilename != "" { logFile, err := os.OpenFile(config.LogFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) if err != nil { psiphon.NoticeError("error opening log file: %s", err) os.Exit(1) } defer logFile.Close() var noticeWriter io.Writer noticeWriter = logFile if formatNotices { noticeWriter = psiphon.NewNoticeConsoleRewriter(noticeWriter) } psiphon.SetNoticeOutput(noticeWriter) } // Handle optional profiling parameter if profileFilename != "" { profileFile, err := os.Create(profileFilename) if err != nil { psiphon.NoticeError("error opening profile file: %s", err) os.Exit(1) } pprof.StartCPUProfile(profileFile) defer pprof.StopCPUProfile() } // Initialize data store err = psiphon.InitDataStore(config) if err != nil { psiphon.NoticeError("error initializing datastore: %s", err) os.Exit(1) } // Handle optional embedded server list file parameter // If specified, the embedded server list is loaded and stored. When there // are no server candidates at all, we wait for this import to complete // before starting the Psiphon controller. Otherwise, we import while // concurrently starting the controller to minimize delay before attempting // to connect to existing candidate servers. // If the import fails, an error notice is emitted, but the controller is // still started: either existing candidate servers may suffice, or the // remote server list fetch may obtain candidate servers. if embeddedServerEntryListFilename != "" { embeddedServerListWaitGroup := new(sync.WaitGroup) embeddedServerListWaitGroup.Add(1) go func() { defer embeddedServerListWaitGroup.Done() serverEntryList, err := ioutil.ReadFile(embeddedServerEntryListFilename) if err != nil { psiphon.NoticeError("error loading embedded server entry list file: %s", err) return } // TODO: stream embedded server list data? also, the cast makes an unnecessary copy of a large buffer? serverEntries, err := psiphon.DecodeAndValidateServerEntryList(string(serverEntryList)) if err != nil { psiphon.NoticeError("error decoding embedded server entry list file: %s", err) return } // Since embedded server list entries may become stale, they will not // overwrite existing stored entries for the same server. err = psiphon.StoreServerEntries(serverEntries, false) if err != nil { psiphon.NoticeError("error storing embedded server entry list data: %s", err) return } }() if psiphon.CountServerEntries(config.EgressRegion, config.TunnelProtocol) == 0 { embeddedServerListWaitGroup.Wait() } else { defer embeddedServerListWaitGroup.Wait() } } if interfaceName != "" { config.ListenInterface = interfaceName } // Run Psiphon controller, err := psiphon.NewController(config) if err != nil { psiphon.NoticeError("error creating controller: %s", err) os.Exit(1) } controllerStopSignal := make(chan struct{}, 1) shutdownBroadcast := make(chan struct{}) controllerWaitGroup := new(sync.WaitGroup) controllerWaitGroup.Add(1) go func() { defer controllerWaitGroup.Done() controller.Run(shutdownBroadcast) controllerStopSignal <- *new(struct{}) }() // Wait for an OS signal or a Run stop signal, then stop Psiphon and exit systemStopSignal := make(chan os.Signal, 1) signal.Notify(systemStopSignal, os.Interrupt, os.Kill) select { case <-systemStopSignal: psiphon.NoticeInfo("shutdown by system") close(shutdownBroadcast) controllerWaitGroup.Wait() case <-controllerStopSignal: psiphon.NoticeInfo("shutdown by controller") } }
func Start( configJson, embeddedServerEntryList string, provider PsiphonProvider, useDeviceBinder bool) error { if controller != nil { return fmt.Errorf("already started") } config, err := psiphon.LoadConfig([]byte(configJson)) if err != nil { return fmt.Errorf("error loading configuration file: %s", err) } config.NetworkConnectivityChecker = provider if useDeviceBinder { config.DeviceBinder = provider config.DnsServerGetter = provider } psiphon.SetNoticeOutput(psiphon.NewNoticeReceiver( func(notice []byte) { provider.Notice(string(notice)) })) // TODO: should following errors be Notices? err = psiphon.InitDataStore(config) if err != nil { return fmt.Errorf("error initializing datastore: %s", err) } // If specified, the embedded server list is loaded and stored. When there // are no server candidates at all, we wait for this import to complete // before starting the Psiphon controller. Otherwise, we import while // concurrently starting the controller to minimize delay before attempting // to connect to existing candidate servers. // If the import fails, an error notice is emitted, but the controller is // still started: either existing candidate servers may suffice, or the // remote server list fetch may obtain candidate servers. // TODO: duplicates logic in psiphonClient.go -- refactor? if embeddedServerEntryList != "" { embeddedServerListWaitGroup := new(sync.WaitGroup) embeddedServerListWaitGroup.Add(1) go func() { defer embeddedServerListWaitGroup.Done() // TODO: stream embedded server list data? serverEntries, err := psiphon.DecodeAndValidateServerEntryList(embeddedServerEntryList) if err != nil { psiphon.NoticeError("error decoding embedded server entry list file: %s", err) return } // Since embedded server list entries may become stale, they will not // overwrite existing stored entries for the same server. err = psiphon.StoreServerEntries(serverEntries, false) if err != nil { psiphon.NoticeError("error storing embedded server entry list data: %s", err) return } }() if psiphon.CountServerEntries(config.EgressRegion, config.TunnelProtocol) == 0 { embeddedServerListWaitGroup.Wait() } else { defer embeddedServerListWaitGroup.Wait() } } controller, err = psiphon.NewController(config) if err != nil { return fmt.Errorf("error initializing controller: %s", err) } shutdownBroadcast = make(chan struct{}) controllerWaitGroup = new(sync.WaitGroup) controllerWaitGroup.Add(1) go func() { defer controllerWaitGroup.Done() controller.Run(shutdownBroadcast) }() return nil }
func runServer(t *testing.T, runConfig *runServerConfig) { // create a server var err error serverIPaddress := "" for _, interfaceName := range []string{"eth0", "en0"} { serverIPaddress, err = common.GetInterfaceIPAddress(interfaceName) if err == nil { break } } if err != nil { t.Fatalf("error getting server IP address: %s", err) } serverConfigJSON, _, encodedServerEntry, err := GenerateConfig( &GenerateConfigParams{ ServerIPAddress: serverIPaddress, EnableSSHAPIRequests: runConfig.enableSSHAPIRequests, WebServerPort: 8000, TunnelProtocolPorts: map[string]int{runConfig.tunnelProtocol: 4000}, }) if err != nil { t.Fatalf("error generating server config: %s", err) } // customize server config // Pave psinet with random values to test handshake homepages. psinetFilename := filepath.Join(testDataDirName, "psinet.json") sponsorID, expectedHomepageURL := pavePsinetDatabaseFile(t, psinetFilename) // Pave traffic rules file which exercises handshake parameter filtering. Client // must handshake with specified sponsor ID in order to allow ports for tunneled // requests. trafficRulesFilename := filepath.Join(testDataDirName, "traffic_rules.json") paveTrafficRulesFile(t, trafficRulesFilename, sponsorID, runConfig.denyTrafficRules) oslConfigFilename := filepath.Join(testDataDirName, "osl_config.json") propagationChannelID := paveOSLConfigFile(t, oslConfigFilename) var serverConfig map[string]interface{} json.Unmarshal(serverConfigJSON, &serverConfig) serverConfig["GeoIPDatabaseFilename"] = "" serverConfig["PsinetDatabaseFilename"] = psinetFilename serverConfig["TrafficRulesFilename"] = trafficRulesFilename serverConfig["OSLConfigFilename"] = oslConfigFilename serverConfig["LogLevel"] = "error" serverConfigJSON, _ = json.Marshal(serverConfig) // run server serverWaitGroup := new(sync.WaitGroup) serverWaitGroup.Add(1) go func() { defer serverWaitGroup.Done() err := RunServices(serverConfigJSON) 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") } }() // TODO: monitor logs for more robust wait-until-loaded time.Sleep(1 * time.Second) // Test: hot reload (of psinet and traffic rules) if runConfig.doHotReload { // Pave a new psinet and traffic rules with different random values. sponsorID, expectedHomepageURL = pavePsinetDatabaseFile(t, psinetFilename) paveTrafficRulesFile(t, trafficRulesFilename, sponsorID, runConfig.denyTrafficRules) p, _ := os.FindProcess(os.Getpid()) p.Signal(syscall.SIGUSR1) // TODO: monitor logs for more robust wait-until-reloaded time.Sleep(1 * time.Second) // After reloading psinet, the new sponsorID/expectedHomepageURL // should be active, as tested in the client "Homepage" notice // handler below. } // Exercise server_load logging p, _ := os.FindProcess(os.Getpid()) p.Signal(syscall.SIGUSR2) // connect to server with client // TODO: currently, TargetServerEntry only works with one tunnel numTunnels := 1 localSOCKSProxyPort := 1081 localHTTPProxyPort := 8081 establishTunnelPausePeriodSeconds := 1 // Note: calling LoadConfig ensures all *int config fields are initialized clientConfigJSON := ` { "ClientPlatform" : "Windows", "ClientVersion" : "0", "SponsorId" : "0", "PropagationChannelId" : "0", "DisableRemoteServerListFetcher" : true }` clientConfig, _ := psiphon.LoadConfig([]byte(clientConfigJSON)) clientConfig.SponsorId = sponsorID clientConfig.PropagationChannelId = propagationChannelID clientConfig.ConnectionWorkerPoolSize = numTunnels clientConfig.TunnelPoolSize = numTunnels clientConfig.EstablishTunnelPausePeriodSeconds = &establishTunnelPausePeriodSeconds clientConfig.TargetServerEntry = string(encodedServerEntry) clientConfig.TunnelProtocol = runConfig.tunnelProtocol clientConfig.LocalSocksProxyPort = localSOCKSProxyPort clientConfig.LocalHttpProxyPort = localHTTPProxyPort clientConfig.EmitSLOKs = true if runConfig.doClientVerification { clientConfig.ClientPlatform = "Android" } clientConfig.DataStoreDirectory = testDataDirName 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) homepageReceived := make(chan struct{}, 1) slokSeeded := make(chan struct{}, 1) verificationRequired := make(chan struct{}, 1) verificationCompleted := 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": // Do not set verification payload until tunnel is // established. Otherwise will silently take no action. controller.SetClientVerificationPayloadForActiveTunnels("") count := int(payload["count"].(float64)) if count >= numTunnels { sendNotificationReceived(tunnelsEstablished) } case "Homepage": homepageURL := payload["url"].(string) if homepageURL != expectedHomepageURL { // TODO: wrong goroutine for t.FatalNow() t.Fatalf("unexpected homepage: %s", homepageURL) } sendNotificationReceived(homepageReceived) case "SLOKSeeded": sendNotificationReceived(slokSeeded) case "ClientVerificationRequired": sendNotificationReceived(verificationRequired) controller.SetClientVerificationPayloadForActiveTunnels(dummyClientVerificationPayload) case "NoticeClientVerificationRequestCompleted": sendNotificationReceived(verificationCompleted) } })) controllerShutdownBroadcast := make(chan struct{}) controllerWaitGroup := new(sync.WaitGroup) controllerWaitGroup.Add(1) go func() { defer controllerWaitGroup.Done() controller.Run(controllerShutdownBroadcast) }() defer func() { close(controllerShutdownBroadcast) shutdownTimeout := time.NewTimer(20 * time.Second) shutdownOk := make(chan struct{}, 1) go func() { controllerWaitGroup.Wait() shutdownOk <- *new(struct{}) }() select { case <-shutdownOk: case <-shutdownTimeout.C: t.Fatalf("controller shutdown timeout exceeded") } }() // Test: tunnels must be established, and correct homepage // must be received, within 30 seconds timeoutSignal := make(chan struct{}) go func() { timer := time.NewTimer(30 * time.Second) <-timer.C close(timeoutSignal) }() waitOnNotification(t, tunnelsEstablished, timeoutSignal, "tunnel establish timeout exceeded") waitOnNotification(t, homepageReceived, timeoutSignal, "homepage received timeout exceeded") if runConfig.doClientVerification { waitOnNotification(t, verificationRequired, timeoutSignal, "verification required timeout exceeded") waitOnNotification(t, verificationCompleted, timeoutSignal, "verification completed timeout exceeded") } if runConfig.doTunneledWebRequest { // Test: tunneled web site fetch err = makeTunneledWebRequest(t, localHTTPProxyPort) if err == nil { if runConfig.denyTrafficRules { t.Fatalf("unexpected tunneled web request success") } } else { if !runConfig.denyTrafficRules { t.Fatalf("tunneled web request failed: %s", err) } } } if runConfig.doTunneledNTPRequest { // Test: tunneled UDP packets udpgwServerAddress := serverConfig["UDPInterceptUdpgwServerAddress"].(string) err = makeTunneledNTPRequest(t, localSOCKSProxyPort, udpgwServerAddress) if err == nil { if runConfig.denyTrafficRules { t.Fatalf("unexpected tunneled NTP request success") } } else { if !runConfig.denyTrafficRules { t.Fatalf("tunneled NTP request failed: %s", err) } } } // Test: await SLOK payload if !runConfig.denyTrafficRules { time.Sleep(1 * time.Second) waitOnNotification(t, slokSeeded, timeoutSignal, "SLOK seeded timeout exceeded") } }
func runServer(t *testing.T, runConfig *runServerConfig) { // create a server serverConfigJSON, _, encodedServerEntry, err := GenerateConfig( &GenerateConfigParams{ ServerIPAddress: "127.0.0.1", EnableSSHAPIRequests: runConfig.enableSSHAPIRequests, WebServerPort: 8000, TunnelProtocolPorts: map[string]int{runConfig.tunnelProtocol: 4000}, }) if err != nil { t.Fatalf("error generating server config: %s", err) } // customize server config // Pave psinet with random values to test handshake homepages. psinetFilename := "psinet.json" sponsorID, expectedHomepageURL := pavePsinetDatabaseFile(t, psinetFilename) var serverConfig interface{} json.Unmarshal(serverConfigJSON, &serverConfig) serverConfig.(map[string]interface{})["GeoIPDatabaseFilename"] = "" serverConfig.(map[string]interface{})["PsinetDatabaseFilename"] = psinetFilename serverConfig.(map[string]interface{})["TrafficRulesFilename"] = "" serverConfigJSON, _ = json.Marshal(serverConfig) // run server serverWaitGroup := new(sync.WaitGroup) serverWaitGroup.Add(1) go func() { defer serverWaitGroup.Done() err := RunServices(serverConfigJSON) 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") } }() // Test: hot reload (of psinet) if runConfig.doHotReload { // TODO: monitor logs for more robust wait-until-loaded time.Sleep(1 * time.Second) // Pave a new psinet with different random values. sponsorID, expectedHomepageURL = pavePsinetDatabaseFile(t, psinetFilename) p, _ := os.FindProcess(os.Getpid()) p.Signal(syscall.SIGUSR1) // TODO: monitor logs for more robust wait-until-reloaded time.Sleep(1 * time.Second) // After reloading psinet, the new sponsorID/expectedHomepageURL // should be active, as tested in the client "Homepage" notice // handler below. } // connect to server with client // TODO: currently, TargetServerEntry only works with one tunnel numTunnels := 1 localHTTPProxyPort := 8081 establishTunnelPausePeriodSeconds := 1 // Note: calling LoadConfig ensures all *int config fields are initialized clientConfigJSON := ` { "ClientVersion" : "0", "SponsorId" : "0", "PropagationChannelId" : "0" }` clientConfig, _ := psiphon.LoadConfig([]byte(clientConfigJSON)) clientConfig.SponsorId = sponsorID clientConfig.ConnectionWorkerPoolSize = numTunnels clientConfig.TunnelPoolSize = numTunnels clientConfig.DisableRemoteServerListFetcher = true clientConfig.EstablishTunnelPausePeriodSeconds = &establishTunnelPausePeriodSeconds clientConfig.TargetServerEntry = string(encodedServerEntry) clientConfig.TunnelProtocol = runConfig.tunnelProtocol 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) homepageReceived := 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: } } case "Homepage": homepageURL := payload["url"].(string) if homepageURL != expectedHomepageURL { // TODO: wrong goroutine for t.FatalNow() t.Fatalf("unexpected homepage: %s", homepageURL) } select { case homepageReceived <- *new(struct{}): default: } } })) controllerShutdownBroadcast := make(chan struct{}) controllerWaitGroup := new(sync.WaitGroup) controllerWaitGroup.Add(1) go func() { defer controllerWaitGroup.Done() controller.Run(controllerShutdownBroadcast) }() defer func() { close(controllerShutdownBroadcast) shutdownTimeout := time.NewTimer(20 * time.Second) shutdownOk := make(chan struct{}, 1) go func() { controllerWaitGroup.Wait() shutdownOk <- *new(struct{}) }() select { case <-shutdownOk: case <-shutdownTimeout.C: t.Fatalf("controller shutdown timeout exceeded") } }() // Test: tunnels must be established, and correct homepage // must be received, within 30 seconds establishTimeout := time.NewTimer(30 * time.Second) select { case <-tunnelsEstablished: case <-establishTimeout.C: t.Fatalf("tunnel establish timeout exceeded") } select { case <-homepageReceived: case <-establishTimeout.C: t.Fatalf("homepage received 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() }