// remoteServerListFetcher fetches an out-of-band list of server entries // for more tunnel candidates. It fetches when signalled, with retries // on failure. func (controller *Controller) remoteServerListFetcher() { defer controller.runWaitGroup.Done() if controller.config.RemoteServerListUrl == "" { NoticeAlert("remote server list URL is blank") return } if controller.config.RemoteServerListSignaturePublicKey == "" { NoticeAlert("remote server list signature public key blank") return } var lastFetchTime monotime.Time fetcherLoop: for { // Wait for a signal before fetching select { case <-controller.signalFetchRemoteServerList: case <-controller.shutdownBroadcast: break fetcherLoop } // Skip fetch entirely (i.e., send no request at all, even when ETag would save // on response size) when a recent fetch was successful if lastFetchTime != 0 && lastFetchTime.Add(FETCH_REMOTE_SERVER_LIST_STALE_PERIOD).After(monotime.Now()) { continue } retryLoop: for { // Don't attempt to fetch while there is no network connectivity, // to avoid alert notice noise. if !WaitForNetworkConnectivity( controller.config.NetworkConnectivityChecker, controller.shutdownBroadcast) { break fetcherLoop } // Pick any active tunnel and make the next fetch attempt. If there's // no active tunnel, the untunneledDialConfig will be used. tunnel := controller.getNextActiveTunnel() err := FetchRemoteServerList( controller.config, tunnel, controller.untunneledDialConfig) if err == nil { lastFetchTime = monotime.Now() break retryLoop } NoticeAlert("failed to fetch remote server list: %s", err) timeout := time.After( time.Duration(*controller.config.FetchRemoteServerListRetryPeriodSeconds) * time.Second) select { case <-timeout: case <-controller.shutdownBroadcast: break fetcherLoop } } } NoticeInfo("exiting remote server list fetcher") }
// upgradeDownloader makes periodic attemps to complete a client upgrade // download. DownloadUpgrade() is resumable, so each attempt has potential for // getting closer to completion, even in conditions where the download or // tunnel is repeatedly interrupted. // An upgrade download is triggered by either a handshake response indicating // that a new version is available; or after failing to connect, in which case // it's useful to check, out-of-band, for an upgrade with new circumvention // capabilities. // Once the download operation completes successfully, the downloader exits // and is not run again: either there is not a newer version, or the upgrade // has been downloaded and is ready to be applied. // We're assuming that the upgrade will be applied and the entire system // restarted before another upgrade is to be downloaded. // // TODO: refactor upgrade downloader and remote server list fetcher to use // common code (including the resumable download routines). // func (controller *Controller) upgradeDownloader() { defer controller.runWaitGroup.Done() var lastDownloadTime monotime.Time downloadLoop: for { // Wait for a signal before downloading var handshakeVersion string select { case handshakeVersion = <-controller.signalDownloadUpgrade: case <-controller.shutdownBroadcast: break downloadLoop } // Unless handshake is explicitly advertizing a new version, skip // checking entirely when a recent download was successful. if handshakeVersion == "" && lastDownloadTime != 0 && lastDownloadTime.Add(DOWNLOAD_UPGRADE_STALE_PERIOD).After(monotime.Now()) { continue } retryLoop: for { // Don't attempt to download while there is no network connectivity, // to avoid alert notice noise. if !WaitForNetworkConnectivity( controller.config.NetworkConnectivityChecker, controller.shutdownBroadcast) { break downloadLoop } // Pick any active tunnel and make the next download attempt. If there's // no active tunnel, the untunneledDialConfig will be used. tunnel := controller.getNextActiveTunnel() err := DownloadUpgrade( controller.config, handshakeVersion, tunnel, controller.untunneledDialConfig) if err == nil { lastDownloadTime = monotime.Now() break retryLoop } NoticeAlert("failed to download upgrade: %s", err) timeout := time.After( time.Duration(*controller.config.DownloadUpgradeRetryPeriodSeconds) * time.Second) select { case <-timeout: case <-controller.shutdownBroadcast: break downloadLoop } } } NoticeInfo("exiting upgrade downloader") }
// remoteServerListFetcher fetches an out-of-band list of server entries // for more tunnel candidates. It fetches when signalled, with retries // on failure. func (controller *Controller) remoteServerListFetcher( name string, fetcher RemoteServerListFetcher, signal <-chan struct{}, retryPeriod, stalePeriod time.Duration) { defer controller.runWaitGroup.Done() var lastFetchTime monotime.Time fetcherLoop: for { // Wait for a signal before fetching select { case <-signal: case <-controller.shutdownBroadcast: break fetcherLoop } // Skip fetch entirely (i.e., send no request at all, even when ETag would save // on response size) when a recent fetch was successful if lastFetchTime != 0 && lastFetchTime.Add(stalePeriod).After(monotime.Now()) { continue } retryLoop: for { // Don't attempt to fetch while there is no network connectivity, // to avoid alert notice noise. if !WaitForNetworkConnectivity( controller.config.NetworkConnectivityChecker, controller.shutdownBroadcast) { break fetcherLoop } // Pick any active tunnel and make the next fetch attempt. If there's // no active tunnel, the untunneledDialConfig will be used. tunnel := controller.getNextActiveTunnel() err := fetcher( controller.config, tunnel, controller.untunneledDialConfig) if err == nil { lastFetchTime = monotime.Now() break retryLoop } NoticeAlert("failed to fetch %s remote server list: %s", name, err) timeout := time.After(retryPeriod) select { case <-timeout: case <-controller.shutdownBroadcast: break fetcherLoop } } } NoticeInfo("exiting %s remote server list fetcher", name) }