server_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  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 sendNotificationReceived(c chan<- struct{}) {
  97. select {
  98. case c <- *new(struct{}):
  99. default:
  100. }
  101. }
  102. func waitOnNotification(t *testing.T, c, timeoutSignal <-chan struct{}, timeoutMessage string) {
  103. select {
  104. case <-c:
  105. case <-timeoutSignal:
  106. t.Fatalf(timeoutMessage)
  107. }
  108. }
  109. const dummyClientVerificationPayload = `
  110. {
  111. "status": 0,
  112. "payload": ""
  113. }`
  114. func runServer(t *testing.T, runConfig *runServerConfig) {
  115. // create a server
  116. var err error
  117. serverIPaddress := ""
  118. for _, interfaceName := range []string{"eth0", "en0"} {
  119. serverIPaddress, err = psiphon.GetInterfaceIPAddress(interfaceName)
  120. if err == nil {
  121. break
  122. }
  123. }
  124. if err != nil {
  125. t.Fatalf("error getting server IP address: %s", err)
  126. }
  127. serverConfigJSON, _, encodedServerEntry, err := GenerateConfig(
  128. &GenerateConfigParams{
  129. ServerIPAddress: serverIPaddress,
  130. EnableSSHAPIRequests: runConfig.enableSSHAPIRequests,
  131. WebServerPort: 8000,
  132. TunnelProtocolPorts: map[string]int{runConfig.tunnelProtocol: 4000},
  133. })
  134. if err != nil {
  135. t.Fatalf("error generating server config: %s", err)
  136. }
  137. // customize server config
  138. // Pave psinet with random values to test handshake homepages.
  139. psinetFilename := "psinet.json"
  140. sponsorID, expectedHomepageURL := pavePsinetDatabaseFile(t, psinetFilename)
  141. var serverConfig interface{}
  142. json.Unmarshal(serverConfigJSON, &serverConfig)
  143. serverConfig.(map[string]interface{})["GeoIPDatabaseFilename"] = ""
  144. serverConfig.(map[string]interface{})["PsinetDatabaseFilename"] = psinetFilename
  145. serverConfig.(map[string]interface{})["TrafficRulesFilename"] = ""
  146. serverConfigJSON, _ = json.Marshal(serverConfig)
  147. // run server
  148. serverWaitGroup := new(sync.WaitGroup)
  149. serverWaitGroup.Add(1)
  150. go func() {
  151. defer serverWaitGroup.Done()
  152. err := RunServices(serverConfigJSON)
  153. if err != nil {
  154. // TODO: wrong goroutine for t.FatalNow()
  155. t.Fatalf("error running server: %s", err)
  156. }
  157. }()
  158. defer func() {
  159. // Test: orderly server shutdown
  160. p, _ := os.FindProcess(os.Getpid())
  161. p.Signal(os.Interrupt)
  162. shutdownTimeout := time.NewTimer(5 * time.Second)
  163. shutdownOk := make(chan struct{}, 1)
  164. go func() {
  165. serverWaitGroup.Wait()
  166. shutdownOk <- *new(struct{})
  167. }()
  168. select {
  169. case <-shutdownOk:
  170. case <-shutdownTimeout.C:
  171. t.Fatalf("server shutdown timeout exceeded")
  172. }
  173. }()
  174. // Test: hot reload (of psinet)
  175. if runConfig.doHotReload {
  176. // TODO: monitor logs for more robust wait-until-loaded
  177. time.Sleep(1 * time.Second)
  178. // Pave a new psinet with different random values.
  179. sponsorID, expectedHomepageURL = pavePsinetDatabaseFile(t, psinetFilename)
  180. p, _ := os.FindProcess(os.Getpid())
  181. p.Signal(syscall.SIGUSR1)
  182. // TODO: monitor logs for more robust wait-until-reloaded
  183. time.Sleep(1 * time.Second)
  184. // After reloading psinet, the new sponsorID/expectedHomepageURL
  185. // should be active, as tested in the client "Homepage" notice
  186. // handler below.
  187. }
  188. // connect to server with client
  189. // TODO: currently, TargetServerEntry only works with one tunnel
  190. numTunnels := 1
  191. localHTTPProxyPort := 8081
  192. establishTunnelPausePeriodSeconds := 1
  193. // Note: calling LoadConfig ensures all *int config fields are initialized
  194. clientConfigJSON := `
  195. {
  196. "ClientPlatform" : "Android",
  197. "ClientVersion" : "0",
  198. "SponsorId" : "0",
  199. "PropagationChannelId" : "0"
  200. }`
  201. clientConfig, _ := psiphon.LoadConfig([]byte(clientConfigJSON))
  202. clientConfig.SponsorId = sponsorID
  203. clientConfig.ConnectionWorkerPoolSize = numTunnels
  204. clientConfig.TunnelPoolSize = numTunnels
  205. clientConfig.DisableRemoteServerListFetcher = true
  206. clientConfig.EstablishTunnelPausePeriodSeconds = &establishTunnelPausePeriodSeconds
  207. clientConfig.TargetServerEntry = string(encodedServerEntry)
  208. clientConfig.TunnelProtocol = runConfig.tunnelProtocol
  209. clientConfig.LocalHttpProxyPort = localHTTPProxyPort
  210. err = psiphon.InitDataStore(clientConfig)
  211. if err != nil {
  212. t.Fatalf("error initializing client datastore: %s", err)
  213. }
  214. controller, err := psiphon.NewController(clientConfig)
  215. if err != nil {
  216. t.Fatalf("error creating client controller: %s", err)
  217. }
  218. tunnelsEstablished := make(chan struct{}, 1)
  219. homepageReceived := make(chan struct{}, 1)
  220. verificationRequired := make(chan struct{}, 1)
  221. verificationCompleted := make(chan struct{}, 1)
  222. psiphon.SetNoticeOutput(psiphon.NewNoticeReceiver(
  223. func(notice []byte) {
  224. //fmt.Printf("%s\n", string(notice))
  225. noticeType, payload, err := psiphon.GetNotice(notice)
  226. if err != nil {
  227. return
  228. }
  229. switch noticeType {
  230. case "Tunnels":
  231. // Do not set verification payload until tunnel is
  232. // established. Otherwise will silently take no action.
  233. controller.SetClientVerificationPayloadForActiveTunnels("")
  234. count := int(payload["count"].(float64))
  235. if count >= numTunnels {
  236. sendNotificationReceived(tunnelsEstablished)
  237. }
  238. case "Homepage":
  239. homepageURL := payload["url"].(string)
  240. if homepageURL != expectedHomepageURL {
  241. // TODO: wrong goroutine for t.FatalNow()
  242. t.Fatalf("unexpected homepage: %s", homepageURL)
  243. }
  244. sendNotificationReceived(homepageReceived)
  245. case "ClientVerificationRequired":
  246. sendNotificationReceived(verificationRequired)
  247. controller.SetClientVerificationPayloadForActiveTunnels(dummyClientVerificationPayload)
  248. case "NoticeClientVerificationRequestCompleted":
  249. sendNotificationReceived(verificationCompleted)
  250. }
  251. }))
  252. controllerShutdownBroadcast := make(chan struct{})
  253. controllerWaitGroup := new(sync.WaitGroup)
  254. controllerWaitGroup.Add(1)
  255. go func() {
  256. defer controllerWaitGroup.Done()
  257. controller.Run(controllerShutdownBroadcast)
  258. }()
  259. defer func() {
  260. close(controllerShutdownBroadcast)
  261. shutdownTimeout := time.NewTimer(20 * time.Second)
  262. shutdownOk := make(chan struct{}, 1)
  263. go func() {
  264. controllerWaitGroup.Wait()
  265. shutdownOk <- *new(struct{})
  266. }()
  267. select {
  268. case <-shutdownOk:
  269. case <-shutdownTimeout.C:
  270. t.Fatalf("controller shutdown timeout exceeded")
  271. }
  272. }()
  273. // Test: tunnels must be established, and correct homepage
  274. // must be received, within 30 seconds
  275. timeoutSignal := make(chan struct{})
  276. go func() {
  277. timer := time.NewTimer(30 * time.Second)
  278. <-timer.C
  279. close(timeoutSignal)
  280. }()
  281. waitOnNotification(t, tunnelsEstablished, timeoutSignal, "tunnel establish timeout exceeded")
  282. waitOnNotification(t, homepageReceived, timeoutSignal, "homepage received timeout exceeded")
  283. waitOnNotification(t, verificationRequired, timeoutSignal, "verification required timeout exceeded")
  284. waitOnNotification(t, verificationCompleted, timeoutSignal, "verification completed timeout exceeded")
  285. // Test: tunneled web site fetch
  286. testUrl := "https://psiphon.ca"
  287. roundTripTimeout := 30 * time.Second
  288. proxyUrl, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", localHTTPProxyPort))
  289. if err != nil {
  290. t.Fatalf("error initializing proxied HTTP request: %s", err)
  291. }
  292. httpClient := &http.Client{
  293. Transport: &http.Transport{
  294. Proxy: http.ProxyURL(proxyUrl),
  295. },
  296. Timeout: roundTripTimeout,
  297. }
  298. response, err := httpClient.Get(testUrl)
  299. if err != nil {
  300. t.Fatalf("error sending proxied HTTP request: %s", err)
  301. }
  302. _, err = ioutil.ReadAll(response.Body)
  303. if err != nil {
  304. t.Fatalf("error reading proxied HTTP response: %s", err)
  305. }
  306. response.Body.Close()
  307. }
  308. func pavePsinetDatabaseFile(t *testing.T, psinetFilename string) (string, string) {
  309. sponsorID, _ := common.MakeRandomStringHex(8)
  310. fakeDomain, _ := common.MakeRandomStringHex(4)
  311. fakePath, _ := common.MakeRandomStringHex(4)
  312. expectedHomepageURL := fmt.Sprintf("https://%s.com/%s", fakeDomain, fakePath)
  313. psinetJSONFormat := `
  314. {
  315. "sponsors": {
  316. "%s": {
  317. "home_pages": {
  318. "None": [
  319. {
  320. "region": null,
  321. "url": "%s"
  322. }
  323. ]
  324. }
  325. }
  326. }
  327. }
  328. `
  329. psinetJSON := fmt.Sprintf(psinetJSONFormat, sponsorID, expectedHomepageURL)
  330. err := ioutil.WriteFile(psinetFilename, []byte(psinetJSON), 0600)
  331. if err != nil {
  332. t.Fatalf("error paving psinet database: %s", err)
  333. }
  334. return sponsorID, expectedHomepageURL
  335. }