serverApi.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864
  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 psiphon
  20. import (
  21. "bytes"
  22. "encoding/base64"
  23. "encoding/hex"
  24. "encoding/json"
  25. "errors"
  26. "fmt"
  27. "io"
  28. "io/ioutil"
  29. "net"
  30. "net/http"
  31. "strconv"
  32. "sync/atomic"
  33. "time"
  34. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/transferstats"
  35. )
  36. const (
  37. SERVER_API_HANDSHAKE_REQUEST_NAME = "psiphon-handshake"
  38. SERVER_API_CONNECTED_REQUEST_NAME = "psiphon-connected"
  39. SERVER_API_STATUS_REQUEST_NAME = "psiphon-status"
  40. SERVER_API_CLIENT_VERIFICATION_REQUEST_NAME = "psiphon-client-verification"
  41. )
  42. // ServerContext is a utility struct which holds all of the data associated
  43. // with a Psiphon server connection. In addition to the established tunnel, this
  44. // includes data and transport mechanisms for Psiphon API requests. Legacy servers
  45. // offer the Psiphon API through a web service; newer servers offer the Psiphon
  46. // API through SSH requests made directly through the tunnel's SSH client.
  47. type ServerContext struct {
  48. sessionId string
  49. tunnelNumber int64
  50. tunnel *Tunnel
  51. psiphonHttpsClient *http.Client
  52. statsRegexps *transferstats.Regexps
  53. clientRegion string
  54. clientUpgradeVersion string
  55. serverHandshakeTimestamp string
  56. }
  57. // MeekStats holds extra stats that are only gathered for meek tunnels.
  58. type MeekStats struct {
  59. DialAddress string
  60. ResolvedIPAddress string
  61. SNIServerName string
  62. HostHeader string
  63. TransformedHostName bool
  64. }
  65. // nextTunnelNumber is a monotonically increasing number assigned to each
  66. // successive tunnel connection. The sessionId and tunnelNumber together
  67. // form a globally unique identifier for tunnels, which is used for
  68. // stats. Note that the number is increasing but not necessarily
  69. // consecutive for each active tunnel in session.
  70. var nextTunnelNumber int64
  71. // MakeSessionId creates a new session ID. The same session ID is used across
  72. // multi-tunnel controller runs, where each tunnel has its own ServerContext
  73. // instance.
  74. // In server-side stats, we now consider a "session" to be the lifetime of the
  75. // Controller (e.g., the user's commanded start and stop) and we measure this
  76. // duration as well as the duration of each tunnel within the session.
  77. func MakeSessionId() (sessionId string, err error) {
  78. randomId, err := MakeSecureRandomBytes(PSIPHON_API_CLIENT_SESSION_ID_LENGTH)
  79. if err != nil {
  80. return "", ContextError(err)
  81. }
  82. return hex.EncodeToString(randomId), nil
  83. }
  84. // NewServerContext makes the tunnelled handshake request to the Psiphon server
  85. // and returns a ServerContext struct for use with subsequent Psiphon server API
  86. // requests (e.g., periodic connected and status requests).
  87. func NewServerContext(tunnel *Tunnel, sessionId string) (*ServerContext, error) {
  88. // For legacy servers, set up psiphonHttpsClient for
  89. // accessing the Psiphon API via the web service.
  90. var psiphonHttpsClient *http.Client
  91. if !tunnel.serverEntry.SupportsSSHAPIRequests() {
  92. var err error
  93. psiphonHttpsClient, err = makePsiphonHttpsClient(tunnel)
  94. if err != nil {
  95. return nil, ContextError(err)
  96. }
  97. }
  98. serverContext := &ServerContext{
  99. sessionId: sessionId,
  100. tunnelNumber: atomic.AddInt64(&nextTunnelNumber, 1),
  101. tunnel: tunnel,
  102. psiphonHttpsClient: psiphonHttpsClient,
  103. }
  104. err := serverContext.doHandshakeRequest()
  105. if err != nil {
  106. return nil, ContextError(err)
  107. }
  108. return serverContext, nil
  109. }
  110. // doHandshakeRequest performs the "handshake" API request. The handshake
  111. // returns upgrade info, newly discovered server entries -- which are
  112. // stored -- and sponsor info (home pages, stat regexes).
  113. func (serverContext *ServerContext) doHandshakeRequest() error {
  114. params := serverContext.getBaseParams()
  115. // *TODO*: this is obsolete?
  116. /*
  117. serverEntryIpAddresses, err := GetServerEntryIpAddresses()
  118. if err != nil {
  119. return ContextError(err)
  120. }
  121. // Submit a list of known servers -- this will be used for
  122. // discovery statistics.
  123. for _, ipAddress := range serverEntryIpAddresses {
  124. params = append(params, requestParam{"known_server", ipAddress})
  125. }
  126. */
  127. var response []byte
  128. if serverContext.psiphonHttpsClient == nil {
  129. request, err := makeSSHAPIRequestPayload(params)
  130. if err != nil {
  131. return ContextError(err)
  132. }
  133. response, err = serverContext.tunnel.SendAPIRequest(
  134. SERVER_API_HANDSHAKE_REQUEST_NAME, request)
  135. if err != nil {
  136. return ContextError(err)
  137. }
  138. } else {
  139. // Legacy web service API request
  140. responseBody, err := serverContext.doGetRequest(
  141. makeRequestUrl(serverContext.tunnel, "", "handshake", params))
  142. if err != nil {
  143. return ContextError(err)
  144. }
  145. // Skip legacy format lines and just parse the JSON config line
  146. configLinePrefix := []byte("Config: ")
  147. for _, line := range bytes.Split(responseBody, []byte("\n")) {
  148. if bytes.HasPrefix(line, configLinePrefix) {
  149. response = line[len(configLinePrefix):]
  150. break
  151. }
  152. }
  153. if len(response) == 0 {
  154. return ContextError(errors.New("no config line found"))
  155. }
  156. }
  157. // Note:
  158. // - 'preemptive_reconnect_lifetime_milliseconds' is currently unused
  159. // - 'ssh_session_id' is ignored; client session ID is used instead
  160. var handshakeResponse struct {
  161. Homepages []string `json:"homepages"`
  162. UpgradeClientVersion string `json:"upgrade_client_version"`
  163. PageViewRegexes []map[string]string `json:"page_view_regexes"`
  164. HttpsRequestRegexes []map[string]string `json:"https_request_regexes"`
  165. EncodedServerList []string `json:"encoded_server_list"`
  166. ClientRegion string `json:"client_region"`
  167. ServerTimestamp string `json:"server_timestamp"`
  168. ClientVerificationRequired bool `json:"client_verification_required"`
  169. }
  170. err := json.Unmarshal(response, &handshakeResponse)
  171. if err != nil {
  172. return ContextError(err)
  173. }
  174. serverContext.clientRegion = handshakeResponse.ClientRegion
  175. NoticeClientRegion(serverContext.clientRegion)
  176. var decodedServerEntries []*ServerEntry
  177. // Store discovered server entries
  178. // We use the server's time, as it's available here, for the server entry
  179. // timestamp since this is more reliable than the client time.
  180. for _, encodedServerEntry := range handshakeResponse.EncodedServerList {
  181. serverEntry, err := DecodeServerEntry(
  182. encodedServerEntry,
  183. TruncateTimestampToHour(handshakeResponse.ServerTimestamp),
  184. SERVER_ENTRY_SOURCE_DISCOVERY)
  185. if err != nil {
  186. return ContextError(err)
  187. }
  188. err = ValidateServerEntry(serverEntry)
  189. if err != nil {
  190. // Skip this entry and continue with the next one
  191. continue
  192. }
  193. decodedServerEntries = append(decodedServerEntries, serverEntry)
  194. }
  195. // The reason we are storing the entire array of server entries at once rather
  196. // than one at a time is that some desirable side-effects get triggered by
  197. // StoreServerEntries that don't get triggered by StoreServerEntry.
  198. err = StoreServerEntries(decodedServerEntries, true)
  199. if err != nil {
  200. return ContextError(err)
  201. }
  202. // TODO: formally communicate the sponsor and upgrade info to an
  203. // outer client via some control interface.
  204. for _, homepage := range handshakeResponse.Homepages {
  205. NoticeHomepage(homepage)
  206. }
  207. serverContext.clientUpgradeVersion = handshakeResponse.UpgradeClientVersion
  208. if handshakeResponse.UpgradeClientVersion != "" {
  209. NoticeClientUpgradeAvailable(handshakeResponse.UpgradeClientVersion)
  210. } else {
  211. NoticeClientIsLatestVersion("")
  212. }
  213. var regexpsNotices []string
  214. serverContext.statsRegexps, regexpsNotices = transferstats.MakeRegexps(
  215. handshakeResponse.PageViewRegexes,
  216. handshakeResponse.HttpsRequestRegexes)
  217. for _, notice := range regexpsNotices {
  218. NoticeAlert(notice)
  219. }
  220. serverContext.serverHandshakeTimestamp = handshakeResponse.ServerTimestamp
  221. if handshakeResponse.ClientVerificationRequired {
  222. NoticeClientVerificationRequired()
  223. }
  224. return nil
  225. }
  226. // DoConnectedRequest performs the "connected" API request. This request is
  227. // used for statistics. The server returns a last_connected token for
  228. // the client to store and send next time it connects. This token is
  229. // a timestamp (using the server clock, and should be rounded to the
  230. // nearest hour) which is used to determine when a connection represents
  231. // a unique user for a time period.
  232. func (serverContext *ServerContext) DoConnectedRequest() error {
  233. params := serverContext.getBaseParams()
  234. const DATA_STORE_LAST_CONNECTED_KEY = "lastConnected"
  235. lastConnected, err := GetKeyValue(DATA_STORE_LAST_CONNECTED_KEY)
  236. if err != nil {
  237. return ContextError(err)
  238. }
  239. if lastConnected == "" {
  240. lastConnected = "None"
  241. }
  242. params["last_connected"] = lastConnected
  243. var response []byte
  244. if serverContext.psiphonHttpsClient == nil {
  245. request, err := makeSSHAPIRequestPayload(params)
  246. if err != nil {
  247. return ContextError(err)
  248. }
  249. response, err = serverContext.tunnel.SendAPIRequest(
  250. SERVER_API_CONNECTED_REQUEST_NAME, request)
  251. if err != nil {
  252. return ContextError(err)
  253. }
  254. } else {
  255. // Legacy web service API request
  256. response, err = serverContext.doGetRequest(
  257. makeRequestUrl(serverContext.tunnel, "", "connected", params))
  258. if err != nil {
  259. return ContextError(err)
  260. }
  261. }
  262. var connectedResponse struct {
  263. ConnectedTimestamp string `json:"connected_timestamp"`
  264. }
  265. err = json.Unmarshal(response, &connectedResponse)
  266. if err != nil {
  267. return ContextError(err)
  268. }
  269. err = SetKeyValue(
  270. DATA_STORE_LAST_CONNECTED_KEY, connectedResponse.ConnectedTimestamp)
  271. if err != nil {
  272. return ContextError(err)
  273. }
  274. return nil
  275. }
  276. // StatsRegexps gets the Regexps used for the statistics for this tunnel.
  277. func (serverContext *ServerContext) StatsRegexps() *transferstats.Regexps {
  278. return serverContext.statsRegexps
  279. }
  280. // DoStatusRequest makes a "status" API request to the server, sending session stats.
  281. func (serverContext *ServerContext) DoStatusRequest(tunnel *Tunnel) error {
  282. params := serverContext.getStatusParams(true)
  283. // Note: ensure putBackStatusRequestPayload is called, to replace
  284. // payload for future attempt, in all failure cases.
  285. statusPayload, statusPayloadInfo, err := makeStatusRequestPayload(
  286. tunnel.serverEntry.IpAddress)
  287. if err != nil {
  288. return ContextError(err)
  289. }
  290. if serverContext.psiphonHttpsClient == nil {
  291. params["statusData"] = json.RawMessage(statusPayload)
  292. var request []byte
  293. request, err = makeSSHAPIRequestPayload(params)
  294. if err == nil {
  295. _, err = serverContext.tunnel.SendAPIRequest(
  296. SERVER_API_STATUS_REQUEST_NAME, request)
  297. }
  298. } else {
  299. // Legacy web service API request
  300. err = serverContext.doPostRequest(
  301. makeRequestUrl(serverContext.tunnel, "", "status", params),
  302. "application/json",
  303. bytes.NewReader(statusPayload))
  304. }
  305. if err != nil {
  306. // Resend the transfer stats and tunnel stats later
  307. // Note: potential duplicate reports if the server received and processed
  308. // the request but the client failed to receive the response.
  309. putBackStatusRequestPayload(statusPayloadInfo)
  310. return ContextError(err)
  311. }
  312. confirmStatusRequestPayload(statusPayloadInfo)
  313. return nil
  314. }
  315. func (serverContext *ServerContext) getStatusParams(isTunneled bool) requestJSONObject {
  316. params := serverContext.getBaseParams()
  317. // Add a random amount of padding to help prevent stats updates from being
  318. // a predictable size (which often happens when the connection is quiet).
  319. // TODO: base64 encoding of padding means the padding size is not exactly
  320. // [0, PADDING_MAX_BYTES].
  321. randomPadding := MakeSecureRandomPadding(0, PSIPHON_API_STATUS_REQUEST_PADDING_MAX_BYTES)
  322. params["padding"] = base64.StdEncoding.EncodeToString(randomPadding)
  323. // Legacy clients set "connected" to "0" when disconnecting, and this value
  324. // is used to calculate session duration estimates. This is now superseded
  325. // by explicit tunnel stats duration reporting.
  326. // The legacy method of reconstructing session durations is not compatible
  327. // with this client's connected request retries and asynchronous final
  328. // status request attempts. So we simply set this "connected" flag to reflect
  329. // whether the request is sent tunneled or not.
  330. connected := "1"
  331. if !isTunneled {
  332. connected = "0"
  333. }
  334. params["connected"] = connected
  335. return params
  336. }
  337. // statusRequestPayloadInfo is a temporary structure for data used to
  338. // either "clear" or "put back" status request payload data depending
  339. // on whether or not the request succeeded.
  340. type statusRequestPayloadInfo struct {
  341. serverId string
  342. transferStats *transferstats.AccumulatedStats
  343. tunnelStats [][]byte
  344. }
  345. func makeStatusRequestPayload(
  346. serverId string) ([]byte, *statusRequestPayloadInfo, error) {
  347. transferStats := transferstats.TakeOutStatsForServer(serverId)
  348. tunnelStats, err := TakeOutUnreportedTunnelStats(
  349. PSIPHON_API_TUNNEL_STATS_MAX_COUNT)
  350. if err != nil {
  351. NoticeAlert(
  352. "TakeOutUnreportedTunnelStats failed: %s", ContextError(err))
  353. tunnelStats = nil
  354. // Proceed with transferStats only
  355. }
  356. payloadInfo := &statusRequestPayloadInfo{
  357. serverId, transferStats, tunnelStats}
  358. payload := make(map[string]interface{})
  359. hostBytes, bytesTransferred := transferStats.GetStatsForStatusRequest()
  360. payload["host_bytes"] = hostBytes
  361. payload["bytes_transferred"] = bytesTransferred
  362. // We're not recording these fields, but the server requires them.
  363. payload["page_views"] = make([]string, 0)
  364. payload["https_requests"] = make([]string, 0)
  365. // Tunnel stats records are already in JSON format
  366. jsonTunnelStats := make([]json.RawMessage, len(tunnelStats))
  367. for i, tunnelStatsRecord := range tunnelStats {
  368. jsonTunnelStats[i] = json.RawMessage(tunnelStatsRecord)
  369. }
  370. payload["tunnel_stats"] = jsonTunnelStats
  371. jsonPayload, err := json.Marshal(payload)
  372. if err != nil {
  373. // Send the transfer stats and tunnel stats later
  374. putBackStatusRequestPayload(payloadInfo)
  375. return nil, nil, ContextError(err)
  376. }
  377. return jsonPayload, payloadInfo, nil
  378. }
  379. func putBackStatusRequestPayload(payloadInfo *statusRequestPayloadInfo) {
  380. transferstats.PutBackStatsForServer(
  381. payloadInfo.serverId, payloadInfo.transferStats)
  382. err := PutBackUnreportedTunnelStats(payloadInfo.tunnelStats)
  383. if err != nil {
  384. // These tunnel stats records won't be resent under after a
  385. // datastore re-initialization.
  386. NoticeAlert(
  387. "PutBackUnreportedTunnelStats failed: %s", ContextError(err))
  388. }
  389. }
  390. func confirmStatusRequestPayload(payloadInfo *statusRequestPayloadInfo) {
  391. err := ClearReportedTunnelStats(payloadInfo.tunnelStats)
  392. if err != nil {
  393. // These tunnel stats records may be resent.
  394. NoticeAlert(
  395. "ClearReportedTunnelStats failed: %s", ContextError(err))
  396. }
  397. }
  398. // TryUntunneledStatusRequest makes direct connections to the specified
  399. // server (if supported) in an attempt to send useful bytes transferred
  400. // and tunnel duration stats after a tunnel has alreay failed.
  401. // The tunnel is assumed to be closed, but its config, protocol, and
  402. // context values must still be valid.
  403. // TryUntunneledStatusRequest emits notices detailing failed attempts.
  404. func (serverContext *ServerContext) TryUntunneledStatusRequest(isShutdown bool) error {
  405. for _, port := range serverContext.tunnel.serverEntry.GetUntunneledWebRequestPorts() {
  406. err := serverContext.doUntunneledStatusRequest(port, isShutdown)
  407. if err == nil {
  408. return nil
  409. }
  410. NoticeAlert("doUntunneledStatusRequest failed for %s:%s: %s",
  411. serverContext.tunnel.serverEntry.IpAddress, port, err)
  412. }
  413. return errors.New("all attempts failed")
  414. }
  415. // doUntunneledStatusRequest attempts an untunneled status request.
  416. func (serverContext *ServerContext) doUntunneledStatusRequest(
  417. port string, isShutdown bool) error {
  418. tunnel := serverContext.tunnel
  419. certificate, err := DecodeCertificate(tunnel.serverEntry.WebServerCertificate)
  420. if err != nil {
  421. return ContextError(err)
  422. }
  423. timeout := time.Duration(*tunnel.config.PsiphonApiServerTimeoutSeconds) * time.Second
  424. dialConfig := tunnel.untunneledDialConfig
  425. if isShutdown {
  426. timeout = PSIPHON_API_SHUTDOWN_SERVER_TIMEOUT
  427. // Use a copy of DialConfig without pendingConns. This ensures
  428. // this request isn't interrupted/canceled. This measure should
  429. // be used only with the very short PSIPHON_API_SHUTDOWN_SERVER_TIMEOUT.
  430. dialConfig = new(DialConfig)
  431. *dialConfig = *tunnel.untunneledDialConfig
  432. }
  433. url := makeRequestUrl(tunnel, port, "status", serverContext.getStatusParams(false))
  434. httpClient, url, err := MakeUntunneledHttpsClient(
  435. dialConfig,
  436. certificate,
  437. url,
  438. timeout)
  439. if err != nil {
  440. return ContextError(err)
  441. }
  442. statusPayload, statusPayloadInfo, err := makeStatusRequestPayload(tunnel.serverEntry.IpAddress)
  443. if err != nil {
  444. return ContextError(err)
  445. }
  446. bodyType := "application/json"
  447. body := bytes.NewReader(statusPayload)
  448. response, err := httpClient.Post(url, bodyType, body)
  449. if err == nil && response.StatusCode != http.StatusOK {
  450. response.Body.Close()
  451. err = fmt.Errorf("HTTP POST request failed with response code: %d", response.StatusCode)
  452. }
  453. if err != nil {
  454. // Resend the transfer stats and tunnel stats later
  455. // Note: potential duplicate reports if the server received and processed
  456. // the request but the client failed to receive the response.
  457. putBackStatusRequestPayload(statusPayloadInfo)
  458. // Trim this error since it may include long URLs
  459. return ContextError(TrimError(err))
  460. }
  461. confirmStatusRequestPayload(statusPayloadInfo)
  462. response.Body.Close()
  463. return nil
  464. }
  465. // RecordTunnelStats records a tunnel duration and bytes
  466. // sent and received for subsequent reporting and quality
  467. // analysis.
  468. //
  469. // Tunnel durations are precisely measured client-side
  470. // and reported in status requests. As the duration is
  471. // not determined until the tunnel is closed, tunnel
  472. // stats records are stored in the persistent datastore
  473. // and reported via subsequent status requests sent to any
  474. // Psiphon server.
  475. //
  476. // Since the status request that reports a tunnel stats
  477. // record is not necessarily handled by the same server, the
  478. // tunnel stats records include the original server ID.
  479. //
  480. // Other fields that may change between tunnel stats recording
  481. // and reporting include client geo data, propagation channel,
  482. // sponsor ID, client version. These are not stored in the
  483. // datastore (client region, in particular, since that would
  484. // create an on-disk record of user location).
  485. // TODO: the server could encrypt, with a nonce and key unknown to
  486. // the client, a blob containing this data; return it in the
  487. // handshake response; and the client could store and later report
  488. // this blob with its tunnel stats records.
  489. //
  490. // Multiple "status" requests may be in flight at once (due
  491. // to multi-tunnel, asynchronous final status retry, and
  492. // aggressive status requests for pre-registered tunnels),
  493. // To avoid duplicate reporting, tunnel stats records are
  494. // "taken-out" by a status request and then "put back" in
  495. // case the request fails.
  496. //
  497. // Note: since tunnel stats records have a globally unique
  498. // identifier (sessionId + tunnelNumber), we could tolerate
  499. // duplicate reporting and filter our duplicates on the
  500. // server-side. Permitting duplicate reporting could increase
  501. // the velocity of reporting (for example, both the asynchronous
  502. // untunneled final status requests and the post-connected
  503. // immediate startus requests could try to report the same tunnel
  504. // stats).
  505. // Duplicate reporting may also occur when a server receives and
  506. // processes a status request but the client fails to receive
  507. // the response.
  508. func RecordTunnelStats(
  509. sessionId string,
  510. tunnelNumber int64,
  511. tunnelServerIpAddress string,
  512. serverHandshakeTimestamp, duration string,
  513. totalBytesSent, totalBytesReceived int64) error {
  514. tunnelStats := struct {
  515. SessionId string `json:"session_id"`
  516. TunnelNumber int64 `json:"tunnel_number"`
  517. TunnelServerIpAddress string `json:"tunnel_server_ip_address"`
  518. ServerHandshakeTimestamp string `json:"server_handshake_timestamp"`
  519. Duration string `json:"duration"`
  520. TotalBytesSent int64 `json:"total_bytes_sent"`
  521. TotalBytesReceived int64 `json:"total_bytes_received"`
  522. }{
  523. sessionId,
  524. tunnelNumber,
  525. tunnelServerIpAddress,
  526. serverHandshakeTimestamp,
  527. duration,
  528. totalBytesSent,
  529. totalBytesReceived,
  530. }
  531. tunnelStatsJson, err := json.Marshal(tunnelStats)
  532. if err != nil {
  533. return ContextError(err)
  534. }
  535. return StoreTunnelStats(tunnelStatsJson)
  536. }
  537. // DoClientVerificationRequest performs the "client_verification" API
  538. // request. This request is used to verify that the client is a valid
  539. // Psiphon client, which will determine how the server treats the client
  540. // traffic. The proof-of-validity is platform-specific and the payload
  541. // is opaque to this function but assumed to be JSON.
  542. func (serverContext *ServerContext) DoClientVerificationRequest(
  543. verificationPayload string) error {
  544. params := serverContext.getBaseParams()
  545. if serverContext.psiphonHttpsClient == nil {
  546. params["verificationData"] = json.RawMessage(verificationPayload)
  547. request, err := makeSSHAPIRequestPayload(params)
  548. if err != nil {
  549. return ContextError(err)
  550. }
  551. _, err = serverContext.tunnel.SendAPIRequest(
  552. SERVER_API_CLIENT_VERIFICATION_REQUEST_NAME, request)
  553. if err != nil {
  554. return ContextError(err)
  555. }
  556. } else {
  557. // Legacy web service API request
  558. err := serverContext.doPostRequest(
  559. makeRequestUrl(serverContext.tunnel, "", "client_verification", params),
  560. "application/json",
  561. bytes.NewReader([]byte(verificationPayload)))
  562. if err != nil {
  563. return ContextError(err)
  564. }
  565. }
  566. return nil
  567. }
  568. // doGetRequest makes a tunneled HTTPS request and returns the response body.
  569. func (serverContext *ServerContext) doGetRequest(
  570. requestUrl string) (responseBody []byte, err error) {
  571. response, err := serverContext.psiphonHttpsClient.Get(requestUrl)
  572. if err == nil && response.StatusCode != http.StatusOK {
  573. response.Body.Close()
  574. err = fmt.Errorf("HTTP GET request failed with response code: %d", response.StatusCode)
  575. }
  576. if err != nil {
  577. // Trim this error since it may include long URLs
  578. return nil, ContextError(TrimError(err))
  579. }
  580. defer response.Body.Close()
  581. body, err := ioutil.ReadAll(response.Body)
  582. if err != nil {
  583. return nil, ContextError(err)
  584. }
  585. return body, nil
  586. }
  587. // doPostRequest makes a tunneled HTTPS POST request.
  588. func (serverContext *ServerContext) doPostRequest(
  589. requestUrl string, bodyType string, body io.Reader) (err error) {
  590. response, err := serverContext.psiphonHttpsClient.Post(requestUrl, bodyType, body)
  591. if err == nil && response.StatusCode != http.StatusOK {
  592. response.Body.Close()
  593. err = fmt.Errorf("HTTP POST request failed with response code: %d", response.StatusCode)
  594. }
  595. if err != nil {
  596. // Trim this error since it may include long URLs
  597. return ContextError(TrimError(err))
  598. }
  599. response.Body.Close()
  600. return nil
  601. }
  602. type requestJSONObject map[string]interface{}
  603. // getBaseParams returns all the common API parameters that are included
  604. // with each Psiphon API request. These common parameters are used for
  605. // statistics.
  606. func (serverContext *ServerContext) getBaseParams() requestJSONObject {
  607. params := make(requestJSONObject)
  608. tunnel := serverContext.tunnel
  609. params["session_id"] = serverContext.sessionId
  610. params["client_session_id"] = serverContext.sessionId
  611. params["server_secret"] = tunnel.serverEntry.WebServerSecret
  612. params["propagation_channel_id"] = tunnel.config.PropagationChannelId
  613. params["sponsor_id"] = tunnel.config.SponsorId
  614. params["client_version"] = tunnel.config.ClientVersion
  615. // TODO: client_tunnel_core_version?
  616. params["relay_protocol"] = tunnel.protocol
  617. params["client_platform"] = tunnel.config.ClientPlatform
  618. params["tunnel_whole_device"] = strconv.Itoa(tunnel.config.TunnelWholeDevice)
  619. // The following parameters may be blank and must
  620. // not be sent to the server if blank.
  621. if tunnel.config.DeviceRegion != "" {
  622. params["device_region"] = tunnel.config.DeviceRegion
  623. }
  624. if tunnel.meekStats != nil {
  625. if tunnel.meekStats.DialAddress != "" {
  626. params["meek_dial_address"] = tunnel.meekStats.DialAddress
  627. }
  628. if tunnel.meekStats.ResolvedIPAddress != "" {
  629. params["meek_resolved_ip_address"] = tunnel.meekStats.ResolvedIPAddress
  630. }
  631. if tunnel.meekStats.SNIServerName != "" {
  632. params["meek_sni_server_name"] = tunnel.meekStats.SNIServerName
  633. }
  634. if tunnel.meekStats.HostHeader != "" {
  635. params["meek_host_header"] = tunnel.meekStats.HostHeader
  636. }
  637. transformedHostName := "0"
  638. if tunnel.meekStats.TransformedHostName {
  639. transformedHostName = "1"
  640. }
  641. params["meek_transformed_host_name"] = transformedHostName
  642. }
  643. if tunnel.serverEntry.Region != "" {
  644. params["server_entry_region"] = tunnel.serverEntry.Region
  645. }
  646. if tunnel.serverEntry.LocalSource != "" {
  647. params["server_entry_source"] = tunnel.serverEntry.LocalSource
  648. }
  649. // As with last_connected, this timestamp stat, which may be
  650. // a precise handshake request server timestamp, is truncated
  651. // to hour granularity to avoid introducing a reconstructable
  652. // cross-session user trace into server logs.
  653. localServerEntryTimestamp := TruncateTimestampToHour(tunnel.serverEntry.LocalTimestamp)
  654. if localServerEntryTimestamp != "" {
  655. params["server_entry_timestamp"] = localServerEntryTimestamp
  656. }
  657. return params
  658. }
  659. // makeSSHAPIRequestPayload makes a JSON payload for an SSH API request.
  660. func makeSSHAPIRequestPayload(params requestJSONObject) ([]byte, error) {
  661. jsonPayload, err := json.Marshal(params)
  662. if err != nil {
  663. return nil, ContextError(err)
  664. }
  665. return jsonPayload, nil
  666. }
  667. // makeRequestUrl makes a URL for a web service API request.
  668. func makeRequestUrl(tunnel *Tunnel, port, path string, params requestJSONObject) string {
  669. var requestUrl bytes.Buffer
  670. if port == "" {
  671. port = tunnel.serverEntry.WebServerPort
  672. }
  673. // Note: don't prefix with HTTPS scheme, see comment in doGetRequest.
  674. // e.g., don't do this: requestUrl.WriteString("https://")
  675. requestUrl.WriteString("http://")
  676. requestUrl.WriteString(tunnel.serverEntry.IpAddress)
  677. requestUrl.WriteString(":")
  678. requestUrl.WriteString(port)
  679. requestUrl.WriteString("/")
  680. requestUrl.WriteString(path)
  681. firstParam := true
  682. for name, value := range params {
  683. if strValue, ok := value.(string); ok {
  684. if firstParam {
  685. requestUrl.WriteString("?")
  686. firstParam = false
  687. } else {
  688. requestUrl.WriteString("&")
  689. }
  690. requestUrl.WriteString(name)
  691. requestUrl.WriteString("=")
  692. requestUrl.WriteString(strValue)
  693. }
  694. }
  695. return requestUrl.String()
  696. }
  697. // makePsiphonHttpsClient creates a Psiphon HTTPS client that tunnels web service API
  698. // requests and which validates the web server using the Psiphon server entry web server
  699. // certificate. This is not a general purpose HTTPS client.
  700. // As the custom dialer makes an explicit TLS connection, URLs submitted to the returned
  701. // http.Client should use the "http://" scheme. Otherwise http.Transport will try to do another TLS
  702. // handshake inside the explicit TLS session.
  703. func makePsiphonHttpsClient(tunnel *Tunnel) (httpsClient *http.Client, err error) {
  704. certificate, err := DecodeCertificate(tunnel.serverEntry.WebServerCertificate)
  705. if err != nil {
  706. return nil, ContextError(err)
  707. }
  708. tunneledDialer := func(_, addr string) (conn net.Conn, err error) {
  709. // TODO: check tunnel.isClosed, and apply TUNNEL_PORT_FORWARD_DIAL_TIMEOUT as in Tunnel.Dial?
  710. return tunnel.sshClient.Dial("tcp", addr)
  711. }
  712. timeout := time.Duration(*tunnel.config.PsiphonApiServerTimeoutSeconds) * time.Second
  713. dialer := NewCustomTLSDialer(
  714. &CustomTLSConfig{
  715. Dial: tunneledDialer,
  716. Timeout: timeout,
  717. VerifyLegacyCertificate: certificate,
  718. })
  719. transport := &http.Transport{
  720. Dial: dialer,
  721. ResponseHeaderTimeout: timeout,
  722. }
  723. return &http.Client{
  724. Transport: transport,
  725. Timeout: timeout,
  726. }, nil
  727. }