/* * Copyright (c) 2015, Psiphon Inc. * All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package psiphon import ( "encoding/base64" "encoding/json" "errors" "fmt" "net/http" "os" "strconv" "strings" "time" "unicode" "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common" "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol" ) // TODO: allow all params to be configured const ( LEGACY_DATA_STORE_FILENAME = "psiphon.db" DATA_STORE_FILENAME = "psiphon.boltdb" CONNECTION_WORKER_POOL_SIZE = 10 TUNNEL_POOL_SIZE = 1 TUNNEL_CONNECT_TIMEOUT_SECONDS = 20 TUNNEL_OPERATE_SHUTDOWN_TIMEOUT = 1 * time.Second TUNNEL_PORT_FORWARD_DIAL_TIMEOUT_SECONDS = 10 TUNNEL_SSH_KEEP_ALIVE_PAYLOAD_MAX_BYTES = 256 TUNNEL_SSH_KEEP_ALIVE_PERIOD_MIN = 60 * time.Second TUNNEL_SSH_KEEP_ALIVE_PERIOD_MAX = 120 * time.Second TUNNEL_SSH_KEEP_ALIVE_PERIODIC_TIMEOUT_SECONDS = 30 TUNNEL_SSH_KEEP_ALIVE_PERIODIC_INACTIVE_PERIOD = 10 * time.Second TUNNEL_SSH_KEEP_ALIVE_PROBE_TIMEOUT_SECONDS = 5 TUNNEL_SSH_KEEP_ALIVE_PROBE_INACTIVE_PERIOD = 10 * time.Second ESTABLISH_TUNNEL_TIMEOUT_SECONDS = 300 ESTABLISH_TUNNEL_WORK_TIME = 60 * time.Second ESTABLISH_TUNNEL_PAUSE_PERIOD_SECONDS = 5 ESTABLISH_TUNNEL_SERVER_AFFINITY_GRACE_PERIOD = 1 * time.Second HTTP_PROXY_ORIGIN_SERVER_TIMEOUT_SECONDS = 15 HTTP_PROXY_MAX_IDLE_CONNECTIONS_PER_HOST = 50 FETCH_REMOTE_SERVER_LIST_TIMEOUT_SECONDS = 30 FETCH_REMOTE_SERVER_LIST_RETRY_PERIOD_SECONDS = 30 FETCH_REMOTE_SERVER_LIST_STALE_PERIOD = 6 * time.Hour PSIPHON_API_SERVER_TIMEOUT_SECONDS = 20 PSIPHON_API_SHUTDOWN_SERVER_TIMEOUT = 1 * time.Second PSIPHON_API_STATUS_REQUEST_PERIOD_MIN = 5 * time.Minute PSIPHON_API_STATUS_REQUEST_PERIOD_MAX = 10 * time.Minute PSIPHON_API_STATUS_REQUEST_SHORT_PERIOD_MIN = 5 * time.Second PSIPHON_API_STATUS_REQUEST_SHORT_PERIOD_MAX = 10 * time.Second PSIPHON_API_STATUS_REQUEST_PADDING_MAX_BYTES = 256 PSIPHON_API_CONNECTED_REQUEST_PERIOD = 24 * time.Hour PSIPHON_API_CONNECTED_REQUEST_RETRY_PERIOD = 5 * time.Second PSIPHON_API_PERSISTENT_STATS_MAX_COUNT = 100 PSIPHON_API_CLIENT_VERIFICATION_REQUEST_RETRY_PERIOD = 5 * time.Second PSIPHON_API_CLIENT_VERIFICATION_REQUEST_MAX_RETRIES = 10 FETCH_ROUTES_TIMEOUT_SECONDS = 60 DOWNLOAD_UPGRADE_TIMEOUT_SECONDS = 60 DOWNLOAD_UPGRADE_RETRY_PERIOD_SECONDS = 30 DOWNLOAD_UPGRADE_STALE_PERIOD = 6 * time.Hour IMPAIRED_PROTOCOL_CLASSIFICATION_DURATION = 2 * time.Minute IMPAIRED_PROTOCOL_CLASSIFICATION_THRESHOLD = 3 TOTAL_BYTES_TRANSFERRED_NOTICE_PERIOD = 5 * time.Minute TRANSFORM_HOST_NAMES_ALWAYS = "always" TRANSFORM_HOST_NAMES_NEVER = "never" ) // To distinguish omitted timeout params from explicit 0 value timeout // params, these params are int pointers. nil means no param was supplied // so use the default; a non-nil pointer to 0 means no timeout. // Config is the Psiphon configuration specified by the application. This // configuration controls the behavior of the core tunnel functionality. type Config struct { // DataStoreDirectory is the directory in which to store the persistent // database, which contains information such as server entries. // By default, current working directory. // // Warning: If the datastore file, DataStoreDirectory/DATA_STORE_FILENAME, // exists but fails to open for any reason (checksum error, unexpected file // format, etc.) it will be deleted in order to pave a new datastore and // continue running. DataStoreDirectory string // PropagationChannelId is a string identifier which indicates how the // Psiphon client was distributed. This parameter is required. // This value is supplied by and depends on the Psiphon Network, and is // typically embedded in the client binary. PropagationChannelId string // PropagationChannelId is a string identifier which indicates who // is sponsoring this Psiphon client. One purpose of this value is to // determine the home pages for display. This parameter is required. // This value is supplied by and depends on the Psiphon Network, and is // typically embedded in the client binary. SponsorId string // RemoteServerListUrl is a URL which specifies a location to fetch // out-of-band server entries. This facility is used when a tunnel cannot // be established to known servers. // This value is supplied by and depends on the Psiphon Network, and is // typically embedded in the client binary. // // Deprecated: Use RemoteServerListURLs. When RemoteServerListURLs is // not nil, this parameter is ignored. RemoteServerListUrl string // RemoteServerListURLs is list of URLs which specify locations to fetch // out-of-band server entries. This facility is used when a tunnel cannot // be established to known servers. // This value is supplied by and depends on the Psiphon Network, and is // typically embedded in the client binary. // All URLs must point to the same entity with the same ETag. At least // one DownloadURL must have OnlyAfterAttempts = 0. RemoteServerListURLs []*DownloadURL // RemoteServerListDownloadFilename specifies a target filename for // storing the remote server list download. Data is stored in co-located // files (RemoteServerListDownloadFilename.part*) to allow for resumable // downloading. RemoteServerListDownloadFilename string // RemoteServerListSignaturePublicKey specifies a public key that's // used to authenticate the remote server list payload. // This value is supplied by and depends on the Psiphon Network, and is // typically embedded in the client binary. RemoteServerListSignaturePublicKey string // ObfuscatedServerListRootURL is a URL which specifies the root location // from which to fetch obfuscated server list files. // This value is supplied by and depends on the Psiphon Network, and is // typically embedded in the client binary. // // Deprecated: Use ObfuscatedServerListRootURLs. When // ObfuscatedServerListRootURLs is not nil, this parameter is ignored. ObfuscatedServerListRootURL string // ObfuscatedServerListRootURLs is a list of URLs which specify root // locations from which to fetch obfuscated server list files. // This value is supplied by and depends on the Psiphon Network, and is // typically embedded in the client binary. // All URLs must point to the same entity with the same ETag. At least // one DownloadURL must have OnlyAfterAttempts = 0. ObfuscatedServerListRootURLs []*DownloadURL // ObfuscatedServerListDownloadDirectory specifies a target directory for // storing the obfuscated remote server list downloads. Data is stored in // co-located files (.part*) to allow for resumable // downloading. ObfuscatedServerListDownloadDirectory string // ClientVersion is the client version number that the client reports // to the server. The version number refers to the host client application, // not the core tunnel library. One purpose of this value is to enable // automatic updates. // This value is supplied by and depends on the Psiphon Network, and is // typically embedded in the client binary. // Note that sending a ClientPlatform string which includes "windows" // (case insensitive) and a ClientVersion of <= 44 will cause an // error in processing the response to DoConnectedRequest calls. ClientVersion string // ClientPlatform is the client platform ("Windows", "Android", etc.) that // the client reports to the server. ClientPlatform string // TunnelWholeDevice is a flag that is passed through to the handshake // request for stats purposes. Set to 1 when the host application is tunneling // the whole device, 0 otherwise. TunnelWholeDevice int // EgressRegion is a ISO 3166-1 alpha-2 country code which indicates which // country to egress from. For the default, "", the best performing server // in any country is selected. EgressRegion string // TunnelProtocol indicates which protocol to use. Valid values include: // "SSH", "OSSH", "UNFRONTED-MEEK-OSSH", "UNFRONTED-MEEK-HTTPS-OSSH", // "UNFRONTED-MEEK-SESSION-TICKET-OSSH", "FRONTED-MEEK-OSSH", // "FRONTED-MEEK-HTTP-OSSH". // For the default, "", the best performing protocol is used. TunnelProtocol string // EstablishTunnelTimeoutSeconds specifies a time limit after which to halt // the core tunnel controller if no tunnel has been established. The default // is ESTABLISH_TUNNEL_TIMEOUT_SECONDS. EstablishTunnelTimeoutSeconds *int // ListenInterface specifies which interface to listen on. If no interface // is provided then listen on 127.0.0.1. // If 'any' is provided then use 0.0.0.0. // If there are multiple IP addresses on an interface use the first IPv4 address. ListenInterface string // DisableLocalSocksProxy disables running the local SOCKS proxy. DisableLocalSocksProxy bool // LocalSocksProxyPort specifies a port number for the local SOCKS proxy // running at 127.0.0.1. For the default value, 0, the system selects a free // port (a notice reporting the selected port is emitted). LocalSocksProxyPort int // LocalHttpProxyPort specifies a port number for the local HTTP proxy // running at 127.0.0.1. For the default value, 0, the system selects a free // port (a notice reporting the selected port is emitted). LocalHttpProxyPort int // DisableLocalHTTPProxy disables running the local HTTP proxy. DisableLocalHTTPProxy bool // ConnectionWorkerPoolSize specifies how many connection attempts to attempt // in parallel. The default, 0, uses CONNECTION_WORKER_POOL_SIZE which is // recommended. ConnectionWorkerPoolSize int // TunnelPoolSize specifies how many tunnels to run in parallel. Port forwards // are multiplexed over multiple tunnels. The default, 0, uses TUNNEL_POOL_SIZE // which is recommended. TunnelPoolSize int // UpstreamProxyUrl is a URL specifying an upstream proxy to use for all // outbound connections. The URL should include proxy type and authentication // information, as required. See example URLs here: // https://github.com/Psiphon-Labs/psiphon-tunnel-core/tree/master/psiphon/upstreamproxy UpstreamProxyUrl string // CustomHeaders is a set of additional arbitrary HTTP headers that are // added to all plaintext HTTP requests and requests made through an HTTP // upstream proxy when specified by UpstreamProxyUrl. CustomHeaders http.Header // Deprecated: Use CustomHeaders. When CustomHeaders is // not nil, this parameter is ignored. UpstreamProxyCustomHeaders http.Header // NetworkConnectivityChecker is an interface that enables the core tunnel to call // into the host application to check for network connectivity. This parameter is // only applicable to library deployments. NetworkConnectivityChecker NetworkConnectivityChecker // DeviceBinder is an interface that enables the core tunnel to call // into the host application to bind sockets to specific devices. This is used // for VPN routing exclusion. This parameter is only applicable to library // deployments. DeviceBinder DeviceBinder // IPv6Synthesizer is an interface that allows the core tunnel to call // into the host application to synthesize IPv6 addresses from IPv4 ones. This // is used to correctly lookup IPs on DNS64/NAT64 networks. IPv6Synthesizer IPv6Synthesizer // DnsServerGetter is an interface that enables the core tunnel to call // into the host application to discover the native network DNS server settings. // This parameter is only applicable to library deployments. DnsServerGetter DnsServerGetter // TransformHostNames specifies whether to use hostname transformation circumvention // strategies. Set to "always" to always transform, "never" to never transform, and // "", the default, for the default transformation strategy. TransformHostNames string // TargetServerEntry is an encoded server entry. When specified, this server entry // is used exclusively and all other known servers are ignored. TargetServerEntry string // DisableApi disables Psiphon server API calls including handshake, connected, // status, etc. This is used for special case temporary tunnels (Windows VPN mode). DisableApi bool // TargetApiProtocol specifies whether to force use of "ssh" or "web" API protocol. // When blank, the default, the optimal API protocol is used. Note that this // capability check is not applied before the "CandidateServers" count is emitted. // This parameter is intended for testing and debugging only. TargetApiProtocol string // DisableRemoteServerListFetcher disables fetching remote server lists. This is // used for special case temporary tunnels. DisableRemoteServerListFetcher bool // SplitTunnelRoutesUrlFormat is an URL which specifies the location of a routes // file to use for split tunnel mode. The URL must include a placeholder for the // client region to be supplied. Split tunnel mode uses the routes file to classify // port forward destinations as foreign or domestic and does not tunnel domestic // destinations. Split tunnel mode is on when all the SplitTunnel parameters are // supplied. // This value is supplied by and depends on the Psiphon Network, and is // typically embedded in the client binary. SplitTunnelRoutesUrlFormat string // SplitTunnelRoutesSignaturePublicKey specifies a public key that's // used to authenticate the split tunnel routes payload. // This value is supplied by and depends on the Psiphon Network, and is // typically embedded in the client binary. SplitTunnelRoutesSignaturePublicKey string // SplitTunnelDnsServer specifies a DNS server to use when resolving port // forward target domain names to IP addresses for classification. The DNS // server must support TCP requests. SplitTunnelDnsServer string // UpgradeDownloadUrl specifies a URL from which to download a host client upgrade // file, when one is available. The core tunnel controller provides a resumable // download facility which downloads this resource and emits a notice when complete. // This value is supplied by and depends on the Psiphon Network, and is // typically embedded in the client binary. // // Deprecated: Use UpgradeDownloadURLs. When UpgradeDownloadURLs // is not nil, this parameter is ignored. UpgradeDownloadUrl string // UpgradeDownloadURLs is list of URLs which specify locations from which to // download a host client upgrade file, when one is available. The core tunnel // controller provides a resumable download facility which downloads this resource // and emits a notice when complete. // This value is supplied by and depends on the Psiphon Network, and is // typically embedded in the client binary. // All URLs must point to the same entity with the same ETag. At least // one DownloadURL must have OnlyAfterAttempts = 0. UpgradeDownloadURLs []*DownloadURL // UpgradeDownloadClientVersionHeader specifies the HTTP header name for the // entity at UpgradeDownloadURLs which specifies the client version (an integer // value). A HEAD request may be made to check the version number available at // UpgradeDownloadURLs. UpgradeDownloadClientVersionHeader is required when // UpgradeDownloadURLs is specified. UpgradeDownloadClientVersionHeader string // UpgradeDownloadFilename is the local target filename for an upgrade download. // This parameter is required when UpgradeDownloadURLs (or UpgradeDownloadUrl) // is specified. // Data is stored in co-located files (UpgradeDownloadFilename.part*) to allow // for resumable downloading. UpgradeDownloadFilename string // EmitBytesTransferred indicates whether to emit periodic notices showing // bytes sent and received. EmitBytesTransferred bool // UseIndistinguishableTLS enables use of alternative TLS profiles with a less // distinct fingerprint (ClientHello content) than the stock Go TLS. UseIndistinguishableTLS bool // UseTrustedCACertificatesForStockTLS toggles use of the trusted CA certs, specified // in TrustedCACertificatesFilename, for tunneled TLS connections that expect // server certificates signed with public certificate authorities (currently, // only upgrade downloads). This option is used with stock Go TLS in cases where // Go may fail to obtain a list of root CAs from the operating system. // Requires TrustedCACertificatesFilename to be set. UseTrustedCACertificatesForStockTLS bool // TrustedCACertificatesFilename specifies a file containing trusted CA certs. // The file contents should be compatible with OpenSSL's SSL_CTX_load_verify_locations. // When specified, this enables use of OpenSSL for HTTPS requests that require // typical (system CA) server authentication. TrustedCACertificatesFilename string // DisablePeriodicSshKeepAlive indicates whether to send an SSH keepalive every // 1-2 minutes, when the tunnel is idle. If the SSH keepalive times out, the tunnel // is considered to have failed. DisablePeriodicSshKeepAlive bool // DeviceRegion is the optional, reported region the host device is running in. // This input value should be a ISO 3166-1 alpha-2 country code. The device region // is reported to the server in the connected request and recorded for Psiphon // stats. // When provided, this value may be used, pre-connection, to select performance // or circumvention optimization strategies for the given region. DeviceRegion string // EmitDiagnosticNotices indicates whether to output notices containing detailed // information about the Psiphon session. As these notices may contain sensitive // network information, they should not be insecurely distributed or displayed // to users. Default is off. EmitDiagnosticNotices bool // TunnelConnectTimeoutSeconds specifies a single tunnel connection sequence timeout. // Zero value means that connection process will not time out. // If omitted, the default value is TUNNEL_CONNECT_TIMEOUT_SECONDS. TunnelConnectTimeoutSeconds *int // TunnelPortForwardDialTimeoutSeconds specifies a dial timeout per SSH port forward. // Zero value means a port forward dial will not time out. // If omitted, the default value is TUNNEL_PORT_FORWARD_DIAL_TIMEOUT_SECONDS. TunnelPortForwardDialTimeoutSeconds *int // TunnelSshKeepAliveProbeTimeoutSeconds specifies a timeout value for "probe" // SSH keep-alive that is sent upon port forward failure. // Zero value means keep-alive request will not time out. // If omitted, the default value is TUNNEL_SSH_KEEP_ALIVE_PROBE_TIMEOUT_SECONDS. TunnelSshKeepAliveProbeTimeoutSeconds *int // TunnelSshKeepAlivePeriodicTimeoutSeconds specifies a timeout value for regular // SSH keep-alives that are sent periodically. // Zero value means keep-alive request will not time out. // If omitted, the default value is TUNNEL_SSH_KEEP_ALIVE_PERIODIC_TIMEOUT_SECONDS. TunnelSshKeepAlivePeriodicTimeoutSeconds *int // FetchRemoteServerListTimeoutSeconds specifies a timeout value for remote server list // HTTP requests. Zero value means that request will not time out. // If omitted, the default value is FETCH_REMOTE_SERVER_LIST_TIMEOUT_SECONDS. FetchRemoteServerListTimeoutSeconds *int // PsiphonApiServerTimeoutSeconds specifies a timeout for periodic API HTTP // requests to Psiphon server such as stats, home pages, etc. // Zero value means that request will not time out. // If omitted, the default value is PSIPHON_API_SERVER_TIMEOUT_SECONDS. // Note that this value is overridden for final stats requests during shutdown // process in order to prevent hangs. PsiphonApiServerTimeoutSeconds *int // FetchRoutesTimeoutSeconds specifies a timeout value for split tunnel routes // HTTP requests. Zero value means that request will not time out. // If omitted, the default value is FETCH_ROUTES_TIMEOUT_SECONDS. FetchRoutesTimeoutSeconds *int // UpgradeDownloadTimeoutSeconds specifies a timeout value for upgrade download // HTTP requests. Zero value means that request will not time out. // If omitted, the default value is DOWNLOAD_UPGRADE_TIMEOUT_SECONDS. DownloadUpgradeTimeoutSeconds *int // HttpProxyOriginServerTimeoutSeconds specifies an HTTP response header timeout // value in various HTTP relays found in httpProxy. // Zero value means that request will not time out. // If omitted, the default value is HTTP_PROXY_ORIGIN_SERVER_TIMEOUT_SECONDS. HttpProxyOriginServerTimeoutSeconds *int // FetchRemoteServerListRetryPeriodSeconds specifies the delay before // resuming a remote server list download after a failure. // If omitted, the default value FETCH_REMOTE_SERVER_LIST_RETRY_PERIOD_SECONDS. FetchRemoteServerListRetryPeriodSeconds *int // DownloadUpgradestRetryPeriodSeconds specifies the delay before // resuming a client upgrade download after a failure. // If omitted, the default value DOWNLOAD_UPGRADE_RETRY_PERIOD_SECONDS. DownloadUpgradeRetryPeriodSeconds *int // EstablishTunnelPausePeriodSeconds specifies the delay between attempts // to establish tunnels. Briefly pausing allows for network conditions to improve // and for asynchronous operations such as fetch remote server list to complete. // If omitted, the default value is ESTABLISH_TUNNEL_PAUSE_PERIOD_SECONDS. EstablishTunnelPausePeriodSeconds *int // RateLimits specify throttling configuration for the tunnel. RateLimits common.RateLimits // EmitSLOKs indicates whether to emit notices for each seeded SLOK. As this // could reveal user browsing activity, it's intended for debugging and testing // only. EmitSLOKs bool // PacketTunnelTunDeviceFileDescriptor specifies a tun device file descriptor // to use for running a packet tunnel. When this value is > 0, a packet tunnel // is established through the server and packets are relayed via the tun device // file descriptor. The file descriptor is duped in NewController. // When PacketTunnelTunDeviceFileDescriptor is set, TunnelPoolSize must be 1. PacketTunnelTunFileDescriptor int // StaggerConnectionWorkersMilliseconds adds a specified delay before making each // server candidate available to connection workers. This option is enabled when // StaggerConnectionWorkersMilliseconds > 0. StaggerConnectionWorkersMilliseconds int // LimitMeekConnectionWorkers limits the number of concurrent connection workers // attempting connections with meek protocols. // This option is enabled when LimitMeekConnectionWorkers > 0. LimitMeekConnectionWorkers int // LimitMeekBufferSizes selects smaller buffers for meek protocols. LimitMeekBufferSizes bool // IgnoreHandshakeStatsRegexps skips compiling and using stats regexes. IgnoreHandshakeStatsRegexps bool // SessionID specifies a client session ID to use in the Psiphon API. The session // ID should be a randomly generated value that is used only for a single session, // which is defined as the period between a user starting a Psiphon client and // stopping the client. // A session ID must be 32 hex digits (lower case). When blank, a random session // ID is automatically generated. Supply a session ID when a single client session // will cross multiple Controller instances. SessionID string } // DownloadURL specifies a URL for downloading resources along with parameters // for the download strategy. type DownloadURL struct { // URL is the location of the resource. This string is slightly obfuscated // with base64 encoding to mitigate trivial binary executable string scanning. URL string // SkipVerify indicates whether to verify HTTPS certificates. It some // circumvention scenarios, verification is not possible. This must // only be set to true when the resource has its own verification mechanism. SkipVerify bool // OnlyAfterAttempts specifies how to schedule this URL when downloading // the same resource (same entity, same ETag) from multiple different // candidate locations. For a value of N, this URL is only a candidate // after N rounds of attempting the download from other URLs. OnlyAfterAttempts int } // LoadConfig parses and validates a JSON format Psiphon config JSON // string and returns a Config struct populated with config values. func LoadConfig(configJson []byte) (*Config, error) { var config Config err := json.Unmarshal(configJson, &config) if err != nil { return nil, common.ContextError(err) } // Do SetEmitDiagnosticNotices first, to ensure config file errors are emitted. if config.EmitDiagnosticNotices { SetEmitDiagnosticNotices(true) } // These fields are required; the rest are optional if config.PropagationChannelId == "" { return nil, common.ContextError( errors.New("propagation channel ID is missing from the configuration file")) } if config.SponsorId == "" { return nil, common.ContextError( errors.New("sponsor ID is missing from the configuration file")) } if config.DataStoreDirectory == "" { config.DataStoreDirectory, err = os.Getwd() if err != nil { return nil, common.ContextError(err) } } if config.ClientVersion == "" { config.ClientVersion = "0" } _, err = strconv.Atoi(config.ClientVersion) if err != nil { return nil, common.ContextError( fmt.Errorf("invalid client version: %s", err)) } if config.TunnelProtocol != "" { if !common.Contains(protocol.SupportedTunnelProtocols, config.TunnelProtocol) { return nil, common.ContextError( errors.New("invalid tunnel protocol")) } } if config.EstablishTunnelTimeoutSeconds == nil { defaultEstablishTunnelTimeoutSeconds := ESTABLISH_TUNNEL_TIMEOUT_SECONDS config.EstablishTunnelTimeoutSeconds = &defaultEstablishTunnelTimeoutSeconds } if config.ConnectionWorkerPoolSize == 0 { config.ConnectionWorkerPoolSize = CONNECTION_WORKER_POOL_SIZE } if config.TunnelPoolSize == 0 { config.TunnelPoolSize = TUNNEL_POOL_SIZE } if config.CustomHeaders == nil { // Promote legacy parameter config.CustomHeaders = config.UpstreamProxyCustomHeaders config.UpstreamProxyCustomHeaders = nil } if config.NetworkConnectivityChecker != nil { return nil, common.ContextError( errors.New("NetworkConnectivityChecker interface must be set at runtime")) } if config.DeviceBinder != nil { return nil, common.ContextError( errors.New("DeviceBinder interface must be set at runtime")) } if config.DnsServerGetter != nil { return nil, common.ContextError( errors.New("DnsServerGetter interface must be set at runtime")) } if !common.Contains( []string{"", TRANSFORM_HOST_NAMES_ALWAYS, TRANSFORM_HOST_NAMES_NEVER}, config.TransformHostNames) { return nil, common.ContextError( errors.New("invalid TransformHostNames")) } if !common.Contains( []string{"", protocol.PSIPHON_SSH_API_PROTOCOL, protocol.PSIPHON_WEB_API_PROTOCOL}, config.TargetApiProtocol) { return nil, common.ContextError( errors.New("invalid TargetApiProtocol")) } if config.UpgradeDownloadUrl != "" && config.UpgradeDownloadURLs == nil { config.UpgradeDownloadURLs = promoteLegacyDownloadURL(config.UpgradeDownloadUrl) } if config.UpgradeDownloadURLs != nil { err := decodeAndValidateDownloadURLs("UpgradeDownloadURLs", config.UpgradeDownloadURLs) if err != nil { return nil, common.ContextError(err) } if config.UpgradeDownloadClientVersionHeader == "" { return nil, common.ContextError(errors.New("missing UpgradeDownloadClientVersionHeader")) } if config.UpgradeDownloadFilename == "" { return nil, common.ContextError(errors.New("missing UpgradeDownloadFilename")) } } if !config.DisableRemoteServerListFetcher { if config.RemoteServerListUrl != "" && config.RemoteServerListURLs == nil { config.RemoteServerListURLs = promoteLegacyDownloadURL(config.RemoteServerListUrl) } if config.RemoteServerListURLs != nil { err := decodeAndValidateDownloadURLs("RemoteServerListURLs", config.RemoteServerListURLs) if err != nil { return nil, common.ContextError(err) } if config.RemoteServerListSignaturePublicKey == "" { return nil, common.ContextError(errors.New("missing RemoteServerListSignaturePublicKey")) } if config.RemoteServerListDownloadFilename == "" { return nil, common.ContextError(errors.New("missing RemoteServerListDownloadFilename")) } } if config.ObfuscatedServerListRootURL != "" && config.ObfuscatedServerListRootURLs == nil { config.ObfuscatedServerListRootURLs = promoteLegacyDownloadURL(config.ObfuscatedServerListRootURL) } if config.ObfuscatedServerListRootURLs != nil { err := decodeAndValidateDownloadURLs("ObfuscatedServerListRootURLs", config.ObfuscatedServerListRootURLs) if err != nil { return nil, common.ContextError(err) } if config.RemoteServerListSignaturePublicKey == "" { return nil, common.ContextError(errors.New("missing RemoteServerListSignaturePublicKey")) } if config.ObfuscatedServerListDownloadDirectory == "" { return nil, common.ContextError(errors.New("missing ObfuscatedServerListDownloadDirectory")) } } } // This constraint is expected by logic in Controller.runTunnels() if config.PacketTunnelTunFileDescriptor > 0 && config.TunnelPoolSize != 1 { return nil, common.ContextError(errors.New("packet tunnel mode requires TunnelPoolSize to be 1")) } if config.TunnelConnectTimeoutSeconds == nil { defaultTunnelConnectTimeoutSeconds := TUNNEL_CONNECT_TIMEOUT_SECONDS config.TunnelConnectTimeoutSeconds = &defaultTunnelConnectTimeoutSeconds } if config.TunnelPortForwardDialTimeoutSeconds == nil { TunnelPortForwardDialTimeoutSeconds := TUNNEL_PORT_FORWARD_DIAL_TIMEOUT_SECONDS config.TunnelPortForwardDialTimeoutSeconds = &TunnelPortForwardDialTimeoutSeconds } if config.TunnelSshKeepAliveProbeTimeoutSeconds == nil { defaultTunnelSshKeepAliveProbeTimeoutSeconds := TUNNEL_SSH_KEEP_ALIVE_PROBE_TIMEOUT_SECONDS config.TunnelSshKeepAliveProbeTimeoutSeconds = &defaultTunnelSshKeepAliveProbeTimeoutSeconds } if config.TunnelSshKeepAlivePeriodicTimeoutSeconds == nil { defaultTunnelSshKeepAlivePeriodicTimeoutSeconds := TUNNEL_SSH_KEEP_ALIVE_PERIODIC_TIMEOUT_SECONDS config.TunnelSshKeepAlivePeriodicTimeoutSeconds = &defaultTunnelSshKeepAlivePeriodicTimeoutSeconds } if config.FetchRemoteServerListTimeoutSeconds == nil { defaultFetchRemoteServerListTimeoutSeconds := FETCH_REMOTE_SERVER_LIST_TIMEOUT_SECONDS config.FetchRemoteServerListTimeoutSeconds = &defaultFetchRemoteServerListTimeoutSeconds } if config.PsiphonApiServerTimeoutSeconds == nil { defaultPsiphonApiServerTimeoutSeconds := PSIPHON_API_SERVER_TIMEOUT_SECONDS config.PsiphonApiServerTimeoutSeconds = &defaultPsiphonApiServerTimeoutSeconds } if config.FetchRoutesTimeoutSeconds == nil { defaultFetchRoutesTimeoutSeconds := FETCH_ROUTES_TIMEOUT_SECONDS config.FetchRoutesTimeoutSeconds = &defaultFetchRoutesTimeoutSeconds } if config.DownloadUpgradeTimeoutSeconds == nil { defaultDownloadUpgradeTimeoutSeconds := DOWNLOAD_UPGRADE_TIMEOUT_SECONDS config.DownloadUpgradeTimeoutSeconds = &defaultDownloadUpgradeTimeoutSeconds } if config.HttpProxyOriginServerTimeoutSeconds == nil { defaultHttpProxyOriginServerTimeoutSeconds := HTTP_PROXY_ORIGIN_SERVER_TIMEOUT_SECONDS config.HttpProxyOriginServerTimeoutSeconds = &defaultHttpProxyOriginServerTimeoutSeconds } if config.FetchRemoteServerListRetryPeriodSeconds == nil { defaultFetchRemoteServerListRetryPeriodSeconds := FETCH_REMOTE_SERVER_LIST_RETRY_PERIOD_SECONDS config.FetchRemoteServerListRetryPeriodSeconds = &defaultFetchRemoteServerListRetryPeriodSeconds } if config.DownloadUpgradeRetryPeriodSeconds == nil { defaultDownloadUpgradeRetryPeriodSeconds := DOWNLOAD_UPGRADE_RETRY_PERIOD_SECONDS config.DownloadUpgradeRetryPeriodSeconds = &defaultDownloadUpgradeRetryPeriodSeconds } if config.EstablishTunnelPausePeriodSeconds == nil { defaultEstablishTunnelPausePeriodSeconds := ESTABLISH_TUNNEL_PAUSE_PERIOD_SECONDS config.EstablishTunnelPausePeriodSeconds = &defaultEstablishTunnelPausePeriodSeconds } if config.SessionID == "" { sessionID, err := MakeSessionId() if err != nil { return nil, common.ContextError(err) } config.SessionID = sessionID } // SessionID must be PSIPHON_API_CLIENT_SESSION_ID_LENGTH lowercase hex-encoded bytes if len(config.SessionID) != 2*protocol.PSIPHON_API_CLIENT_SESSION_ID_LENGTH || -1 != strings.IndexFunc(config.SessionID, func(c rune) bool { return !unicode.Is(unicode.ASCII_Hex_Digit, c) || unicode.IsUpper(c) }) { return nil, common.ContextError(errors.New("invalid SessionID")) } return &config, nil } func promoteLegacyDownloadURL(URL string) []*DownloadURL { downloadURLs := make([]*DownloadURL, 1) downloadURLs[0] = &DownloadURL{ URL: base64.StdEncoding.EncodeToString([]byte(URL)), SkipVerify: false, OnlyAfterAttempts: 0, } return downloadURLs } func decodeAndValidateDownloadURLs(name string, downloadURLs []*DownloadURL) error { hasOnlyAfterZero := false for _, downloadURL := range downloadURLs { if downloadURL.OnlyAfterAttempts == 0 { hasOnlyAfterZero = true } decodedURL, err := base64.StdEncoding.DecodeString(downloadURL.URL) if err != nil { return fmt.Errorf("failed to decode URL in %s: %s", name, err) } downloadURL.URL = string(decodedURL) } var err error if !hasOnlyAfterZero { err = fmt.Errorf("must be at least one DownloadURL with OnlyAfterAttempts = 0 in %s", name) } return err } func selectDownloadURL(attempt int, downloadURLs []*DownloadURL) (string, string, bool) { // The first OnlyAfterAttempts = 0 URL is the canonical URL. This // is the value used as the key for SetUrlETag when multiple download // URLs can be used to fetch a single entity. canonicalURL := "" for _, downloadURL := range downloadURLs { if downloadURL.OnlyAfterAttempts == 0 { canonicalURL = downloadURL.URL break } } candidates := make([]int, 0) for index, URL := range downloadURLs { if attempt >= URL.OnlyAfterAttempts { candidates = append(candidates, index) } } if len(candidates) < 1 { // This case is not expected, as decodeAndValidateDownloadURLs // should reject configs that would have no candidates for // 0 attempts. return "", "", true } selection, err := common.MakeSecureRandomInt(len(candidates)) if err != nil { selection = 0 } downloadURL := downloadURLs[candidates[selection]] return downloadURL.URL, canonicalURL, downloadURL.SkipVerify }