server_test.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  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. package server
  20. import (
  21. "encoding/json"
  22. "flag"
  23. "fmt"
  24. "io/ioutil"
  25. "net/http"
  26. "net/url"
  27. "os"
  28. "sync"
  29. "syscall"
  30. "testing"
  31. "time"
  32. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
  33. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  34. )
  35. func TestMain(m *testing.M) {
  36. flag.Parse()
  37. os.Remove(psiphon.DATA_STORE_FILENAME)
  38. psiphon.SetEmitDiagnosticNotices(true)
  39. os.Exit(m.Run())
  40. }
  41. // Note: not testing fronting meek protocols, which client is
  42. // hard-wired to except running on privileged ports 80 and 443.
  43. func TestSSH(t *testing.T) {
  44. runServer(t,
  45. &runServerConfig{
  46. tunnelProtocol: "SSH",
  47. enableSSHAPIRequests: true,
  48. doHotReload: false,
  49. })
  50. }
  51. func TestOSSH(t *testing.T) {
  52. runServer(t,
  53. &runServerConfig{
  54. tunnelProtocol: "OSSH",
  55. enableSSHAPIRequests: true,
  56. doHotReload: false,
  57. })
  58. }
  59. func TestUnfrontedMeek(t *testing.T) {
  60. runServer(t,
  61. &runServerConfig{
  62. tunnelProtocol: "UNFRONTED-MEEK-OSSH",
  63. enableSSHAPIRequests: true,
  64. doHotReload: false,
  65. })
  66. }
  67. func TestUnfrontedMeekHTTPS(t *testing.T) {
  68. runServer(t,
  69. &runServerConfig{
  70. tunnelProtocol: "UNFRONTED-MEEK-HTTPS-OSSH",
  71. enableSSHAPIRequests: true,
  72. doHotReload: false,
  73. })
  74. }
  75. func TestWebTransportAPIRequests(t *testing.T) {
  76. runServer(t,
  77. &runServerConfig{
  78. tunnelProtocol: "OSSH",
  79. enableSSHAPIRequests: false,
  80. doHotReload: false,
  81. })
  82. }
  83. func TestHotReload(t *testing.T) {
  84. runServer(t,
  85. &runServerConfig{
  86. tunnelProtocol: "OSSH",
  87. enableSSHAPIRequests: true,
  88. doHotReload: true,
  89. })
  90. }
  91. type runServerConfig struct {
  92. tunnelProtocol string
  93. enableSSHAPIRequests bool
  94. doHotReload bool
  95. }
  96. func runServer(t *testing.T, runConfig *runServerConfig) {
  97. // create a server
  98. var err error
  99. serverIPaddress := ""
  100. for _, interfaceName := range []string{"eth0", "en0"} {
  101. serverIPaddress, err = psiphon.GetInterfaceIPAddress(interfaceName)
  102. if err == nil {
  103. break
  104. }
  105. }
  106. if err != nil {
  107. t.Fatalf("error getting server IP address: %s", err)
  108. }
  109. serverConfigJSON, _, encodedServerEntry, err := GenerateConfig(
  110. &GenerateConfigParams{
  111. ServerIPAddress: serverIPaddress,
  112. EnableSSHAPIRequests: runConfig.enableSSHAPIRequests,
  113. WebServerPort: 8000,
  114. TunnelProtocolPorts: map[string]int{runConfig.tunnelProtocol: 4000},
  115. })
  116. if err != nil {
  117. t.Fatalf("error generating server config: %s", err)
  118. }
  119. // customize server config
  120. // Pave psinet with random values to test handshake homepages.
  121. psinetFilename := "psinet.json"
  122. sponsorID, expectedHomepageURL := pavePsinetDatabaseFile(t, psinetFilename)
  123. var serverConfig interface{}
  124. json.Unmarshal(serverConfigJSON, &serverConfig)
  125. serverConfig.(map[string]interface{})["GeoIPDatabaseFilename"] = ""
  126. serverConfig.(map[string]interface{})["PsinetDatabaseFilename"] = psinetFilename
  127. serverConfig.(map[string]interface{})["TrafficRulesFilename"] = ""
  128. serverConfigJSON, _ = json.Marshal(serverConfig)
  129. // run server
  130. serverWaitGroup := new(sync.WaitGroup)
  131. serverWaitGroup.Add(1)
  132. go func() {
  133. defer serverWaitGroup.Done()
  134. err := RunServices(serverConfigJSON)
  135. if err != nil {
  136. // TODO: wrong goroutine for t.FatalNow()
  137. t.Fatalf("error running server: %s", err)
  138. }
  139. }()
  140. defer func() {
  141. // Test: orderly server shutdown
  142. p, _ := os.FindProcess(os.Getpid())
  143. p.Signal(os.Interrupt)
  144. shutdownTimeout := time.NewTimer(5 * time.Second)
  145. shutdownOk := make(chan struct{}, 1)
  146. go func() {
  147. serverWaitGroup.Wait()
  148. shutdownOk <- *new(struct{})
  149. }()
  150. select {
  151. case <-shutdownOk:
  152. case <-shutdownTimeout.C:
  153. t.Fatalf("server shutdown timeout exceeded")
  154. }
  155. }()
  156. // Test: hot reload (of psinet)
  157. if runConfig.doHotReload {
  158. // TODO: monitor logs for more robust wait-until-loaded
  159. time.Sleep(1 * time.Second)
  160. // Pave a new psinet with different random values.
  161. sponsorID, expectedHomepageURL = pavePsinetDatabaseFile(t, psinetFilename)
  162. p, _ := os.FindProcess(os.Getpid())
  163. p.Signal(syscall.SIGUSR1)
  164. // TODO: monitor logs for more robust wait-until-reloaded
  165. time.Sleep(1 * time.Second)
  166. // After reloading psinet, the new sponsorID/expectedHomepageURL
  167. // should be active, as tested in the client "Homepage" notice
  168. // handler below.
  169. }
  170. // connect to server with client
  171. // TODO: currently, TargetServerEntry only works with one tunnel
  172. numTunnels := 1
  173. localHTTPProxyPort := 8081
  174. establishTunnelPausePeriodSeconds := 1
  175. // Note: calling LoadConfig ensures all *int config fields are initialized
  176. clientConfigJSON := `
  177. {
  178. "ClientVersion" : "0",
  179. "SponsorId" : "0",
  180. "PropagationChannelId" : "0"
  181. }`
  182. clientConfig, _ := psiphon.LoadConfig([]byte(clientConfigJSON))
  183. clientConfig.SponsorId = sponsorID
  184. clientConfig.ConnectionWorkerPoolSize = numTunnels
  185. clientConfig.TunnelPoolSize = numTunnels
  186. clientConfig.DisableRemoteServerListFetcher = true
  187. clientConfig.EstablishTunnelPausePeriodSeconds = &establishTunnelPausePeriodSeconds
  188. clientConfig.TargetServerEntry = string(encodedServerEntry)
  189. clientConfig.TunnelProtocol = runConfig.tunnelProtocol
  190. clientConfig.LocalHttpProxyPort = localHTTPProxyPort
  191. err = psiphon.InitDataStore(clientConfig)
  192. if err != nil {
  193. t.Fatalf("error initializing client datastore: %s", err)
  194. }
  195. controller, err := psiphon.NewController(clientConfig)
  196. if err != nil {
  197. t.Fatalf("error creating client controller: %s", err)
  198. }
  199. tunnelsEstablished := make(chan struct{}, 1)
  200. homepageReceived := make(chan struct{}, 1)
  201. psiphon.SetNoticeOutput(psiphon.NewNoticeReceiver(
  202. func(notice []byte) {
  203. fmt.Printf("%s\n", string(notice))
  204. noticeType, payload, err := psiphon.GetNotice(notice)
  205. if err != nil {
  206. return
  207. }
  208. switch noticeType {
  209. case "Tunnels":
  210. count := int(payload["count"].(float64))
  211. if count >= numTunnels {
  212. select {
  213. case tunnelsEstablished <- *new(struct{}):
  214. default:
  215. }
  216. }
  217. case "Homepage":
  218. homepageURL := payload["url"].(string)
  219. if homepageURL != expectedHomepageURL {
  220. // TODO: wrong goroutine for t.FatalNow()
  221. t.Fatalf("unexpected homepage: %s", homepageURL)
  222. }
  223. select {
  224. case homepageReceived <- *new(struct{}):
  225. default:
  226. }
  227. }
  228. }))
  229. controllerShutdownBroadcast := make(chan struct{})
  230. controllerWaitGroup := new(sync.WaitGroup)
  231. controllerWaitGroup.Add(1)
  232. go func() {
  233. defer controllerWaitGroup.Done()
  234. controller.Run(controllerShutdownBroadcast)
  235. }()
  236. defer func() {
  237. close(controllerShutdownBroadcast)
  238. shutdownTimeout := time.NewTimer(20 * time.Second)
  239. shutdownOk := make(chan struct{}, 1)
  240. go func() {
  241. controllerWaitGroup.Wait()
  242. shutdownOk <- *new(struct{})
  243. }()
  244. select {
  245. case <-shutdownOk:
  246. case <-shutdownTimeout.C:
  247. t.Fatalf("controller shutdown timeout exceeded")
  248. }
  249. }()
  250. // Test: tunnels must be established, and correct homepage
  251. // must be received, within 30 seconds
  252. establishTimeout := time.NewTimer(30 * time.Second)
  253. select {
  254. case <-tunnelsEstablished:
  255. case <-establishTimeout.C:
  256. t.Fatalf("tunnel establish timeout exceeded")
  257. }
  258. select {
  259. case <-homepageReceived:
  260. case <-establishTimeout.C:
  261. t.Fatalf("homepage received timeout exceeded")
  262. }
  263. // Test: tunneled web site fetch
  264. testUrl := "https://psiphon.ca"
  265. roundTripTimeout := 30 * time.Second
  266. proxyUrl, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", localHTTPProxyPort))
  267. if err != nil {
  268. t.Fatalf("error initializing proxied HTTP request: %s", err)
  269. }
  270. httpClient := &http.Client{
  271. Transport: &http.Transport{
  272. Proxy: http.ProxyURL(proxyUrl),
  273. },
  274. Timeout: roundTripTimeout,
  275. }
  276. response, err := httpClient.Get(testUrl)
  277. if err != nil {
  278. t.Fatalf("error sending proxied HTTP request: %s", err)
  279. }
  280. _, err = ioutil.ReadAll(response.Body)
  281. if err != nil {
  282. t.Fatalf("error reading proxied HTTP response: %s", err)
  283. }
  284. response.Body.Close()
  285. }
  286. func pavePsinetDatabaseFile(t *testing.T, psinetFilename string) (string, string) {
  287. sponsorID, _ := common.MakeRandomStringHex(8)
  288. fakeDomain, _ := common.MakeRandomStringHex(4)
  289. fakePath, _ := common.MakeRandomStringHex(4)
  290. expectedHomepageURL := fmt.Sprintf("https://%s.com/%s", fakeDomain, fakePath)
  291. psinetJSONFormat := `
  292. {
  293. "sponsors": {
  294. "%s": {
  295. "home_pages": {
  296. "None": [
  297. {
  298. "region": null,
  299. "url": "%s"
  300. }
  301. ]
  302. }
  303. }
  304. }
  305. }
  306. `
  307. psinetJSON := fmt.Sprintf(psinetJSONFormat, sponsorID, expectedHomepageURL)
  308. err := ioutil.WriteFile(psinetFilename, []byte(psinetJSON), 0600)
  309. if err != nil {
  310. t.Fatalf("error paving psinet database: %s", err)
  311. }
  312. return sponsorID, expectedHomepageURL
  313. }