serverApi.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866
  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. rawMessage := json.RawMessage(statusPayload)
  292. params["statusData"] = &rawMessage
  293. var request []byte
  294. request, err = makeSSHAPIRequestPayload(params)
  295. if err == nil {
  296. _, err = serverContext.tunnel.SendAPIRequest(
  297. SERVER_API_STATUS_REQUEST_NAME, request)
  298. }
  299. } else {
  300. // Legacy web service API request
  301. err = serverContext.doPostRequest(
  302. makeRequestUrl(serverContext.tunnel, "", "status", params),
  303. "application/json",
  304. bytes.NewReader(statusPayload))
  305. }
  306. if err != nil {
  307. // Resend the transfer stats and tunnel stats later
  308. // Note: potential duplicate reports if the server received and processed
  309. // the request but the client failed to receive the response.
  310. putBackStatusRequestPayload(statusPayloadInfo)
  311. return ContextError(err)
  312. }
  313. confirmStatusRequestPayload(statusPayloadInfo)
  314. return nil
  315. }
  316. func (serverContext *ServerContext) getStatusParams(isTunneled bool) requestJSONObject {
  317. params := serverContext.getBaseParams()
  318. // Add a random amount of padding to help prevent stats updates from being
  319. // a predictable size (which often happens when the connection is quiet).
  320. // TODO: base64 encoding of padding means the padding size is not exactly
  321. // [0, PADDING_MAX_BYTES].
  322. randomPadding := MakeSecureRandomPadding(0, PSIPHON_API_STATUS_REQUEST_PADDING_MAX_BYTES)
  323. params["padding"] = base64.StdEncoding.EncodeToString(randomPadding)
  324. // Legacy clients set "connected" to "0" when disconnecting, and this value
  325. // is used to calculate session duration estimates. This is now superseded
  326. // by explicit tunnel stats duration reporting.
  327. // The legacy method of reconstructing session durations is not compatible
  328. // with this client's connected request retries and asynchronous final
  329. // status request attempts. So we simply set this "connected" flag to reflect
  330. // whether the request is sent tunneled or not.
  331. connected := "1"
  332. if !isTunneled {
  333. connected = "0"
  334. }
  335. params["connected"] = connected
  336. return params
  337. }
  338. // statusRequestPayloadInfo is a temporary structure for data used to
  339. // either "clear" or "put back" status request payload data depending
  340. // on whether or not the request succeeded.
  341. type statusRequestPayloadInfo struct {
  342. serverId string
  343. transferStats *transferstats.AccumulatedStats
  344. tunnelStats [][]byte
  345. }
  346. func makeStatusRequestPayload(
  347. serverId string) ([]byte, *statusRequestPayloadInfo, error) {
  348. transferStats := transferstats.TakeOutStatsForServer(serverId)
  349. tunnelStats, err := TakeOutUnreportedTunnelStats(
  350. PSIPHON_API_TUNNEL_STATS_MAX_COUNT)
  351. if err != nil {
  352. NoticeAlert(
  353. "TakeOutUnreportedTunnelStats failed: %s", ContextError(err))
  354. tunnelStats = nil
  355. // Proceed with transferStats only
  356. }
  357. payloadInfo := &statusRequestPayloadInfo{
  358. serverId, transferStats, tunnelStats}
  359. payload := make(map[string]interface{})
  360. hostBytes, bytesTransferred := transferStats.GetStatsForStatusRequest()
  361. payload["host_bytes"] = hostBytes
  362. payload["bytes_transferred"] = bytesTransferred
  363. // We're not recording these fields, but the server requires them.
  364. payload["page_views"] = make([]string, 0)
  365. payload["https_requests"] = make([]string, 0)
  366. // Tunnel stats records are already in JSON format
  367. jsonTunnelStats := make([]json.RawMessage, len(tunnelStats))
  368. for i, tunnelStatsRecord := range tunnelStats {
  369. jsonTunnelStats[i] = json.RawMessage(tunnelStatsRecord)
  370. }
  371. payload["tunnel_stats"] = jsonTunnelStats
  372. jsonPayload, err := json.Marshal(payload)
  373. if err != nil {
  374. // Send the transfer stats and tunnel stats later
  375. putBackStatusRequestPayload(payloadInfo)
  376. return nil, nil, ContextError(err)
  377. }
  378. return jsonPayload, payloadInfo, nil
  379. }
  380. func putBackStatusRequestPayload(payloadInfo *statusRequestPayloadInfo) {
  381. transferstats.PutBackStatsForServer(
  382. payloadInfo.serverId, payloadInfo.transferStats)
  383. err := PutBackUnreportedTunnelStats(payloadInfo.tunnelStats)
  384. if err != nil {
  385. // These tunnel stats records won't be resent under after a
  386. // datastore re-initialization.
  387. NoticeAlert(
  388. "PutBackUnreportedTunnelStats failed: %s", ContextError(err))
  389. }
  390. }
  391. func confirmStatusRequestPayload(payloadInfo *statusRequestPayloadInfo) {
  392. err := ClearReportedTunnelStats(payloadInfo.tunnelStats)
  393. if err != nil {
  394. // These tunnel stats records may be resent.
  395. NoticeAlert(
  396. "ClearReportedTunnelStats failed: %s", ContextError(err))
  397. }
  398. }
  399. // TryUntunneledStatusRequest makes direct connections to the specified
  400. // server (if supported) in an attempt to send useful bytes transferred
  401. // and tunnel duration stats after a tunnel has alreay failed.
  402. // The tunnel is assumed to be closed, but its config, protocol, and
  403. // context values must still be valid.
  404. // TryUntunneledStatusRequest emits notices detailing failed attempts.
  405. func (serverContext *ServerContext) TryUntunneledStatusRequest(isShutdown bool) error {
  406. for _, port := range serverContext.tunnel.serverEntry.GetUntunneledWebRequestPorts() {
  407. err := serverContext.doUntunneledStatusRequest(port, isShutdown)
  408. if err == nil {
  409. return nil
  410. }
  411. NoticeAlert("doUntunneledStatusRequest failed for %s:%s: %s",
  412. serverContext.tunnel.serverEntry.IpAddress, port, err)
  413. }
  414. return errors.New("all attempts failed")
  415. }
  416. // doUntunneledStatusRequest attempts an untunneled status request.
  417. func (serverContext *ServerContext) doUntunneledStatusRequest(
  418. port string, isShutdown bool) error {
  419. tunnel := serverContext.tunnel
  420. certificate, err := DecodeCertificate(tunnel.serverEntry.WebServerCertificate)
  421. if err != nil {
  422. return ContextError(err)
  423. }
  424. timeout := time.Duration(*tunnel.config.PsiphonApiServerTimeoutSeconds) * time.Second
  425. dialConfig := tunnel.untunneledDialConfig
  426. if isShutdown {
  427. timeout = PSIPHON_API_SHUTDOWN_SERVER_TIMEOUT
  428. // Use a copy of DialConfig without pendingConns. This ensures
  429. // this request isn't interrupted/canceled. This measure should
  430. // be used only with the very short PSIPHON_API_SHUTDOWN_SERVER_TIMEOUT.
  431. dialConfig = new(DialConfig)
  432. *dialConfig = *tunnel.untunneledDialConfig
  433. }
  434. url := makeRequestUrl(tunnel, port, "status", serverContext.getStatusParams(false))
  435. httpClient, url, err := MakeUntunneledHttpsClient(
  436. dialConfig,
  437. certificate,
  438. url,
  439. timeout)
  440. if err != nil {
  441. return ContextError(err)
  442. }
  443. statusPayload, statusPayloadInfo, err := makeStatusRequestPayload(tunnel.serverEntry.IpAddress)
  444. if err != nil {
  445. return ContextError(err)
  446. }
  447. bodyType := "application/json"
  448. body := bytes.NewReader(statusPayload)
  449. response, err := httpClient.Post(url, bodyType, body)
  450. if err == nil && response.StatusCode != http.StatusOK {
  451. response.Body.Close()
  452. err = fmt.Errorf("HTTP POST request failed with response code: %d", response.StatusCode)
  453. }
  454. if err != nil {
  455. // Resend the transfer stats and tunnel stats later
  456. // Note: potential duplicate reports if the server received and processed
  457. // the request but the client failed to receive the response.
  458. putBackStatusRequestPayload(statusPayloadInfo)
  459. // Trim this error since it may include long URLs
  460. return ContextError(TrimError(err))
  461. }
  462. confirmStatusRequestPayload(statusPayloadInfo)
  463. response.Body.Close()
  464. return nil
  465. }
  466. // RecordTunnelStats records a tunnel duration and bytes
  467. // sent and received for subsequent reporting and quality
  468. // analysis.
  469. //
  470. // Tunnel durations are precisely measured client-side
  471. // and reported in status requests. As the duration is
  472. // not determined until the tunnel is closed, tunnel
  473. // stats records are stored in the persistent datastore
  474. // and reported via subsequent status requests sent to any
  475. // Psiphon server.
  476. //
  477. // Since the status request that reports a tunnel stats
  478. // record is not necessarily handled by the same server, the
  479. // tunnel stats records include the original server ID.
  480. //
  481. // Other fields that may change between tunnel stats recording
  482. // and reporting include client geo data, propagation channel,
  483. // sponsor ID, client version. These are not stored in the
  484. // datastore (client region, in particular, since that would
  485. // create an on-disk record of user location).
  486. // TODO: the server could encrypt, with a nonce and key unknown to
  487. // the client, a blob containing this data; return it in the
  488. // handshake response; and the client could store and later report
  489. // this blob with its tunnel stats records.
  490. //
  491. // Multiple "status" requests may be in flight at once (due
  492. // to multi-tunnel, asynchronous final status retry, and
  493. // aggressive status requests for pre-registered tunnels),
  494. // To avoid duplicate reporting, tunnel stats records are
  495. // "taken-out" by a status request and then "put back" in
  496. // case the request fails.
  497. //
  498. // Note: since tunnel stats records have a globally unique
  499. // identifier (sessionId + tunnelNumber), we could tolerate
  500. // duplicate reporting and filter our duplicates on the
  501. // server-side. Permitting duplicate reporting could increase
  502. // the velocity of reporting (for example, both the asynchronous
  503. // untunneled final status requests and the post-connected
  504. // immediate startus requests could try to report the same tunnel
  505. // stats).
  506. // Duplicate reporting may also occur when a server receives and
  507. // processes a status request but the client fails to receive
  508. // the response.
  509. func RecordTunnelStats(
  510. sessionId string,
  511. tunnelNumber int64,
  512. tunnelServerIpAddress string,
  513. serverHandshakeTimestamp, duration string,
  514. totalBytesSent, totalBytesReceived int64) error {
  515. tunnelStats := struct {
  516. SessionId string `json:"session_id"`
  517. TunnelNumber int64 `json:"tunnel_number"`
  518. TunnelServerIpAddress string `json:"tunnel_server_ip_address"`
  519. ServerHandshakeTimestamp string `json:"server_handshake_timestamp"`
  520. Duration string `json:"duration"`
  521. TotalBytesSent int64 `json:"total_bytes_sent"`
  522. TotalBytesReceived int64 `json:"total_bytes_received"`
  523. }{
  524. sessionId,
  525. tunnelNumber,
  526. tunnelServerIpAddress,
  527. serverHandshakeTimestamp,
  528. duration,
  529. totalBytesSent,
  530. totalBytesReceived,
  531. }
  532. tunnelStatsJson, err := json.Marshal(tunnelStats)
  533. if err != nil {
  534. return ContextError(err)
  535. }
  536. return StoreTunnelStats(tunnelStatsJson)
  537. }
  538. // DoClientVerificationRequest performs the "client_verification" API
  539. // request. This request is used to verify that the client is a valid
  540. // Psiphon client, which will determine how the server treats the client
  541. // traffic. The proof-of-validity is platform-specific and the payload
  542. // is opaque to this function but assumed to be JSON.
  543. func (serverContext *ServerContext) DoClientVerificationRequest(
  544. verificationPayload string) error {
  545. params := serverContext.getBaseParams()
  546. if serverContext.psiphonHttpsClient == nil {
  547. rawMessage := json.RawMessage(verificationPayload)
  548. params["verificationData"] = &rawMessage
  549. request, err := makeSSHAPIRequestPayload(params)
  550. if err != nil {
  551. return ContextError(err)
  552. }
  553. _, err = serverContext.tunnel.SendAPIRequest(
  554. SERVER_API_CLIENT_VERIFICATION_REQUEST_NAME, request)
  555. if err != nil {
  556. return ContextError(err)
  557. }
  558. } else {
  559. // Legacy web service API request
  560. err := serverContext.doPostRequest(
  561. makeRequestUrl(serverContext.tunnel, "", "client_verification", params),
  562. "application/json",
  563. bytes.NewReader([]byte(verificationPayload)))
  564. if err != nil {
  565. return ContextError(err)
  566. }
  567. }
  568. return nil
  569. }
  570. // doGetRequest makes a tunneled HTTPS request and returns the response body.
  571. func (serverContext *ServerContext) doGetRequest(
  572. requestUrl string) (responseBody []byte, err error) {
  573. response, err := serverContext.psiphonHttpsClient.Get(requestUrl)
  574. if err == nil && response.StatusCode != http.StatusOK {
  575. response.Body.Close()
  576. err = fmt.Errorf("HTTP GET request failed with response code: %d", response.StatusCode)
  577. }
  578. if err != nil {
  579. // Trim this error since it may include long URLs
  580. return nil, ContextError(TrimError(err))
  581. }
  582. defer response.Body.Close()
  583. body, err := ioutil.ReadAll(response.Body)
  584. if err != nil {
  585. return nil, ContextError(err)
  586. }
  587. return body, nil
  588. }
  589. // doPostRequest makes a tunneled HTTPS POST request.
  590. func (serverContext *ServerContext) doPostRequest(
  591. requestUrl string, bodyType string, body io.Reader) (err error) {
  592. response, err := serverContext.psiphonHttpsClient.Post(requestUrl, bodyType, body)
  593. if err == nil && response.StatusCode != http.StatusOK {
  594. response.Body.Close()
  595. err = fmt.Errorf("HTTP POST request failed with response code: %d", response.StatusCode)
  596. }
  597. if err != nil {
  598. // Trim this error since it may include long URLs
  599. return ContextError(TrimError(err))
  600. }
  601. response.Body.Close()
  602. return nil
  603. }
  604. type requestJSONObject map[string]interface{}
  605. // getBaseParams returns all the common API parameters that are included
  606. // with each Psiphon API request. These common parameters are used for
  607. // statistics.
  608. func (serverContext *ServerContext) getBaseParams() requestJSONObject {
  609. params := make(requestJSONObject)
  610. tunnel := serverContext.tunnel
  611. params["session_id"] = serverContext.sessionId
  612. params["client_session_id"] = serverContext.sessionId
  613. params["server_secret"] = tunnel.serverEntry.WebServerSecret
  614. params["propagation_channel_id"] = tunnel.config.PropagationChannelId
  615. params["sponsor_id"] = tunnel.config.SponsorId
  616. params["client_version"] = tunnel.config.ClientVersion
  617. // TODO: client_tunnel_core_version?
  618. params["relay_protocol"] = tunnel.protocol
  619. params["client_platform"] = tunnel.config.ClientPlatform
  620. params["tunnel_whole_device"] = strconv.Itoa(tunnel.config.TunnelWholeDevice)
  621. // The following parameters may be blank and must
  622. // not be sent to the server if blank.
  623. if tunnel.config.DeviceRegion != "" {
  624. params["device_region"] = tunnel.config.DeviceRegion
  625. }
  626. if tunnel.meekStats != nil {
  627. if tunnel.meekStats.DialAddress != "" {
  628. params["meek_dial_address"] = tunnel.meekStats.DialAddress
  629. }
  630. if tunnel.meekStats.ResolvedIPAddress != "" {
  631. params["meek_resolved_ip_address"] = tunnel.meekStats.ResolvedIPAddress
  632. }
  633. if tunnel.meekStats.SNIServerName != "" {
  634. params["meek_sni_server_name"] = tunnel.meekStats.SNIServerName
  635. }
  636. if tunnel.meekStats.HostHeader != "" {
  637. params["meek_host_header"] = tunnel.meekStats.HostHeader
  638. }
  639. transformedHostName := "0"
  640. if tunnel.meekStats.TransformedHostName {
  641. transformedHostName = "1"
  642. }
  643. params["meek_transformed_host_name"] = transformedHostName
  644. }
  645. if tunnel.serverEntry.Region != "" {
  646. params["server_entry_region"] = tunnel.serverEntry.Region
  647. }
  648. if tunnel.serverEntry.LocalSource != "" {
  649. params["server_entry_source"] = tunnel.serverEntry.LocalSource
  650. }
  651. // As with last_connected, this timestamp stat, which may be
  652. // a precise handshake request server timestamp, is truncated
  653. // to hour granularity to avoid introducing a reconstructable
  654. // cross-session user trace into server logs.
  655. localServerEntryTimestamp := TruncateTimestampToHour(tunnel.serverEntry.LocalTimestamp)
  656. if localServerEntryTimestamp != "" {
  657. params["server_entry_timestamp"] = localServerEntryTimestamp
  658. }
  659. return params
  660. }
  661. // makeSSHAPIRequestPayload makes a JSON payload for an SSH API request.
  662. func makeSSHAPIRequestPayload(params requestJSONObject) ([]byte, error) {
  663. jsonPayload, err := json.Marshal(params)
  664. if err != nil {
  665. return nil, ContextError(err)
  666. }
  667. return jsonPayload, nil
  668. }
  669. // makeRequestUrl makes a URL for a web service API request.
  670. func makeRequestUrl(tunnel *Tunnel, port, path string, params requestJSONObject) string {
  671. var requestUrl bytes.Buffer
  672. if port == "" {
  673. port = tunnel.serverEntry.WebServerPort
  674. }
  675. // Note: don't prefix with HTTPS scheme, see comment in doGetRequest.
  676. // e.g., don't do this: requestUrl.WriteString("https://")
  677. requestUrl.WriteString("http://")
  678. requestUrl.WriteString(tunnel.serverEntry.IpAddress)
  679. requestUrl.WriteString(":")
  680. requestUrl.WriteString(port)
  681. requestUrl.WriteString("/")
  682. requestUrl.WriteString(path)
  683. firstParam := true
  684. for name, value := range params {
  685. if strValue, ok := value.(string); ok {
  686. if firstParam {
  687. requestUrl.WriteString("?")
  688. firstParam = false
  689. } else {
  690. requestUrl.WriteString("&")
  691. }
  692. requestUrl.WriteString(name)
  693. requestUrl.WriteString("=")
  694. requestUrl.WriteString(strValue)
  695. }
  696. }
  697. return requestUrl.String()
  698. }
  699. // makePsiphonHttpsClient creates a Psiphon HTTPS client that tunnels web service API
  700. // requests and which validates the web server using the Psiphon server entry web server
  701. // certificate. This is not a general purpose HTTPS client.
  702. // As the custom dialer makes an explicit TLS connection, URLs submitted to the returned
  703. // http.Client should use the "http://" scheme. Otherwise http.Transport will try to do another TLS
  704. // handshake inside the explicit TLS session.
  705. func makePsiphonHttpsClient(tunnel *Tunnel) (httpsClient *http.Client, err error) {
  706. certificate, err := DecodeCertificate(tunnel.serverEntry.WebServerCertificate)
  707. if err != nil {
  708. return nil, ContextError(err)
  709. }
  710. tunneledDialer := func(_, addr string) (conn net.Conn, err error) {
  711. // TODO: check tunnel.isClosed, and apply TUNNEL_PORT_FORWARD_DIAL_TIMEOUT as in Tunnel.Dial?
  712. return tunnel.sshClient.Dial("tcp", addr)
  713. }
  714. timeout := time.Duration(*tunnel.config.PsiphonApiServerTimeoutSeconds) * time.Second
  715. dialer := NewCustomTLSDialer(
  716. &CustomTLSConfig{
  717. Dial: tunneledDialer,
  718. Timeout: timeout,
  719. VerifyLegacyCertificate: certificate,
  720. })
  721. transport := &http.Transport{
  722. Dial: dialer,
  723. ResponseHeaderTimeout: timeout,
  724. }
  725. return &http.Client{
  726. Transport: transport,
  727. Timeout: timeout,
  728. }, nil
  729. }