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 }
func (webServer *webServer) handshakeHandler(w http.ResponseWriter, r *http.Request) { if !webServer.checkWebServerSecret(r) { // TODO: log more details? log.WithContext().Warning("checkWebServerSecret failed") // TODO: psi_web returns NotFound in this case w.WriteHeader(http.StatusForbidden) return } // TODO: validate; proper log log.WithContextFields(LogFields{"queryParams": r.URL.Query()}).Info("handshake") // TODO: necessary, in case client sends bogus request body? _, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } // TODO: backwards compatibility cases (only sending the new JSON format response line) // TODO: share struct definition with psiphon/serverApi.go? // TODO: populate more response data var handshakeConfig struct { Homepages []string `json:"homepages"` UpgradeClientVersion string `json:"upgrade_client_version"` PageViewRegexes []map[string]string `json:"page_view_regexes"` HttpsRequestRegexes []map[string]string `json:"https_request_regexes"` EncodedServerList []string `json:"encoded_server_list"` ClientRegion string `json:"client_region"` ServerTimestamp string `json:"server_timestamp"` } handshakeConfig.ServerTimestamp = psiphon.GetCurrentTimestamp() jsonPayload, err := json.Marshal(handshakeConfig) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } responseBody := append([]byte("Config: "), jsonPayload...) w.WriteHeader(http.StatusOK) w.Write(responseBody) }
func (webServer *webServer) connectedHandler(w http.ResponseWriter, r *http.Request) { if !webServer.checkWebServerSecret(r) { // TODO: log more details? log.WithContext().Warning("checkWebServerSecret failed") // TODO: psi_web does NotFound in this case w.WriteHeader(http.StatusForbidden) return } // TODO: validate; proper log log.WithContextFields(LogFields{"queryParams": r.URL.Query()}).Info("connected") // TODO: necessary, in case client sends bogus request body? _, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } var connectedResponse struct { ConnectedTimestamp string `json:"connected_timestamp"` } connectedResponse.ConnectedTimestamp = psiphon.TruncateTimestampToHour(psiphon.GetCurrentTimestamp()) responseBody, err := json.Marshal(connectedResponse) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) w.Write(responseBody) }
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), psiphon.GetCurrentTimestamp(), psiphon.SERVER_ENTRY_SOURCE_EMBEDDED) 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") } }