main.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. /*
  2. * Copyright (c) 2015, 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. package main
  20. import (
  21. "bytes"
  22. "encoding/json"
  23. "flag"
  24. "fmt"
  25. "io"
  26. "io/ioutil"
  27. "os"
  28. "os/signal"
  29. "runtime/pprof"
  30. "sort"
  31. "sync"
  32. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
  33. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  34. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
  35. )
  36. func main() {
  37. // Define command-line parameters
  38. var configFilename string
  39. flag.StringVar(&configFilename, "config", "", "configuration input file")
  40. var embeddedServerEntryListFilename string
  41. flag.StringVar(&embeddedServerEntryListFilename, "serverList", "", "embedded server entry list input file")
  42. var formatNotices bool
  43. flag.BoolVar(&formatNotices, "formatNotices", false, "emit notices in human-readable format")
  44. var profileFilename string
  45. flag.StringVar(&profileFilename, "profile", "", "CPU profile output file")
  46. var interfaceName string
  47. flag.StringVar(&interfaceName, "listenInterface", "", "Interface Name")
  48. var versionDetails bool
  49. flag.BoolVar(&versionDetails, "version", false, "Print build information and exit")
  50. flag.BoolVar(&versionDetails, "v", false, "Print build information and exit")
  51. flag.Parse()
  52. if versionDetails {
  53. b := common.GetBuildInfo()
  54. var printableDependencies bytes.Buffer
  55. var dependencyMap map[string]string
  56. longestRepoUrl := 0
  57. json.Unmarshal(b.Dependencies, &dependencyMap)
  58. sortedRepoUrls := make([]string, 0, len(dependencyMap))
  59. for repoUrl := range dependencyMap {
  60. repoUrlLength := len(repoUrl)
  61. if repoUrlLength > longestRepoUrl {
  62. longestRepoUrl = repoUrlLength
  63. }
  64. sortedRepoUrls = append(sortedRepoUrls, repoUrl)
  65. }
  66. sort.Strings(sortedRepoUrls)
  67. for repoUrl := range sortedRepoUrls {
  68. printableDependencies.WriteString(fmt.Sprintf(" %s ", sortedRepoUrls[repoUrl]))
  69. for i := 0; i < (longestRepoUrl - len(sortedRepoUrls[repoUrl])); i++ {
  70. printableDependencies.WriteString(" ")
  71. }
  72. printableDependencies.WriteString(fmt.Sprintf("%s\n", dependencyMap[sortedRepoUrls[repoUrl]]))
  73. }
  74. fmt.Printf("Psiphon Console Client\n Build Date: %s\n Built With: %s\n Repository: %s\n Revision: %s\n Dependencies:\n%s\n", b.BuildDate, b.GoVersion, b.BuildRepo, b.BuildRev, printableDependencies.String())
  75. os.Exit(0)
  76. }
  77. // Initialize default Notice output (stderr)
  78. var noticeWriter io.Writer
  79. noticeWriter = os.Stderr
  80. if formatNotices {
  81. noticeWriter = psiphon.NewNoticeConsoleRewriter(noticeWriter)
  82. }
  83. psiphon.SetNoticeOutput(noticeWriter)
  84. psiphon.NoticeBuildInfo()
  85. // Handle required config file parameter
  86. if configFilename == "" {
  87. psiphon.SetEmitDiagnosticNotices(true)
  88. psiphon.NoticeError("configuration file is required")
  89. os.Exit(1)
  90. }
  91. configFileContents, err := ioutil.ReadFile(configFilename)
  92. if err != nil {
  93. psiphon.SetEmitDiagnosticNotices(true)
  94. psiphon.NoticeError("error loading configuration file: %s", err)
  95. os.Exit(1)
  96. }
  97. config, err := psiphon.LoadConfig(configFileContents)
  98. if err != nil {
  99. psiphon.SetEmitDiagnosticNotices(true)
  100. psiphon.NoticeError("error processing configuration file: %s", err)
  101. os.Exit(1)
  102. }
  103. // When a logfile is configured, reinitialize Notice output
  104. if config.LogFilename != "" {
  105. logFile, err := os.OpenFile(config.LogFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
  106. if err != nil {
  107. psiphon.NoticeError("error opening log file: %s", err)
  108. os.Exit(1)
  109. }
  110. defer logFile.Close()
  111. var noticeWriter io.Writer
  112. noticeWriter = logFile
  113. if formatNotices {
  114. noticeWriter = psiphon.NewNoticeConsoleRewriter(noticeWriter)
  115. }
  116. psiphon.SetNoticeOutput(noticeWriter)
  117. }
  118. // Handle optional profiling parameter
  119. if profileFilename != "" {
  120. profileFile, err := os.Create(profileFilename)
  121. if err != nil {
  122. psiphon.NoticeError("error opening profile file: %s", err)
  123. os.Exit(1)
  124. }
  125. pprof.StartCPUProfile(profileFile)
  126. defer pprof.StopCPUProfile()
  127. }
  128. // Initialize data store
  129. err = psiphon.InitDataStore(config)
  130. if err != nil {
  131. psiphon.NoticeError("error initializing datastore: %s", err)
  132. os.Exit(1)
  133. }
  134. // Handle optional embedded server list file parameter
  135. // If specified, the embedded server list is loaded and stored. When there
  136. // are no server candidates at all, we wait for this import to complete
  137. // before starting the Psiphon controller. Otherwise, we import while
  138. // concurrently starting the controller to minimize delay before attempting
  139. // to connect to existing candidate servers.
  140. // If the import fails, an error notice is emitted, but the controller is
  141. // still started: either existing candidate servers may suffice, or the
  142. // remote server list fetch may obtain candidate servers.
  143. if embeddedServerEntryListFilename != "" {
  144. embeddedServerListWaitGroup := new(sync.WaitGroup)
  145. embeddedServerListWaitGroup.Add(1)
  146. go func() {
  147. defer embeddedServerListWaitGroup.Done()
  148. serverEntryList, err := ioutil.ReadFile(embeddedServerEntryListFilename)
  149. if err != nil {
  150. psiphon.NoticeError("error loading embedded server entry list file: %s", err)
  151. return
  152. }
  153. // TODO: stream embedded server list data? also, the cast makes an unnecessary copy of a large buffer?
  154. serverEntries, err := protocol.DecodeAndValidateServerEntryList(
  155. string(serverEntryList),
  156. common.GetCurrentTimestamp(),
  157. protocol.SERVER_ENTRY_SOURCE_EMBEDDED)
  158. if err != nil {
  159. psiphon.NoticeError("error decoding embedded server entry list file: %s", err)
  160. return
  161. }
  162. // Since embedded server list entries may become stale, they will not
  163. // overwrite existing stored entries for the same server.
  164. err = psiphon.StoreServerEntries(serverEntries, false)
  165. if err != nil {
  166. psiphon.NoticeError("error storing embedded server entry list data: %s", err)
  167. return
  168. }
  169. }()
  170. if psiphon.CountServerEntries(config.EgressRegion, config.TunnelProtocol) == 0 {
  171. embeddedServerListWaitGroup.Wait()
  172. } else {
  173. defer embeddedServerListWaitGroup.Wait()
  174. }
  175. }
  176. if interfaceName != "" {
  177. config.ListenInterface = interfaceName
  178. }
  179. // Run Psiphon
  180. controller, err := psiphon.NewController(config)
  181. if err != nil {
  182. psiphon.NoticeError("error creating controller: %s", err)
  183. os.Exit(1)
  184. }
  185. controllerStopSignal := make(chan struct{}, 1)
  186. shutdownBroadcast := make(chan struct{})
  187. controllerWaitGroup := new(sync.WaitGroup)
  188. controllerWaitGroup.Add(1)
  189. go func() {
  190. defer controllerWaitGroup.Done()
  191. controller.Run(shutdownBroadcast)
  192. controllerStopSignal <- *new(struct{})
  193. }()
  194. // Wait for an OS signal or a Run stop signal, then stop Psiphon and exit
  195. systemStopSignal := make(chan os.Signal, 1)
  196. signal.Notify(systemStopSignal, os.Interrupt, os.Kill)
  197. select {
  198. case <-systemStopSignal:
  199. psiphon.NoticeInfo("shutdown by system")
  200. close(shutdownBroadcast)
  201. controllerWaitGroup.Wait()
  202. case <-controllerStopSignal:
  203. psiphon.NoticeInfo("shutdown by controller")
  204. }
  205. }