// StoreServerEntry adds the server entry to the data store. // A newly stored (or re-stored) server entry is assigned the next-to-top // rank for iteration order (the previous top ranked entry is promoted). The // purpose of inserting at next-to-top is to keep the last selected server // as the top ranked server. // When replaceIfExists is true, an existing server entry record is // overwritten; otherwise, the existing record is unchanged. // If the server entry data is malformed, an alert notice is issued and // the entry is skipped; no error is returned. func StoreServerEntry(serverEntry *protocol.ServerEntry, replaceIfExists bool) error { checkInitDataStore() // Server entries should already be validated before this point, // so instead of skipping we fail with an error. err := protocol.ValidateServerEntry(serverEntry) if err != nil { return common.ContextError(errors.New("invalid server entry")) } // BoltDB implementation note: // For simplicity, we don't maintain indexes on server entry // region or supported protocols. Instead, we perform full-bucket // scans with a filter. With a small enough database (thousands or // even tens of thousand of server entries) and common enough // values (e.g., many servers support all protocols), performance // is expected to be acceptable. err = singleton.db.Update(func(tx *bolt.Tx) error { serverEntries := tx.Bucket([]byte(serverEntriesBucket)) // Check not only that the entry exists, but is valid. This // will replace in the rare case where the data is corrupt. existingServerEntryValid := false existingData := serverEntries.Get([]byte(serverEntry.IpAddress)) if existingData != nil { existingServerEntry := new(protocol.ServerEntry) if json.Unmarshal(existingData, existingServerEntry) == nil { existingServerEntryValid = true } } if existingServerEntryValid && !replaceIfExists { // Disabling this notice, for now, as it generates too much noise // in diagnostics with clients that always submit embedded servers // to the core on each run. // NoticeInfo("ignored update for server %s", serverEntry.IpAddress) return nil } data, err := json.Marshal(serverEntry) if err != nil { return common.ContextError(err) } err = serverEntries.Put([]byte(serverEntry.IpAddress), data) if err != nil { return common.ContextError(err) } err = insertRankedServerEntry(tx, serverEntry.IpAddress, 1) if err != nil { return common.ContextError(err) } NoticeInfo("updated server %s", serverEntry.IpAddress) return nil }) if err != nil { return common.ContextError(err) } return nil }
// doHandshakeRequest performs the "handshake" API request. The handshake // returns upgrade info, newly discovered server entries -- which are // stored -- and sponsor info (home pages, stat regexes). func (serverContext *ServerContext) doHandshakeRequest() error { params := serverContext.getBaseParams() // *TODO*: this is obsolete? /* serverEntryIpAddresses, err := GetServerEntryIpAddresses() if err != nil { return common.ContextError(err) } // Submit a list of known servers -- this will be used for // discovery statistics. for _, ipAddress := range serverEntryIpAddresses { params = append(params, requestParam{"known_server", ipAddress}) } */ var response []byte if serverContext.psiphonHttpsClient == nil { request, err := makeSSHAPIRequestPayload(params) if err != nil { return common.ContextError(err) } response, err = serverContext.tunnel.SendAPIRequest( protocol.PSIPHON_API_HANDSHAKE_REQUEST_NAME, request) if err != nil { return common.ContextError(err) } } else { // Legacy web service API request responseBody, err := serverContext.doGetRequest( makeRequestUrl(serverContext.tunnel, "", "handshake", params)) if err != nil { return common.ContextError(err) } // Skip legacy format lines and just parse the JSON config line configLinePrefix := []byte("Config: ") for _, line := range bytes.Split(responseBody, []byte("\n")) { if bytes.HasPrefix(line, configLinePrefix) { response = line[len(configLinePrefix):] break } } if len(response) == 0 { return common.ContextError(errors.New("no config line found")) } } // Legacy fields: // - 'preemptive_reconnect_lifetime_milliseconds' is unused and ignored // - 'ssh_session_id' is ignored; client session ID is used instead var handshakeResponse protocol.HandshakeResponse err := json.Unmarshal(response, &handshakeResponse) if err != nil { return common.ContextError(err) } serverContext.clientRegion = handshakeResponse.ClientRegion NoticeClientRegion(serverContext.clientRegion) var decodedServerEntries []*protocol.ServerEntry // Store discovered server entries // We use the server's time, as it's available here, for the server entry // timestamp since this is more reliable than the client time. for _, encodedServerEntry := range handshakeResponse.EncodedServerList { serverEntry, err := protocol.DecodeServerEntry( encodedServerEntry, common.TruncateTimestampToHour(handshakeResponse.ServerTimestamp), protocol.SERVER_ENTRY_SOURCE_DISCOVERY) if err != nil { return common.ContextError(err) } err = protocol.ValidateServerEntry(serverEntry) if err != nil { // Skip this entry and continue with the next one NoticeAlert("invalid server entry: %s", err) continue } decodedServerEntries = append(decodedServerEntries, serverEntry) } // The reason we are storing the entire array of server entries at once rather // than one at a time is that some desirable side-effects get triggered by // StoreServerEntries that don't get triggered by StoreServerEntry. err = StoreServerEntries(decodedServerEntries, true) if err != nil { return common.ContextError(err) } // TODO: formally communicate the sponsor and upgrade info to an // outer client via some control interface. for _, homepage := range handshakeResponse.Homepages { NoticeHomepage(homepage) } serverContext.clientUpgradeVersion = handshakeResponse.UpgradeClientVersion if handshakeResponse.UpgradeClientVersion != "" { NoticeClientUpgradeAvailable(handshakeResponse.UpgradeClientVersion) } else { NoticeClientIsLatestVersion("") } var regexpsNotices []string serverContext.statsRegexps, regexpsNotices = transferstats.MakeRegexps( handshakeResponse.PageViewRegexes, handshakeResponse.HttpsRequestRegexes) for _, notice := range regexpsNotices { NoticeAlert(notice) } serverContext.serverHandshakeTimestamp = handshakeResponse.ServerTimestamp return nil }