Exemplo n.º 1
0
// connectedAPIRequestHandler implements the "connected" API request.
// Clients make the connected request once a tunnel connection has been
// established and at least once per day. The last_connected input value,
// which should be a connected_timestamp output from a previous connected
// response, is used to calculate unique user stats.
func connectedAPIRequestHandler(
	support *SupportServices,
	geoIPData GeoIPData,
	params requestJSONObject) ([]byte, error) {

	err := validateRequestParams(support, params, connectedRequestParams)
	if err != nil {
		return nil, common.ContextError(err)
	}

	log.LogRawFieldsWithTimestamp(
		getRequestLogFields(
			support,
			"connected",
			geoIPData,
			params,
			connectedRequestParams))

	connectedResponse := common.ConnectedResponse{
		ConnectedTimestamp: common.TruncateTimestampToHour(common.GetCurrentTimestamp()),
	}

	responsePayload, err := json.Marshal(connectedResponse)
	if err != nil {
		return nil, common.ContextError(err)
	}

	return responsePayload, nil
}
Exemplo n.º 2
0
// RecordRemoteServerListStat records a completed common or OSL
// remote server list resource download. These stats use the same
// persist-until-reported mechanism described in RecordTunnelStats.
func RecordRemoteServerListStat(
	url, etag string) error {

	remoteServerListStat := struct {
		ClientDownloadTimestamp string `json:"client_download_timestamp"`
		URL                     string `json:"url"`
		ETag                    string `json:"etag"`
	}{
		common.TruncateTimestampToHour(common.GetCurrentTimestamp()),
		url,
		etag,
	}

	remoteServerListStatJson, err := json.Marshal(remoteServerListStat)
	if err != nil {
		return common.ContextError(err)
	}

	return StorePersistentStat(
		PERSISTENT_STAT_TYPE_REMOTE_SERVER_LIST, remoteServerListStatJson)
}
Exemplo n.º 3
0
// getBaseParams returns all the common API parameters that are included
// with each Psiphon API request. These common parameters are used for
// statistics.
func (serverContext *ServerContext) getBaseParams() requestJSONObject {

	params := make(requestJSONObject)

	tunnel := serverContext.tunnel

	params["session_id"] = serverContext.sessionId
	params["client_session_id"] = serverContext.sessionId
	params["server_secret"] = tunnel.serverEntry.WebServerSecret
	params["propagation_channel_id"] = tunnel.config.PropagationChannelId
	params["sponsor_id"] = tunnel.config.SponsorId
	params["client_version"] = tunnel.config.ClientVersion
	// TODO: client_tunnel_core_version?
	params["relay_protocol"] = tunnel.protocol
	params["client_platform"] = tunnel.config.ClientPlatform
	params["tunnel_whole_device"] = strconv.Itoa(tunnel.config.TunnelWholeDevice)

	// The following parameters may be blank and must
	// not be sent to the server if blank.

	if tunnel.config.DeviceRegion != "" {
		params["device_region"] = tunnel.config.DeviceRegion
	}
	if tunnel.dialStats != nil {
		if tunnel.dialStats.UpstreamProxyType != "" {
			params["upstream_proxy_type"] = tunnel.dialStats.UpstreamProxyType
		}
		if tunnel.dialStats.UpstreamProxyCustomHeaderNames != nil {
			params["upstream_proxy_custom_header_names"] = tunnel.dialStats.UpstreamProxyCustomHeaderNames
		}
		if tunnel.dialStats.MeekDialAddress != "" {
			params["meek_dial_address"] = tunnel.dialStats.MeekDialAddress
		}
		if tunnel.dialStats.MeekResolvedIPAddress != "" {
			params["meek_resolved_ip_address"] = tunnel.dialStats.MeekResolvedIPAddress
		}
		if tunnel.dialStats.MeekSNIServerName != "" {
			params["meek_sni_server_name"] = tunnel.dialStats.MeekSNIServerName
		}
		if tunnel.dialStats.MeekHostHeader != "" {
			params["meek_host_header"] = tunnel.dialStats.MeekHostHeader
		}
		transformedHostName := "0"
		if tunnel.dialStats.MeekTransformedHostName {
			transformedHostName = "1"
		}
		params["meek_transformed_host_name"] = transformedHostName
	}

	if tunnel.serverEntry.Region != "" {
		params["server_entry_region"] = tunnel.serverEntry.Region
	}

	if tunnel.serverEntry.LocalSource != "" {
		params["server_entry_source"] = tunnel.serverEntry.LocalSource
	}

	// As with last_connected, this timestamp stat, which may be
	// a precise handshake request server timestamp, is truncated
	// to hour granularity to avoid introducing a reconstructable
	// cross-session user trace into server logs.
	localServerEntryTimestamp := common.TruncateTimestampToHour(tunnel.serverEntry.LocalTimestamp)
	if localServerEntryTimestamp != "" {
		params["server_entry_timestamp"] = localServerEntryTimestamp
	}

	return params
}
Exemplo n.º 4
0
// 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(
			common.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 common.HandshakeResponse
	err := json.Unmarshal(response, &handshakeResponse)
	if err != nil {
		return common.ContextError(err)
	}

	serverContext.clientRegion = handshakeResponse.ClientRegion
	NoticeClientRegion(serverContext.clientRegion)

	var decodedServerEntries []*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 := DecodeServerEntry(
			encodedServerEntry,
			common.TruncateTimestampToHour(handshakeResponse.ServerTimestamp),
			common.SERVER_ENTRY_SOURCE_DISCOVERY)
		if err != nil {
			return common.ContextError(err)
		}

		err = ValidateServerEntry(serverEntry)
		if err != nil {
			// Skip this entry and continue with the next one
			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
}