func (serverEntry *ServerEntry) GetUntunneledWebRequestPorts() []string { ports := make([]string, 0) if common.Contains(serverEntry.Capabilities, common.CAPABILITY_UNTUNNELED_WEB_API_REQUESTS) { // Server-side configuration quirk: there's a port forward from // port 443 to the web server, which we can try, except on servers // running FRONTED_MEEK, which listens on port 443. if !serverEntry.SupportsProtocol(common.TUNNEL_PROTOCOL_FRONTED_MEEK) { ports = append(ports, "443") } ports = append(ports, serverEntry.WebServerPort) } return ports }
// NewClientSeedState creates a new client seed state to track // client progress towards seeding SLOKs. psiphond maintains one // ClientSeedState for each connected client. // // A signal is sent on signalIssueSLOKs when sufficient progress // has been made that a new SLOK *may* be issued. psiphond will // receive the signal and then call GetClientSeedPayload/IssueSLOKs // to issue SLOKs, generate payload, and send to the client. The // sender will not block sending to signalIssueSLOKs; the channel // should be appropriately buffered. func (config *Config) NewClientSeedState( clientRegion, propagationChannelID string, signalIssueSLOKs chan struct{}) *ClientSeedState { config.ReloadableFile.RLock() defer config.ReloadableFile.RUnlock() for _, scheme := range config.Schemes { // Only the first matching scheme is selected. // Note: this implementation assumes a few simple schemes. For more // schemes with many propagation channel IDs or region filters, use // maps for more efficient lookup. if scheme.epoch.Before(time.Now().UTC()) && common.Contains(scheme.PropagationChannelIDs, propagationChannelID) && (len(scheme.Regions) == 0 || common.Contains(scheme.Regions, clientRegion)) { // Empty progress is initialized up front for all seed specs. Once // created, the progress structure is read-only (the slice, not the // TrafficValue fields); this permits lock-free operation. progress := make([]*TrafficValues, len(scheme.SeedSpecs)) for index := 0; index < len(scheme.SeedSpecs); index++ { progress[index] = &TrafficValues{} } return &ClientSeedState{ scheme: scheme, propagationChannelID: propagationChannelID, signalIssueSLOKs: signalIssueSLOKs, progressSLOKTime: getSLOKTime(scheme.SeedPeriodNanoseconds), progress: progress, issuedSLOKs: make(map[string]*SLOK), payloadSLOKs: nil, } } } return &ClientSeedState{} }
func (sshClient *sshClient) isPortForwardPermitted( portForwardType int, host string, port int) bool { sshClient.Lock() defer sshClient.Unlock() if !sshClient.handshakeState.completed { return false } if common.Contains(SSH_DISALLOWED_PORT_FORWARD_HOSTS, host) { return false } var allowPorts []int if portForwardType == portForwardTypeTCP { allowPorts = sshClient.trafficRules.AllowTCPPorts } else { allowPorts = sshClient.trafficRules.AllowUDPPorts } if len(allowPorts) == 0 { return true } // TODO: faster lookup? if len(allowPorts) > 0 { for _, allowPort := range allowPorts { if port == allowPort { return true } } } // TODO: AllowSubnets won't match when host is a domain. // Callers should resolve domain host before checking // isPortForwardPermitted. if ip := net.ParseIP(host); ip != nil { for _, subnet := range sshClient.trafficRules.AllowSubnets { // Note: ignoring error as config has been validated _, network, _ := net.ParseCIDR(subnet) if network.Contains(ip) { return true } } } return false }
func hasExpectedCustomHeaders(h http.Header) bool { for name, values := range upstreamProxyCustomHeaders { if h[name] == nil { return false } // Order may not be the same for _, value := range values { if !common.Contains(h[name], value) { return false } } } return true }
func (body *jwtBody) verifyJWTBody() (validApkCert, validApkPackageName bool) { // Verify apk certificate digest if body.ApkCertificateDigestSha256 != nil { if len(*body.ApkCertificateDigestSha256) > 0 && (*body.ApkCertificateDigestSha256)[0] == psiphon3Base64CertHash { validApkCert = true } } // Verify apk package name if body.ApkPackageName != nil && common.Contains(psiphonApkPackagenames, *body.ApkPackageName) { validApkPackageName = true } return }
// StorePersistentStats adds a new persistent stat record, which // is set to StateUnreported and is an immediate candidate for // reporting. // // The stat is a JSON byte array containing fields as // required by the Psiphon server API. It's assumed that the // JSON value contains enough unique information for the value to // function as a key in the key/value datastore. This assumption // is currently satisfied by the fields sessionId + tunnelNumber // for tunnel stats, and URL + ETag for remote server list stats. func StorePersistentStat(statType string, stat []byte) error { checkInitDataStore() if !common.Contains(persistentStatTypes, statType) { return common.ContextError(fmt.Errorf("invalid persistent stat type: %s", statType)) } err := singleton.db.Update(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(statType)) err := bucket.Put(stat, persistentStatStateUnreported) return err }) if err != nil { return common.ContextError(err) } return nil }
// newTargetServerEntryIterator is a helper for initializing the TargetServerEntry case func newTargetServerEntryIterator(config *Config) (iterator *ServerEntryIterator, err error) { serverEntry, err := protocol.DecodeServerEntry( config.TargetServerEntry, common.GetCurrentTimestamp(), protocol.SERVER_ENTRY_SOURCE_TARGET) if err != nil { return nil, err } if config.EgressRegion != "" && serverEntry.Region != config.EgressRegion { return nil, errors.New("TargetServerEntry does not support EgressRegion") } if config.TunnelProtocol != "" { // Note: same capability/protocol mapping as in StoreServerEntry requiredCapability := strings.TrimSuffix(config.TunnelProtocol, "-OSSH") if !common.Contains(serverEntry.Capabilities, requiredCapability) { return nil, errors.New("TargetServerEntry does not support TunnelProtocol") } } iterator = &ServerEntryIterator{ isTargetServerEntryIterator: true, hasNextTargetServerEntry: true, targetServerEntry: serverEntry, } NoticeInfo("using TargetServerEntry: %s", serverEntry.IpAddress) return iterator, nil }
// SupportsSSHAPIRequests returns true when the server supports // SSH API requests. func (serverEntry *ServerEntry) SupportsSSHAPIRequests() bool { return common.Contains(serverEntry.Capabilities, common.CAPABILITY_SSH_API_REQUESTS) }
// Pave creates the full set of OSL files, for all schemes in the // configuration, to be dropped in an out-of-band distribution site. // Only OSLs for the propagation channel ID associated with the // distribution site are paved. This function is used by automation. // // The Name component of each file relates to the values returned by // the client functions GetRegistryURL and GetOSLFileURL. // // Pave returns a pave file for the entire registry of all OSLs from // epoch. It only returns pave files for OSLs referenced in // paveServerEntries. paveServerEntries is a list of maps, one for each // scheme, from the first SLOK time period identifying an OSL to a // payload to encrypt and pave. // The registry file spec MD5 checksum values are populated only for // OSLs referenced in paveServerEntries. To ensure a registry is fully // populated with hashes for skipping redownloading, all OSLs should // be paved. // // Automation is responsible for consistently distributing server entries // to OSLs in the case where OSLs are repaved in subsequent calls. func (config *Config) Pave( endTime time.Time, propagationChannelID string, signingPublicKey string, signingPrivateKey string, paveServerEntries []map[time.Time]string) ([]*PaveFile, error) { config.ReloadableFile.RLock() defer config.ReloadableFile.RUnlock() var paveFiles []*PaveFile registry := &Registry{} if len(paveServerEntries) != len(config.Schemes) { return nil, common.ContextError(errors.New("invalid paveServerEntries")) } for schemeIndex, scheme := range config.Schemes { slokTimePeriodsPerOSL := 1 for _, keySplit := range scheme.SeedPeriodKeySplits { slokTimePeriodsPerOSL *= keySplit.Total } if common.Contains(scheme.PropagationChannelIDs, propagationChannelID) { oslTime := scheme.epoch for !oslTime.After(endTime) { firstSLOKTime := oslTime fileKey, fileSpec, err := makeOSLFileSpec( scheme, propagationChannelID, firstSLOKTime) if err != nil { return nil, common.ContextError(err) } registry.FileSpecs = append(registry.FileSpecs, fileSpec) serverEntries, ok := paveServerEntries[schemeIndex][oslTime] if ok { signedServerEntries, err := common.WriteAuthenticatedDataPackage( serverEntries, signingPublicKey, signingPrivateKey) if err != nil { return nil, common.ContextError(err) } boxedServerEntries, err := box(fileKey, compress(signedServerEntries)) if err != nil { return nil, common.ContextError(err) } md5sum := md5.Sum(boxedServerEntries) fileSpec.MD5Sum = md5sum[:] fileName := fmt.Sprintf( OSL_FILENAME_FORMAT, hex.EncodeToString(fileSpec.ID)) paveFiles = append(paveFiles, &PaveFile{ Name: fileName, Contents: boxedServerEntries, }) } oslTime = oslTime.Add( time.Duration( int64(slokTimePeriodsPerOSL) * scheme.SeedPeriodNanoseconds)) } } } registryJSON, err := json.Marshal(registry) if err != nil { return nil, common.ContextError(err) } signedRegistry, err := common.WriteAuthenticatedDataPackage( base64.StdEncoding.EncodeToString(registryJSON), signingPublicKey, signingPrivateKey) if err != nil { return nil, common.ContextError(err) } paveFiles = append(paveFiles, &PaveFile{ Name: REGISTRY_FILENAME, Contents: compress(signedRegistry), }) return paveFiles, nil }
// GenerateConfig creates a new Psiphon server config. It returns JSON // encoded configs and a client-compatible "server entry" for the server. It // generates all necessary secrets and key material, which are emitted in // the config file and server entry as necessary. // GenerateConfig uses sample values for many fields. The intention is for // generated configs to be used for testing or as a template for production // setup, not to generate production-ready configurations. func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, []byte, error) { // Input validation if net.ParseIP(params.ServerIPAddress) == nil { return nil, nil, nil, common.ContextError(errors.New("invalid IP address")) } if len(params.TunnelProtocolPorts) == 0 { return nil, nil, nil, common.ContextError(errors.New("no tunnel protocols")) } usedPort := make(map[int]bool) if params.WebServerPort != 0 { usedPort[params.WebServerPort] = true } usingMeek := false for protocol, port := range params.TunnelProtocolPorts { if !common.Contains(common.SupportedTunnelProtocols, protocol) { return nil, nil, nil, common.ContextError(errors.New("invalid tunnel protocol")) } if usedPort[port] { return nil, nil, nil, common.ContextError(errors.New("duplicate listening port")) } usedPort[port] = true if common.TunnelProtocolUsesMeekHTTP(protocol) || common.TunnelProtocolUsesMeekHTTPS(protocol) { usingMeek = true } } // Web server config var webServerSecret, webServerCertificate, webServerPrivateKey, webServerPortForwardAddress string if params.WebServerPort != 0 { var err error webServerSecret, err = common.MakeRandomStringHex(WEB_SERVER_SECRET_BYTE_LENGTH) if err != nil { return nil, nil, nil, common.ContextError(err) } webServerCertificate, webServerPrivateKey, err = GenerateWebServerCertificate("") if err != nil { return nil, nil, nil, common.ContextError(err) } webServerPortForwardAddress = net.JoinHostPort( params.ServerIPAddress, strconv.Itoa(params.WebServerPort)) } // SSH config // TODO: use other key types: anti-fingerprint by varying params rsaKey, err := rsa.GenerateKey(rand.Reader, SSH_RSA_HOST_KEY_BITS) if err != nil { return nil, nil, nil, common.ContextError(err) } sshPrivateKey := pem.EncodeToMemory( &pem.Block{ Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaKey), }, ) signer, err := ssh.NewSignerFromKey(rsaKey) if err != nil { return nil, nil, nil, common.ContextError(err) } sshPublicKey := signer.PublicKey() sshUserNameSuffix, err := common.MakeRandomStringHex(SSH_USERNAME_SUFFIX_BYTE_LENGTH) if err != nil { return nil, nil, nil, common.ContextError(err) } sshUserName := "******" + sshUserNameSuffix sshPassword, err := common.MakeRandomStringHex(SSH_PASSWORD_BYTE_LENGTH) if err != nil { return nil, nil, nil, common.ContextError(err) } // TODO: vary version string for anti-fingerprint sshServerVersion := "SSH-2.0-Psiphon" // Obfuscated SSH config obfuscatedSSHKey, err := common.MakeRandomStringHex(SSH_OBFUSCATED_KEY_BYTE_LENGTH) if err != nil { return nil, nil, nil, common.ContextError(err) } // Meek config var meekCookieEncryptionPublicKey, meekCookieEncryptionPrivateKey, meekObfuscatedKey string if usingMeek { rawMeekCookieEncryptionPublicKey, rawMeekCookieEncryptionPrivateKey, err := box.GenerateKey(rand.Reader) if err != nil { return nil, nil, nil, common.ContextError(err) } meekCookieEncryptionPublicKey = base64.StdEncoding.EncodeToString(rawMeekCookieEncryptionPublicKey[:]) meekCookieEncryptionPrivateKey = base64.StdEncoding.EncodeToString(rawMeekCookieEncryptionPrivateKey[:]) meekObfuscatedKey, err = common.MakeRandomStringHex(SSH_OBFUSCATED_KEY_BYTE_LENGTH) if err != nil { return nil, nil, nil, common.ContextError(err) } } // Other config discoveryValueHMACKey, err := common.MakeRandomStringBase64(DISCOVERY_VALUE_KEY_BYTE_LENGTH) if err != nil { return nil, nil, nil, common.ContextError(err) } // Assemble configs and server entry // Note: this config is intended for either testing or as an illustrative // example or template and is not intended for production deployment. config := &Config{ LogLevel: "info", LogFilename: params.LogFilename, GeoIPDatabaseFilenames: nil, HostID: "example-host-id", ServerIPAddress: params.ServerIPAddress, DiscoveryValueHMACKey: discoveryValueHMACKey, WebServerPort: params.WebServerPort, WebServerSecret: webServerSecret, WebServerCertificate: webServerCertificate, WebServerPrivateKey: webServerPrivateKey, WebServerPortForwardAddress: webServerPortForwardAddress, SSHPrivateKey: string(sshPrivateKey), SSHServerVersion: sshServerVersion, SSHUserName: sshUserName, SSHPassword: sshPassword, ObfuscatedSSHKey: obfuscatedSSHKey, TunnelProtocolPorts: params.TunnelProtocolPorts, DNSResolverIPAddress: "8.8.8.8", UDPInterceptUdpgwServerAddress: "127.0.0.1:7300", MeekCookieEncryptionPrivateKey: meekCookieEncryptionPrivateKey, MeekObfuscatedKey: meekObfuscatedKey, MeekCertificateCommonName: "www.example.org", MeekProhibitedHeaders: nil, MeekProxyForwardedForHeaders: []string{"X-Forwarded-For"}, LoadMonitorPeriodSeconds: 300, TrafficRulesFilename: params.TrafficRulesFilename, } encodedConfig, err := json.MarshalIndent(config, "\n", " ") if err != nil { return nil, nil, nil, common.ContextError(err) } intPtr := func(i int) *int { return &i } trafficRulesSet := &TrafficRulesSet{ DefaultRules: TrafficRules{ RateLimits: RateLimits{ ReadUnthrottledBytes: new(int64), ReadBytesPerSecond: new(int64), WriteUnthrottledBytes: new(int64), WriteBytesPerSecond: new(int64), }, IdleTCPPortForwardTimeoutMilliseconds: intPtr(DEFAULT_IDLE_TCP_PORT_FORWARD_TIMEOUT_MILLISECONDS), IdleUDPPortForwardTimeoutMilliseconds: intPtr(DEFAULT_IDLE_UDP_PORT_FORWARD_TIMEOUT_MILLISECONDS), MaxTCPPortForwardCount: intPtr(DEFAULT_MAX_TCP_PORT_FORWARD_COUNT), MaxUDPPortForwardCount: intPtr(DEFAULT_MAX_UDP_PORT_FORWARD_COUNT), AllowTCPPorts: nil, AllowUDPPorts: nil, }, } encodedTrafficRulesSet, err := json.MarshalIndent(trafficRulesSet, "\n", " ") if err != nil { return nil, nil, nil, common.ContextError(err) } capabilities := []string{} if params.EnableSSHAPIRequests { capabilities = append(capabilities, common.CAPABILITY_SSH_API_REQUESTS) } if params.WebServerPort != 0 { capabilities = append(capabilities, common.CAPABILITY_UNTUNNELED_WEB_API_REQUESTS) } for protocol, _ := range params.TunnelProtocolPorts { capabilities = append(capabilities, psiphon.GetCapability(protocol)) } sshPort := params.TunnelProtocolPorts["SSH"] obfuscatedSSHPort := params.TunnelProtocolPorts["OSSH"] // Meek port limitations // - fronted meek protocols are hard-wired in the client to be port 443 or 80. // - only one other meek port may be specified. meekPort := params.TunnelProtocolPorts["UNFRONTED-MEEK-OSSH"] if meekPort == 0 { meekPort = params.TunnelProtocolPorts["UNFRONTED-MEEK-HTTPS-OSSH"] } // Note: fronting params are a stub; this server entry will exercise // client and server fronting code paths, but not actually traverse // a fronting hop. serverEntryWebServerPort := "" strippedWebServerCertificate := "" if params.WebServerPort != 0 { serverEntryWebServerPort = fmt.Sprintf("%d", params.WebServerPort) // Server entry format omits the BEGIN/END lines and newlines lines := strings.Split(webServerCertificate, "\n") strippedWebServerCertificate = strings.Join(lines[1:len(lines)-2], "") } serverEntry := &psiphon.ServerEntry{ IpAddress: params.ServerIPAddress, WebServerPort: serverEntryWebServerPort, WebServerSecret: webServerSecret, WebServerCertificate: strippedWebServerCertificate, SshPort: sshPort, SshUsername: sshUserName, SshPassword: sshPassword, SshHostKey: base64.RawStdEncoding.EncodeToString(sshPublicKey.Marshal()), SshObfuscatedPort: obfuscatedSSHPort, SshObfuscatedKey: obfuscatedSSHKey, Capabilities: capabilities, Region: "US", MeekServerPort: meekPort, MeekCookieEncryptionPublicKey: meekCookieEncryptionPublicKey, MeekObfuscatedKey: meekObfuscatedKey, MeekFrontingHosts: []string{params.ServerIPAddress}, MeekFrontingAddresses: []string{params.ServerIPAddress}, MeekFrontingDisableSNI: false, } encodedServerEntry, err := psiphon.EncodeServerEntry(serverEntry) if err != nil { return nil, nil, nil, common.ContextError(err) } return encodedConfig, encodedTrafficRulesSet, []byte(encodedServerEntry), nil }
// LoadConfig loads and validates a JSON encoded server config. func LoadConfig(configJSON []byte) (*Config, error) { var config Config err := json.Unmarshal(configJSON, &config) if err != nil { return nil, common.ContextError(err) } if config.ServerIPAddress == "" { return nil, errors.New("ServerIPAddress is required") } if config.WebServerPort > 0 && (config.WebServerSecret == "" || config.WebServerCertificate == "" || config.WebServerPrivateKey == "") { return nil, errors.New( "Web server requires WebServerSecret, WebServerCertificate, WebServerPrivateKey") } if config.WebServerPortForwardAddress != "" { if err := validateNetworkAddress(config.WebServerPortForwardAddress, false); err != nil { return nil, errors.New("WebServerPortForwardAddress is invalid") } } if config.WebServerPortForwardRedirectAddress != "" { if config.WebServerPortForwardAddress == "" { return nil, errors.New( "WebServerPortForwardRedirectAddress requires WebServerPortForwardAddress") } if err := validateNetworkAddress(config.WebServerPortForwardRedirectAddress, false); err != nil { return nil, errors.New("WebServerPortForwardRedirectAddress is invalid") } } for tunnelProtocol, _ := range config.TunnelProtocolPorts { if !common.Contains(common.SupportedTunnelProtocols, tunnelProtocol) { return nil, fmt.Errorf("Unsupported tunnel protocol: %s", tunnelProtocol) } if common.TunnelProtocolUsesSSH(tunnelProtocol) || common.TunnelProtocolUsesObfuscatedSSH(tunnelProtocol) { if config.SSHPrivateKey == "" || config.SSHServerVersion == "" || config.SSHUserName == "" || config.SSHPassword == "" { return nil, fmt.Errorf( "Tunnel protocol %s requires SSHPrivateKey, SSHServerVersion, SSHUserName, SSHPassword", tunnelProtocol) } } if common.TunnelProtocolUsesObfuscatedSSH(tunnelProtocol) { if config.ObfuscatedSSHKey == "" { return nil, fmt.Errorf( "Tunnel protocol %s requires ObfuscatedSSHKey", tunnelProtocol) } } if common.TunnelProtocolUsesMeekHTTP(tunnelProtocol) || common.TunnelProtocolUsesMeekHTTPS(tunnelProtocol) { if config.MeekCookieEncryptionPrivateKey == "" || config.MeekObfuscatedKey == "" { return nil, fmt.Errorf( "Tunnel protocol %s requires MeekCookieEncryptionPrivateKey, MeekObfuscatedKey", tunnelProtocol) } } if common.TunnelProtocolUsesMeekHTTPS(tunnelProtocol) { if config.MeekCertificateCommonName == "" { return nil, fmt.Errorf( "Tunnel protocol %s requires MeekCertificateCommonName", tunnelProtocol) } } } if config.UDPInterceptUdpgwServerAddress != "" { if err := validateNetworkAddress(config.UDPInterceptUdpgwServerAddress, true); err != nil { return nil, fmt.Errorf("UDPInterceptUdpgwServerAddress is invalid: %s", err) } } if config.DNSResolverIPAddress != "" { if net.ParseIP(config.DNSResolverIPAddress) == nil { return nil, fmt.Errorf("DNSResolverIPAddress is invalid") } } return &config, nil }
// 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.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 config.HostNameTransformer != nil { return nil, common.ContextError( errors.New("HostNameTransformer interface must be set at runtime")) } 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.UpgradeDownloadClientVersionHeader == "" || config.UpgradeDownloadFilename == "") { return nil, common.ContextError(errors.New( "UpgradeDownloadUrl requires UpgradeDownloadClientVersionHeader and UpgradeDownloadFilename")) } if !config.DisableRemoteServerListFetcher { if config.RemoteServerListUrl != "" { 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 != "" { if config.RemoteServerListSignaturePublicKey == "" { return nil, common.ContextError(errors.New("missing RemoteServerListSignaturePublicKey")) } if config.ObfuscatedServerListDownloadDirectory == "" { return nil, common.ContextError(errors.New("missing ObfuscatedServerListDownloadDirectory")) } } } 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.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 } return &config, nil }
func isRelayProtocol(_ *SupportServices, value string) bool { return common.Contains(common.SupportedTunnelProtocols, value) }
func serverEntrySupportsProtocol(serverEntry *protocol.ServerEntry, protocol string) bool { // Note: for meek, the capabilities are FRONTED-MEEK and UNFRONTED-MEEK // and the additonal OSSH service is assumed to be available internally. requiredCapability := strings.TrimSuffix(protocol, "-OSSH") return common.Contains(serverEntry.Capabilities, requiredCapability) }
func isServerEntrySource(_ *SupportServices, value string) bool { return common.Contains(common.SupportedServerEntrySources, value) }
// SupportsProtocol returns true if and only if the ServerEntry has // the necessary capability to support the specified tunnel protocol. func (serverEntry *ServerEntry) SupportsProtocol(protocol string) bool { requiredCapability := GetCapability(protocol) return common.Contains(serverEntry.Capabilities, requiredCapability) }
// GetTrafficRules determines the traffic rules for a client based on its attributes. // For the return value TrafficRules, all pointer and slice fields are initialized, // so nil checks are not required. The caller must not modify the returned TrafficRules. func (set *TrafficRulesSet) GetTrafficRules( tunnelProtocol string, geoIPData GeoIPData, state handshakeState) TrafficRules { set.ReloadableFile.RLock() defer set.ReloadableFile.RUnlock() // Start with a copy of the DefaultRules, and then select the first // matches Rules from FilteredTrafficRules, taking only the explicitly // specified fields from that Rules. // // Notes: // - Scalar pointers are used in TrafficRules and RateLimits to distinguish between // omitted fields (in serialized JSON) and default values. For example, if a filtered // Rules specifies a field value of 0, this will override the default; but if the // serialized filtered rule omits the field, the default is to be retained. // - We use shallow copies and slices and scalar pointers are shared between the // return value TrafficRules, so callers must treat the return value as immutable. // This also means that these slices and pointers can remain referenced in memory even // after a hot reload. trafficRules := set.DefaultRules // Populate defaults for omitted DefaultRules fields if trafficRules.RateLimits.ReadUnthrottledBytes == nil { trafficRules.RateLimits.ReadUnthrottledBytes = new(int64) } if trafficRules.RateLimits.ReadBytesPerSecond == nil { trafficRules.RateLimits.ReadBytesPerSecond = new(int64) } if trafficRules.RateLimits.WriteUnthrottledBytes == nil { trafficRules.RateLimits.WriteUnthrottledBytes = new(int64) } if trafficRules.RateLimits.WriteBytesPerSecond == nil { trafficRules.RateLimits.WriteBytesPerSecond = new(int64) } if trafficRules.RateLimits.CloseAfterExhausted == nil { trafficRules.RateLimits.CloseAfterExhausted = new(bool) } intPtr := func(i int) *int { return &i } if trafficRules.IdleTCPPortForwardTimeoutMilliseconds == nil { trafficRules.IdleTCPPortForwardTimeoutMilliseconds = intPtr(DEFAULT_IDLE_TCP_PORT_FORWARD_TIMEOUT_MILLISECONDS) } if trafficRules.IdleUDPPortForwardTimeoutMilliseconds == nil { trafficRules.IdleUDPPortForwardTimeoutMilliseconds = intPtr(DEFAULT_IDLE_UDP_PORT_FORWARD_TIMEOUT_MILLISECONDS) } if trafficRules.MaxTCPPortForwardCount == nil { trafficRules.MaxTCPPortForwardCount = intPtr(DEFAULT_MAX_TCP_PORT_FORWARD_COUNT) } if trafficRules.MaxUDPPortForwardCount == nil { trafficRules.MaxUDPPortForwardCount = intPtr(DEFAULT_MAX_UDP_PORT_FORWARD_COUNT) } if trafficRules.AllowTCPPorts == nil { trafficRules.AllowTCPPorts = make([]int, 0) } if trafficRules.AllowUDPPorts == nil { trafficRules.AllowUDPPorts = make([]int, 0) } // TODO: faster lookup? for _, filteredRules := range set.FilteredRules { log.WithContextFields(LogFields{"filter": filteredRules.Filter}).Debug("filter check") if len(filteredRules.Filter.TunnelProtocols) > 0 { if !common.Contains(filteredRules.Filter.TunnelProtocols, tunnelProtocol) { continue } } if len(filteredRules.Filter.Regions) > 0 { if !common.Contains(filteredRules.Filter.Regions, geoIPData.Country) { continue } } if filteredRules.Filter.APIProtocol != "" { if !state.completed { continue } if state.apiProtocol != filteredRules.Filter.APIProtocol { continue } } if filteredRules.Filter.HandshakeParameters != nil { if !state.completed { continue } mismatch := false for name, values := range filteredRules.Filter.HandshakeParameters { clientValue, err := getStringRequestParam(state.apiParams, name) if err != nil || !common.Contains(values, clientValue) { mismatch = true break } } if mismatch { continue } } log.WithContextFields(LogFields{"filter": filteredRules.Filter}).Debug("filter match") // This is the first match. Override defaults using provided fields from selected rules, and return result. if filteredRules.Rules.RateLimits.ReadUnthrottledBytes != nil { trafficRules.RateLimits.ReadUnthrottledBytes = filteredRules.Rules.RateLimits.ReadUnthrottledBytes } if filteredRules.Rules.RateLimits.ReadBytesPerSecond != nil { trafficRules.RateLimits.ReadBytesPerSecond = filteredRules.Rules.RateLimits.ReadBytesPerSecond } if filteredRules.Rules.RateLimits.WriteUnthrottledBytes != nil { trafficRules.RateLimits.WriteUnthrottledBytes = filteredRules.Rules.RateLimits.WriteUnthrottledBytes } if filteredRules.Rules.RateLimits.WriteBytesPerSecond != nil { trafficRules.RateLimits.WriteBytesPerSecond = filteredRules.Rules.RateLimits.WriteBytesPerSecond } if filteredRules.Rules.RateLimits.CloseAfterExhausted != nil { trafficRules.RateLimits.CloseAfterExhausted = filteredRules.Rules.RateLimits.CloseAfterExhausted } if filteredRules.Rules.IdleTCPPortForwardTimeoutMilliseconds != nil { trafficRules.IdleTCPPortForwardTimeoutMilliseconds = filteredRules.Rules.IdleTCPPortForwardTimeoutMilliseconds } if filteredRules.Rules.IdleUDPPortForwardTimeoutMilliseconds != nil { trafficRules.IdleUDPPortForwardTimeoutMilliseconds = filteredRules.Rules.IdleUDPPortForwardTimeoutMilliseconds } if filteredRules.Rules.MaxTCPPortForwardCount != nil { trafficRules.MaxTCPPortForwardCount = filteredRules.Rules.MaxTCPPortForwardCount } if filteredRules.Rules.MaxUDPPortForwardCount != nil { trafficRules.MaxUDPPortForwardCount = filteredRules.Rules.MaxUDPPortForwardCount } if filteredRules.Rules.AllowTCPPorts != nil { trafficRules.AllowTCPPorts = filteredRules.Rules.AllowTCPPorts } if filteredRules.Rules.AllowUDPPorts != nil { trafficRules.AllowUDPPorts = filteredRules.Rules.AllowUDPPorts } break } log.WithContextFields(LogFields{"trafficRules": trafficRules}).Debug("selected traffic rules") return trafficRules }