Просмотр исходного кода

Merge pull request #162 from efryntov/master

Override/disable network timeouts via config parameters
Rod Hynes 10 лет назад
Родитель
Сommit
e0888cbf98
6 измененных файлов с 128 добавлено и 30 удалено
  1. 94 8
      psiphon/config.go
  2. 5 3
      psiphon/httpProxy.go
  3. 2 1
      psiphon/remoteServerList.go
  4. 7 4
      psiphon/serverApi.go
  5. 2 2
      psiphon/splitTunnel.go
  6. 18 12
      psiphon/tunnel.go

+ 94 - 8
psiphon/config.go

@@ -35,27 +35,27 @@ const (
 	DATA_STORE_FILENAME                            = "psiphon.boltdb"
 	CONNECTION_WORKER_POOL_SIZE                    = 10
 	TUNNEL_POOL_SIZE                               = 1
-	TUNNEL_CONNECT_TIMEOUT                         = 20 * time.Second
+	TUNNEL_CONNECT_TIMEOUT_SECONDS                 = 20
 	TUNNEL_OPERATE_SHUTDOWN_TIMEOUT                = 1 * time.Second
-	TUNNEL_PORT_FORWARD_DIAL_TIMEOUT               = 10 * 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         = 30 * 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            = 5 * 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                  = 5 * time.Second
 	ESTABLISH_TUNNEL_SERVER_AFFINITY_GRACE_PERIOD  = 1 * time.Second
-	HTTP_PROXY_ORIGIN_SERVER_TIMEOUT               = 15 * time.Second
+	HTTP_PROXY_ORIGIN_SERVER_TIMEOUT_SECONDS       = 15
 	HTTP_PROXY_MAX_IDLE_CONNECTIONS_PER_HOST       = 50
-	FETCH_REMOTE_SERVER_LIST_TIMEOUT               = 30 * time.Second
+	FETCH_REMOTE_SERVER_LIST_TIMEOUT_SECONDS       = 30
 	FETCH_REMOTE_SERVER_LIST_RETRY_PERIOD          = 5 * time.Second
 	FETCH_REMOTE_SERVER_LIST_STALE_PERIOD          = 6 * time.Hour
 	PSIPHON_API_CLIENT_SESSION_ID_LENGTH           = 16
-	PSIPHON_API_SERVER_TIMEOUT                     = 20 * time.Second
+	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
@@ -65,7 +65,7 @@ const (
 	PSIPHON_API_CONNECTED_REQUEST_PERIOD           = 24 * time.Hour
 	PSIPHON_API_CONNECTED_REQUEST_RETRY_PERIOD     = 5 * time.Second
 	PSIPHON_API_TUNNEL_STATS_MAX_COUNT             = 1000
-	FETCH_ROUTES_TIMEOUT                           = 1 * time.Minute
+	FETCH_ROUTES_TIMEOUT_SECONDS                   = 60
 	DOWNLOAD_UPGRADE_TIMEOUT                       = 15 * time.Minute
 	DOWNLOAD_UPGRADE_RETRY_PERIOD                  = 5 * time.Second
 	DOWNLOAD_UPGRADE_STALE_PERIOD                  = 6 * time.Hour
@@ -309,6 +309,52 @@ type Config struct {
 	// 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 default value is TUNNEL_CONNECT_TIMEOUT_SECONDS.
+	TunnelConnectTimeoutSeconds *int
+
+	// TunnelPortForwardTimeoutSeconds specifies a timeout per SSH port forward.
+	// Zero value means a port forward will not time out.
+	// If omitted default value is TUNNEL_PORT_FORWARD_DIAL_TIMEOUT_SECONDS.
+	TunnelPortForwardTimeoutSeconds *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 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 default value is TUNNEL_SSH_KEEP_ALIVE_PERIODIC_TIMEOUT_SECONDS.
+	TunnelSshKeepAlivePeriodicTimeoutSeconds *int
+
+	// FetchRemoteServerListTimeoutSeconds specifies a timeout value for remote server list
+	// HTTP request. Zero value means that request will not time out.
+	// If omitted 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 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 request. Zero value means that request will not time out.
+	// If omitted default value is FETCH_ROUTES_TIMEOUT_SECONDS.
+	FetchRoutesTimeoutSeconds *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 default value  HTTP_PROXY_ORIGIN_SERVER_TIMEOUT_SECONDS.
+	HttpProxyOriginServerTimeoutSeconds *int
 }
 
 // LoadConfig parses and validates a JSON format Psiphon config JSON
@@ -394,5 +440,45 @@ func LoadConfig(configJson []byte) (*Config, error) {
 			"UpgradeDownloadUrl requires UpgradeDownloadClientVersionHeader and UpgradeDownloadFilename"))
 	}
 
+	if config.TunnelConnectTimeoutSeconds == nil {
+		defaultTunnelConnectTimeoutSeconds := TUNNEL_CONNECT_TIMEOUT_SECONDS
+		config.TunnelConnectTimeoutSeconds = &defaultTunnelConnectTimeoutSeconds
+	}
+
+	if config.TunnelPortForwardTimeoutSeconds == nil {
+		defaultTunnelPortForwardTimeoutSeconds := TUNNEL_PORT_FORWARD_DIAL_TIMEOUT_SECONDS
+		config.TunnelPortForwardTimeoutSeconds = &defaultTunnelPortForwardTimeoutSeconds
+	}
+
+	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
+	}
+
 	return &config, nil
 }

+ 5 - 3
psiphon/httpProxy.go

@@ -94,12 +94,13 @@ func NewHttpProxy(
 		return DialTCP(addr, untunneledDialConfig)
 	}
 
+	responseHeaderTimeout := time.Duration(*config.HttpProxyOriginServerTimeoutSeconds) * time.Second
 	// TODO: could HTTP proxy share a tunneled transport with URL proxy?
 	// For now, keeping them distinct just to be conservative.
 	httpProxyTunneledRelay := &http.Transport{
 		Dial:                  tunneledDialer,
 		MaxIdleConnsPerHost:   HTTP_PROXY_MAX_IDLE_CONNECTIONS_PER_HOST,
-		ResponseHeaderTimeout: HTTP_PROXY_ORIGIN_SERVER_TIMEOUT,
+		ResponseHeaderTimeout: responseHeaderTimeout,
 	}
 
 	// Note: URL proxy relays use http.Client for upstream requests, so
@@ -109,12 +110,13 @@ func NewHttpProxy(
 	urlProxyTunneledRelay := &http.Transport{
 		Dial:                  tunneledDialer,
 		MaxIdleConnsPerHost:   HTTP_PROXY_MAX_IDLE_CONNECTIONS_PER_HOST,
-		ResponseHeaderTimeout: HTTP_PROXY_ORIGIN_SERVER_TIMEOUT,
+		ResponseHeaderTimeout: responseHeaderTimeout,
 	}
 	urlProxyTunneledClient := &http.Client{
 		Transport: urlProxyTunneledRelay,
 		Jar:       nil, // TODO: cookie support for URL proxy?
 
+		// Leaving original value in the note below:
 		// Note: don't use this timeout -- it interrupts downloads of large response bodies
 		//Timeout:   HTTP_PROXY_ORIGIN_SERVER_TIMEOUT,
 	}
@@ -122,7 +124,7 @@ func NewHttpProxy(
 	urlProxyDirectRelay := &http.Transport{
 		Dial:                  directDialer,
 		MaxIdleConnsPerHost:   HTTP_PROXY_MAX_IDLE_CONNECTIONS_PER_HOST,
-		ResponseHeaderTimeout: HTTP_PROXY_ORIGIN_SERVER_TIMEOUT,
+		ResponseHeaderTimeout: responseHeaderTimeout,
 	}
 	urlProxyDirectClient := &http.Client{
 		Transport: urlProxyDirectRelay,

+ 2 - 1
psiphon/remoteServerList.go

@@ -24,6 +24,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"net/http"
+	"time"
 )
 
 // FetchRemoteServerList downloads a remote server list JSON record from
@@ -41,7 +42,7 @@ func FetchRemoteServerList(config *Config, dialConfig *DialConfig) (err error) {
 	}
 
 	httpClient, requestUrl, err := MakeUntunneledHttpsClient(
-		dialConfig, nil, config.RemoteServerListUrl, FETCH_REMOTE_SERVER_LIST_TIMEOUT)
+		dialConfig, nil, config.RemoteServerListUrl, time.Duration(*config.FetchRemoteServerListTimeoutSeconds)*time.Second)
 	if err != nil {
 		return ContextError(err)
 	}

+ 7 - 4
psiphon/serverApi.go

@@ -32,6 +32,7 @@ import (
 	"net/http"
 	"strconv"
 	"sync/atomic"
+	"time"
 
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/transferstats"
 )
@@ -424,9 +425,10 @@ func doUntunneledStatusRequest(
 		return ContextError(err)
 	}
 
+	timeout := time.Duration(*tunnel.config.PsiphonApiServerTimeoutSeconds) * time.Second
+
 	dialConfig := tunnel.untunneledDialConfig
 
-	timeout := PSIPHON_API_SERVER_TIMEOUT
 	if isShutdown {
 		timeout = PSIPHON_API_SHUTDOWN_SERVER_TIMEOUT
 
@@ -712,18 +714,19 @@ func makePsiphonHttpsClient(tunnel *Tunnel) (httpsClient *http.Client, err error
 		// TODO: check tunnel.isClosed, and apply TUNNEL_PORT_FORWARD_DIAL_TIMEOUT as in Tunnel.Dial?
 		return tunnel.sshClient.Dial("tcp", addr)
 	}
+	timeout := time.Duration(*tunnel.config.PsiphonApiServerTimeoutSeconds) * time.Second
 	dialer := NewCustomTLSDialer(
 		&CustomTLSConfig{
 			Dial:                    tunneledDialer,
-			Timeout:                 PSIPHON_API_SERVER_TIMEOUT,
+			Timeout:                 timeout,
 			VerifyLegacyCertificate: certificate,
 		})
 	transport := &http.Transport{
 		Dial: dialer,
-		ResponseHeaderTimeout: PSIPHON_API_SERVER_TIMEOUT,
+		ResponseHeaderTimeout: timeout,
 	}
 	return &http.Client{
 		Transport: transport,
-		Timeout:   PSIPHON_API_SERVER_TIMEOUT,
+		Timeout:   timeout,
 	}, nil
 }

+ 2 - 2
psiphon/splitTunnel.go

@@ -235,11 +235,11 @@ func (classifier *SplitTunnelClassifier) getRoutes(tunnel *Tunnel) (routesData [
 	}
 	transport := &http.Transport{
 		Dial: tunneledDialer,
-		ResponseHeaderTimeout: FETCH_ROUTES_TIMEOUT,
+		ResponseHeaderTimeout: time.Duration(*tunnel.config.FetchRoutesTimeoutSeconds) * time.Second,
 	}
 	httpClient := &http.Client{
 		Transport: transport,
-		Timeout:   FETCH_ROUTES_TIMEOUT,
+		Timeout:   time.Duration(*tunnel.config.FetchRoutesTimeoutSeconds) * time.Second,
 	}
 
 	// At this time, the largest uncompressed routes data set is ~1MB. For now,

+ 18 - 12
psiphon/tunnel.go

@@ -218,9 +218,11 @@ func (tunnel *Tunnel) Dial(
 		err                error
 	}
 	resultChannel := make(chan *tunnelDialResult, 2)
-	time.AfterFunc(TUNNEL_PORT_FORWARD_DIAL_TIMEOUT, func() {
-		resultChannel <- &tunnelDialResult{nil, errors.New("tunnel dial timeout")}
-	})
+	if *tunnel.config.TunnelPortForwardTimeoutSeconds > 0 {
+		time.AfterFunc(time.Duration(*tunnel.config.TunnelPortForwardTimeoutSeconds)*time.Second, func() {
+			resultChannel <- &tunnelDialResult{nil, errors.New("tunnel dial timeout")}
+		})
+	}
 	go func() {
 		sshPortForwardConn, err := tunnel.sshClient.Dial("tcp", remoteAddr)
 		resultChannel <- &tunnelDialResult{sshPortForwardConn, err}
@@ -513,7 +515,7 @@ func dialSsh(
 	// Create the base transport: meek or direct connection
 	dialConfig := &DialConfig{
 		UpstreamProxyUrl:              config.UpstreamProxyUrl,
-		ConnectTimeout:                TUNNEL_CONNECT_TIMEOUT,
+		ConnectTimeout:                time.Duration(*config.TunnelConnectTimeoutSeconds) * time.Second,
 		PendingConns:                  pendingConns,
 		DeviceBinder:                  config.DeviceBinder,
 		DnsServerGetter:               config.DnsServerGetter,
@@ -597,9 +599,11 @@ func dialSsh(
 		err       error
 	}
 	resultChannel := make(chan *sshNewClientResult, 2)
-	time.AfterFunc(TUNNEL_CONNECT_TIMEOUT, func() {
-		resultChannel <- &sshNewClientResult{nil, errors.New("ssh dial timeout")}
-	})
+	if *config.TunnelConnectTimeoutSeconds > 0 {
+		time.AfterFunc(time.Duration(*config.TunnelConnectTimeoutSeconds)*time.Second, func() {
+			resultChannel <- &sshNewClientResult{nil, errors.New("ssh dial timeout")}
+		})
+	}
 
 	go func() {
 		// The folowing is adapted from ssh.Dial(), here using a custom conn
@@ -797,7 +801,7 @@ func (tunnel *Tunnel) operateTunnel(tunnelOwner TunnelOwner) {
 		case <-sshKeepAliveTimer.C:
 			if lastBytesReceivedTime.Add(TUNNEL_SSH_KEEP_ALIVE_PERIODIC_INACTIVE_PERIOD).Before(time.Now()) {
 				select {
-				case signalSshKeepAlive <- TUNNEL_SSH_KEEP_ALIVE_PERIODIC_TIMEOUT:
+				case signalSshKeepAlive <- time.Duration(*tunnel.config.TunnelSshKeepAlivePeriodicTimeoutSeconds) * time.Second:
 				default:
 				}
 			}
@@ -811,7 +815,7 @@ func (tunnel *Tunnel) operateTunnel(tunnelOwner TunnelOwner) {
 
 			if lastBytesReceivedTime.Add(TUNNEL_SSH_KEEP_ALIVE_PROBE_INACTIVE_PERIOD).Before(time.Now()) {
 				select {
-				case signalSshKeepAlive <- TUNNEL_SSH_KEEP_ALIVE_PROBE_TIMEOUT:
+				case signalSshKeepAlive <- time.Duration(*tunnel.config.TunnelSshKeepAliveProbeTimeoutSeconds) * time.Second:
 				default:
 				}
 			}
@@ -903,9 +907,11 @@ func sendSshKeepAlive(
 	sshClient *ssh.Client, conn net.Conn, timeout time.Duration) error {
 
 	errChannel := make(chan error, 2)
-	time.AfterFunc(timeout, func() {
-		errChannel <- TimeoutError{}
-	})
+	if timeout > 0 {
+		time.AfterFunc(timeout, func() {
+			errChannel <- TimeoutError{}
+		})
+	}
 
 	go func() {
 		// Random padding to frustrate fingerprinting