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

Add DNS cache extension mechanism

Rod Hynes 3 лет назад
Родитель
Сommit
f8d81d55e0

+ 4 - 0
psiphon/common/parameters/parameters.go

@@ -314,6 +314,8 @@ const (
 	DNSResolverProtocolTransformScopedSpecNames      = "DNSResolverProtocolTransformScopedSpecNames"
 	DNSResolverProtocolTransformScopedSpecNames      = "DNSResolverProtocolTransformScopedSpecNames"
 	DNSResolverProtocolTransformProbability          = "DNSResolverProtocolTransformProbability"
 	DNSResolverProtocolTransformProbability          = "DNSResolverProtocolTransformProbability"
 	DNSResolverIncludeEDNS0Probability               = "DNSResolverIncludeEDNS0Probability"
 	DNSResolverIncludeEDNS0Probability               = "DNSResolverIncludeEDNS0Probability"
+	DNSResolverCacheExtensionInitialTTL              = "DNSResolverCacheExtensionInitialTTL"
+	DNSResolverCacheExtensionVerifiedTTL             = "DNSResolverCacheExtensionVerifiedTTL"
 )
 )
 
 
 const (
 const (
@@ -663,6 +665,8 @@ var defaultParameters = map[string]struct {
 	DNSResolverProtocolTransformScopedSpecNames: {value: transforms.ScopedSpecNames{}},
 	DNSResolverProtocolTransformScopedSpecNames: {value: transforms.ScopedSpecNames{}},
 	DNSResolverProtocolTransformProbability:     {value: 0.0, minimum: 0.0},
 	DNSResolverProtocolTransformProbability:     {value: 0.0, minimum: 0.0},
 	DNSResolverIncludeEDNS0Probability:          {value: 0.0, minimum: 0.0},
 	DNSResolverIncludeEDNS0Probability:          {value: 0.0, minimum: 0.0},
+	DNSResolverCacheExtensionInitialTTL:         {value: time.Duration(0), minimum: time.Duration(0)},
+	DNSResolverCacheExtensionVerifiedTTL:        {value: time.Duration(0), minimum: time.Duration(0)},
 }
 }
 
 
 // IsServerSideOnly indicates if the parameter specified by name is used
 // IsServerSideOnly indicates if the parameter specified by name is used

+ 97 - 11
psiphon/common/resolver/resolver.go

@@ -86,6 +86,43 @@ type NetworkConfig struct {
 
 
 	// LogHostnames indicates whether to log hostname in errors or not.
 	// LogHostnames indicates whether to log hostname in errors or not.
 	LogHostnames bool
 	LogHostnames bool
+
+	// CacheExtensionInitialTTL specifies a minimum TTL to use when caching
+	// domain resolution results. This minimum will override any TTL in the
+	// DNS response. CacheExtensionInitialTTL is off when 0.
+	CacheExtensionInitialTTL time.Duration
+
+	// CacheExtensionVerifiedTTL specifies the minimum TTL to set for a cached
+	// domain resolution result after the result has been verified.
+	// CacheExtensionVerifiedTTL is off when 0.
+	//
+	// DNS cache extension is a workaround to partially mitigate issues with
+	// obtaining underlying system DNS resolvers on platforms such as iOS
+	// once a VPN is running and after network changes, such as changing from
+	// Wi-Fi to mobile. While ResolveParameters.AlternateDNSServer can be
+	// used to specify a known public DNS server, it may be the case that
+	// public DNS servers are blocked or always falling back to a public DNS
+	// server creates unusual traffic.
+	//
+	// Extending the TTL for cached responses allows Psiphon to redial domains
+	// using recently successful IPs.
+	//
+	// CacheExtensionInitialTTL allows for a greater initial minimum TTL, so
+	// that the response entry remains in the cache long enough for a dial to
+	// fully complete and verify the endpoint. Psiphon will call
+	// Resolver.VerifyExtendCacheTTL once a dial has authenticated, for
+	// example, the destination Psiphon server. VerifyCacheExtension will
+	// further extend the corresponding TTL to CacheExtensionVerifiedTTL, a
+	// longer TTL. CacheExtensionInitialTTL is intended to be on the order of
+	// minutes and CacheExtensionVerifiedTTL may be on the order of hours.
+	//
+	// When CacheExtensionVerifiedTTL is on, the DNS cache is not flushed on
+	// network changes, to allow for the previously cached entries to remain
+	// available in the problematic scenario. Like adjusting TTLs, this is an
+	// explicit trade-off which doesn't adhere to standard best practise, but
+	// is expected to be more blocking resistent; this approach also assumes
+	// that endpoints such as CDN IPs are typically available on any network.
+	CacheExtensionVerifiedTTL time.Duration
 }
 }
 
 
 func (c *NetworkConfig) logWarning(err error) {
 func (c *NetworkConfig) logWarning(err error) {
@@ -205,15 +242,16 @@ type Resolver struct {
 }
 }
 
 
 type resolverMetrics struct {
 type resolverMetrics struct {
-	resolves      int
-	cacheHits     int
-	requestsIPv4  int
-	requestsIPv6  int
-	responsesIPv4 int
-	responsesIPv6 int
-	peakInFlight  int64
-	minRTT        time.Duration
-	maxRTT        time.Duration
+	resolves                int
+	cacheHits               int
+	verifiedCacheExtensions int
+	requestsIPv4            int
+	requestsIPv6            int
+	responsesIPv4           int
+	responsesIPv6           int
+	peakInFlight            int64
+	minRTT                  time.Duration
+	maxRTT                  time.Duration
 }
 }
 
 
 func newResolverMetrics() resolverMetrics {
 func newResolverMetrics() resolverMetrics {
@@ -827,6 +865,38 @@ func (r *Resolver) ResolveIP(
 	return result.IPs, nil
 	return result.IPs, nil
 }
 }
 
 
+// VerifyCacheExtension extends the TTL for any cached result for the
+// specified hostname to at least NetworkConfig.CacheExtensionVerifiedTTL.
+func (r *Resolver) VerifyCacheExtension(hostname string) {
+	r.mutex.Lock()
+	defer r.mutex.Unlock()
+
+	if r.networkConfig.CacheExtensionVerifiedTTL == 0 {
+		return
+	}
+
+	if net.ParseIP(hostname) != nil {
+		return
+	}
+
+	entry, expires, ok := r.cache.GetWithExpiration(hostname)
+	if !ok {
+		return
+	}
+
+	// Change the TTL only if the entry expires and the existing TTL isn't
+	// longer than the extension.
+	neverExpires := time.Time{}
+	if expires == neverExpires ||
+		expires.After(time.Now().Add(r.networkConfig.CacheExtensionVerifiedTTL)) {
+		return
+	}
+
+	r.cache.Set(hostname, entry, r.networkConfig.CacheExtensionVerifiedTTL)
+
+	r.metrics.verifiedCacheExtensions += 1
+}
+
 // GetMetrics returns a summary of DNS metrics.
 // GetMetrics returns a summary of DNS metrics.
 func (r *Resolver) GetMetrics() string {
 func (r *Resolver) GetMetrics() string {
 	r.mutex.Lock()
 	r.mutex.Lock()
@@ -840,9 +910,15 @@ func (r *Resolver) GetMetrics() string {
 		maxRTT = fmt.Sprintf("%d", r.metrics.maxRTT/time.Millisecond)
 		maxRTT = fmt.Sprintf("%d", r.metrics.maxRTT/time.Millisecond)
 	}
 	}
 
 
-	return fmt.Sprintf("resolves %d | hit %d | req v4/v6 %d/%d | resp %d/%d | peak %d | rtt %s - %s ms.",
+	extend := ""
+	if r.networkConfig.CacheExtensionVerifiedTTL > 0 {
+		extend = fmt.Sprintf("| extend %d ", r.metrics.verifiedCacheExtensions)
+	}
+
+	return fmt.Sprintf("resolves %d | hit %d %s| req v4/v6 %d/%d | resp %d/%d | peak %d | rtt %s - %s ms.",
 		r.metrics.resolves,
 		r.metrics.resolves,
 		r.metrics.cacheHits,
 		r.metrics.cacheHits,
+		extend,
 		r.metrics.requestsIPv4,
 		r.metrics.requestsIPv4,
 		r.metrics.requestsIPv6,
 		r.metrics.requestsIPv6,
 		r.metrics.responsesIPv4,
 		r.metrics.responsesIPv4,
@@ -975,7 +1051,9 @@ func (r *Resolver) updateNetworkState(networkID string) {
 		r.lastServersUpdate = time.Now()
 		r.lastServersUpdate = time.Now()
 	}
 	}
 
 
-	if flushCache {
+	// Skip cache flushes when the extended DNS caching mechanism is enabled.
+	// TODO: retain only verified cache entries?
+	if flushCache && r.networkConfig.CacheExtensionVerifiedTTL == 0 {
 		r.cache.Flush()
 		r.cache.Flush()
 	}
 	}
 
 
@@ -1006,6 +1084,14 @@ func (r *Resolver) setCache(hostname string, IPs []net.IP, TTLs []time.Duration)
 		}
 		}
 	}
 	}
 
 
+	// When NetworkConfig.CacheExtensionInitialTTL configured, ensure the TTL
+	// is no shorter than CacheExtensionInitialTTL.
+	if r.networkConfig.CacheExtensionInitialTTL != 0 &&
+		TTL < r.networkConfig.CacheExtensionInitialTTL {
+
+		TTL = r.networkConfig.CacheExtensionInitialTTL
+	}
+
 	// Limitation: with concurrent ResolveIPs for the same domain, the last
 	// Limitation: with concurrent ResolveIPs for the same domain, the last
 	// setCache call determines the cache value. The results are not merged.
 	// setCache call determines the cache value. The results are not merged.
 
 

+ 53 - 8
psiphon/common/resolver/resolver_test.go

@@ -23,6 +23,7 @@ import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
 	"net"
 	"net"
+	"reflect"
 	"sync/atomic"
 	"sync/atomic"
 	"testing"
 	"testing"
 	"time"
 	"time"
@@ -358,7 +359,7 @@ func runTestResolver() error {
 
 
 	if resolver.metrics.resolves != beforeMetrics.resolves+1 ||
 	if resolver.metrics.resolves != beforeMetrics.resolves+1 ||
 		resolver.metrics.cacheHits != beforeMetrics.cacheHits {
 		resolver.metrics.cacheHits != beforeMetrics.cacheHits {
-		return errors.Tracef("unexpected metrics: %+v", resolver.metrics)
+		return errors.Tracef("unexpected metrics: %+v (%+v)", resolver.metrics, beforeMetrics)
 	}
 	}
 
 
 	// Test: PreferAlternateDNSServer
 	// Test: PreferAlternateDNSServer
@@ -541,6 +542,49 @@ func runTestResolver() error {
 		return errors.Tracef("unexpected metrics: %+v", resolver.metrics)
 		return errors.Tracef("unexpected metrics: %+v", resolver.metrics)
 	}
 	}
 
 
+	// Test: DNS cache extension
+
+	resolver.cache.Flush()
+
+	networkConfig.CacheExtensionInitialTTL = (exampleTTLSeconds * 2) * time.Second
+	networkConfig.CacheExtensionVerifiedTTL = 2 * time.Hour
+
+	now := time.Now()
+
+	IPs, err = resolver.ResolveIP(ctx, networkID, params, exampleDomain)
+	if err != nil {
+		return errors.Trace(err)
+	}
+
+	entry, expiry, ok := resolver.cache.GetWithExpiration(exampleDomain)
+	if !ok ||
+		!reflect.DeepEqual(entry, IPs) ||
+		expiry.Before(now.Add(networkConfig.CacheExtensionInitialTTL)) ||
+		expiry.After(now.Add(networkConfig.CacheExtensionVerifiedTTL)) {
+		return errors.TraceNew("unexpected CacheExtensionInitialTTL state")
+	}
+
+	resolver.VerifyCacheExtension(exampleDomain)
+
+	entry, expiry, ok = resolver.cache.GetWithExpiration(exampleDomain)
+	if !ok ||
+		!reflect.DeepEqual(entry, IPs) ||
+		expiry.Before(now.Add(networkConfig.CacheExtensionVerifiedTTL)) {
+		return errors.TraceNew("unexpected CacheExtensionInitialTTL state")
+	}
+
+	// Set cache flush condition, which should be ignored
+	networkID = "networkID-5"
+
+	resolver.updateNetworkState(networkID)
+
+	entry, expiry, ok = resolver.cache.GetWithExpiration(exampleDomain)
+	if !ok ||
+		!reflect.DeepEqual(entry, IPs) ||
+		expiry.Before(now.Add(networkConfig.CacheExtensionVerifiedTTL)) {
+		return errors.TraceNew("unexpected CacheExtensionInitialTTL state")
+	}
+
 	// Test: cancel context
 	// Test: cancel context
 
 
 	resolver.cache.Flush()
 	resolver.cache.Flush()
@@ -608,11 +652,12 @@ func getPublicDNSServers() []string {
 }
 }
 
 
 const (
 const (
-	exampleDomain   = "example.com"
-	exampleIPv4     = "93.184.216.34"
-	exampleIPv4CIDR = "93.184.216.0/24"
-	exampleIPv6     = "2606:2800:220:1:248:1893:25c8:1946"
-	exampleIPv6CIDR = "2606:2800:220::/48"
+	exampleDomain     = "example.com"
+	exampleIPv4       = "93.184.216.34"
+	exampleIPv4CIDR   = "93.184.216.0/24"
+	exampleIPv6       = "2606:2800:220:1:248:1893:25c8:1946"
+	exampleIPv6CIDR   = "2606:2800:220::/48"
+	exampleTTLSeconds = 60
 )
 )
 
 
 // Set the reserved Z flag
 // Set the reserved Z flag
@@ -687,7 +732,7 @@ func (s *testDNSServer) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
 				Name:   r.Question[0].Name,
 				Name:   r.Question[0].Name,
 				Rrtype: dns.TypeA,
 				Rrtype: dns.TypeA,
 				Class:  dns.ClassINET,
 				Class:  dns.ClassINET,
-				Ttl:    60},
+				Ttl:    exampleTTLSeconds},
 			A: IP,
 			A: IP,
 		}
 		}
 	} else {
 	} else {
@@ -700,7 +745,7 @@ func (s *testDNSServer) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
 				Name:   r.Question[0].Name,
 				Name:   r.Question[0].Name,
 				Rrtype: dns.TypeAAAA,
 				Rrtype: dns.TypeAAAA,
 				Class:  dns.ClassINET,
 				Class:  dns.ClassINET,
-				Ttl:    60},
+				Ttl:    exampleTTLSeconds},
 			AAAA: IP,
 			AAAA: IP,
 		}
 		}
 	}
 	}

+ 33 - 13
psiphon/config.go

@@ -780,17 +780,19 @@ type Config struct {
 
 
 	// DNSResolverAttemptsPerServer and other DNSResolver fields are for
 	// DNSResolverAttemptsPerServer and other DNSResolver fields are for
 	// testing purposes.
 	// testing purposes.
-	DNSResolverAttemptsPerServer                *int
-	DNSResolverRequestTimeoutMilliseconds       *int
-	DNSResolverAwaitTimeoutMilliseconds         *int
-	DNSResolverPreresolvedIPAddressCIDRs        parameters.LabeledCIDRs
-	DNSResolverPreresolvedIPAddressProbability  *float64
-	DNSResolverAlternateServers                 []string
-	DNSResolverPreferAlternateServerProbability *float64
-	DNSResolverProtocolTransformSpecs           transforms.Specs
-	DNSResolverProtocolTransformScopedSpecNames transforms.ScopedSpecNames
-	DNSResolverProtocolTransformProbability     *float64
-	DNSResolverIncludeEDNS0Probability          *float64
+	DNSResolverAttemptsPerServer                     *int
+	DNSResolverRequestTimeoutMilliseconds            *int
+	DNSResolverAwaitTimeoutMilliseconds              *int
+	DNSResolverPreresolvedIPAddressCIDRs             parameters.LabeledCIDRs
+	DNSResolverPreresolvedIPAddressProbability       *float64
+	DNSResolverAlternateServers                      []string
+	DNSResolverPreferAlternateServerProbability      *float64
+	DNSResolverProtocolTransformSpecs                transforms.Specs
+	DNSResolverProtocolTransformScopedSpecNames      transforms.ScopedSpecNames
+	DNSResolverProtocolTransformProbability          *float64
+	DNSResolverIncludeEDNS0Probability               *float64
+	DNSResolverCacheExtensionInitialTTLMilliseconds  *int
+	DNSResolverCacheExtensionVerifiedTTLMilliseconds *int
 
 
 	// params is the active parameters.Parameters with defaults, config values,
 	// params is the active parameters.Parameters with defaults, config values,
 	// and, optionally, tactics applied.
 	// and, optionally, tactics applied.
@@ -1842,12 +1844,20 @@ func (config *Config) makeConfigParameters() map[string]interface{} {
 		applyParameters[parameters.DNSResolverProtocolTransformScopedSpecNames] = config.DNSResolverProtocolTransformScopedSpecNames
 		applyParameters[parameters.DNSResolverProtocolTransformScopedSpecNames] = config.DNSResolverProtocolTransformScopedSpecNames
 	}
 	}
 
 
+	if config.DNSResolverProtocolTransformProbability != nil {
+		applyParameters[parameters.DNSResolverProtocolTransformProbability] = *config.DNSResolverProtocolTransformProbability
+	}
+
 	if config.DNSResolverIncludeEDNS0Probability != nil {
 	if config.DNSResolverIncludeEDNS0Probability != nil {
 		applyParameters[parameters.DNSResolverIncludeEDNS0Probability] = *config.DNSResolverIncludeEDNS0Probability
 		applyParameters[parameters.DNSResolverIncludeEDNS0Probability] = *config.DNSResolverIncludeEDNS0Probability
 	}
 	}
 
 
-	if config.DNSResolverProtocolTransformProbability != nil {
-		applyParameters[parameters.DNSResolverProtocolTransformProbability] = *config.DNSResolverProtocolTransformProbability
+	if config.DNSResolverCacheExtensionInitialTTLMilliseconds != nil {
+		applyParameters[parameters.DNSResolverCacheExtensionInitialTTL] = fmt.Sprintf("%dms", *config.DNSResolverCacheExtensionInitialTTLMilliseconds)
+	}
+
+	if config.DNSResolverCacheExtensionVerifiedTTLMilliseconds != nil {
+		applyParameters[parameters.DNSResolverCacheExtensionVerifiedTTL] = fmt.Sprintf("%dms", *config.DNSResolverCacheExtensionVerifiedTTLMilliseconds)
 	}
 	}
 
 
 	// When adding new config dial parameters that may override tactics, also
 	// When adding new config dial parameters that may override tactics, also
@@ -2248,6 +2258,16 @@ func (config *Config) setDialParametersHash() {
 		binary.Write(hash, binary.LittleEndian, *config.DNSResolverIncludeEDNS0Probability)
 		binary.Write(hash, binary.LittleEndian, *config.DNSResolverIncludeEDNS0Probability)
 	}
 	}
 
 
+	if config.DNSResolverCacheExtensionInitialTTLMilliseconds != nil {
+		hash.Write([]byte("DNSResolverCacheExtensionInitialTTLMilliseconds"))
+		binary.Write(hash, binary.LittleEndian, int64(*config.DNSResolverCacheExtensionInitialTTLMilliseconds))
+	}
+
+	if config.DNSResolverCacheExtensionVerifiedTTLMilliseconds != nil {
+		hash.Write([]byte("DNSResolverCacheExtensionVerifiedTTLMilliseconds"))
+		binary.Write(hash, binary.LittleEndian, int64(*config.DNSResolverCacheExtensionVerifiedTTLMilliseconds))
+	}
+
 	config.dialParametersHash = hash.Sum(nil)
 	config.dialParametersHash = hash.Sum(nil)
 }
 }
 
 

+ 7 - 2
psiphon/net.go

@@ -38,6 +38,7 @@ import (
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/fragmentor"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/fragmentor"
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/resolver"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/resolver"
 	"golang.org/x/net/bpf"
 	"golang.org/x/net/bpf"
 )
 )
@@ -326,9 +327,13 @@ func WaitForNetworkConnectivity(
 // excluded from any VPN routing.
 // excluded from any VPN routing.
 func NewResolver(config *Config, useBindToDevice bool) *resolver.Resolver {
 func NewResolver(config *Config, useBindToDevice bool) *resolver.Resolver {
 
 
+	p := config.GetParameters().Get()
+
 	networkConfig := &resolver.NetworkConfig{
 	networkConfig := &resolver.NetworkConfig{
-		LogWarning:   func(err error) { NoticeWarning("ResolveIP: %v", err) },
-		LogHostnames: config.EmitDiagnosticNetworkParameters,
+		LogWarning:                func(err error) { NoticeWarning("ResolveIP: %v", err) },
+		LogHostnames:              config.EmitDiagnosticNetworkParameters,
+		CacheExtensionInitialTTL:  p.Duration(parameters.DNSResolverCacheExtensionInitialTTL),
+		CacheExtensionVerifiedTTL: p.Duration(parameters.DNSResolverCacheExtensionVerifiedTTL),
 	}
 	}
 
 
 	if config.DNSServerGetter != nil {
 	if config.DNSServerGetter != nil {

+ 17 - 0
psiphon/remoteServerList.go

@@ -23,6 +23,7 @@ import (
 	"context"
 	"context"
 	"encoding/hex"
 	"encoding/hex"
 	"fmt"
 	"fmt"
+	"net/url"
 	"os"
 	"os"
 	"sync/atomic"
 	"sync/atomic"
 	"time"
 	"time"
@@ -489,6 +490,22 @@ func downloadRemoteServerListFile(
 	NoticeRemoteServerListResourceDownloaded(sourceURL)
 	NoticeRemoteServerListResourceDownloaded(sourceURL)
 
 
 	downloadStatRecorder := func(authenticated bool) {
 	downloadStatRecorder := func(authenticated bool) {
+
+		// Invoke DNS cache extension (if enabled in the resolver) now that
+		// the download succeeded and the payload is authenticated. Only
+		// extend when authenticated, as this demonstrates that any domain
+		// name resolved to an endpoint that served a valid Psiphon remote
+		// server list.
+		//
+		// TODO: when !skipVerify, invoke DNS cache extension earlier, in
+		// ResumeDownload, after making the request but before downloading
+		// the response body?
+		resolver := config.GetResolver()
+		url, err := url.Parse(sourceURL)
+		if authenticated && resolver != nil && err == nil {
+			resolver.VerifyCacheExtension(url.Hostname())
+		}
+
 		_ = RecordRemoteServerListStat(
 		_ = RecordRemoteServerListStat(
 			config, tunneled, sourceURL, responseETag, bytes, duration, authenticated)
 			config, tunneled, sourceURL, responseETag, bytes, duration, authenticated)
 	}
 	}

+ 15 - 0
psiphon/tunnel.go

@@ -1162,6 +1162,21 @@ func dialTunnel(
 
 
 	cleanupConn = nil
 	cleanupConn = nil
 
 
+	// Invoke DNS cache extension (if enabled in the resolver) now that the
+	// tunnel is connected and the Psiphon server is authenticated. This
+	// demonstrates that any domain name resolved to an endpoint that is or
+	// is forwarded to the expected Psiphon server.
+	//
+	// Limitation: DNS cache extension is not implemented for Refraction
+	// Networking protocols. iOS VPN, the primary use case for DNS cache
+	// extension, does not enable Refraction Networking.
+	if protocol.TunnelProtocolUsesFrontedMeek(dialParams.TunnelProtocol) {
+		resolver := config.GetResolver()
+		if resolver != nil {
+			resolver.VerifyCacheExtension(dialParams.MeekFrontingDialAddress)
+		}
+	}
+
 	// When configured to do so, hold-off on activating this tunnel. This allows
 	// When configured to do so, hold-off on activating this tunnel. This allows
 	// some extra time for slower but less resource intensive protocols to
 	// some extra time for slower but less resource intensive protocols to
 	// establish tunnels. By holding off post-connect, the client has this
 	// establish tunnels. By holding off post-connect, the client has this

+ 5 - 0
psiphon/upgradeDownload.go

@@ -178,5 +178,10 @@ func DownloadUpgrade(
 
 
 	NoticeClientUpgradeDownloaded(config.GetUpgradeDownloadFilename())
 	NoticeClientUpgradeDownloaded(config.GetUpgradeDownloadFilename())
 
 
+	// Limitation: unlike the remote server list download case, DNS cache
+	// extension is not invoked here since payload authentication is not
+	// currently implemented at this level. iOS VPN, the primary use case for
+	// DNS cache extension, does not use this side-load upgrade mechanism.
+
 	return nil
 	return nil
 }
 }