Browse Source

net/tshttpproxy: add GetProxyForURL negative cache

Otherwise when PAC server is down, we log, and each log entry is a new
HTTP request (from logtail) and a new GetProxyForURL call, which again
logs, non-stop. This is also nicer to the WinHTTP service.

Then also hook up link change notifications to the cache to reset it
if there's a chance the network might work sooner.
Brad Fitzpatrick 5 years ago
parent
commit
5bcac4eaac

+ 29 - 0
net/tshttpproxy/tshttpproxy.go

@@ -10,14 +10,43 @@ import (
 	"net/http"
 	"net/url"
 	"os"
+	"sync"
+	"time"
 )
 
+// InvalidateCache invalidates the package-level cache for ProxyFromEnvironment.
+//
+// It's intended to be called on network link/routing table changes.
+func InvalidateCache() {
+	mu.Lock()
+	defer mu.Unlock()
+	noProxyUntil = time.Time{}
+}
+
+var (
+	mu           sync.Mutex
+	noProxyUntil time.Time // if non-zero, time at which ProxyFromEnvironment should check again
+)
+
+func setNoProxyUntil(d time.Duration) {
+	mu.Lock()
+	defer mu.Unlock()
+	noProxyUntil = time.Now().Add(d)
+}
+
 // sysProxyFromEnv, if non-nil, specifies a platform-specific ProxyFromEnvironment
 // func to use if http.ProxyFromEnvironment doesn't return a proxy.
 // For example, WPAD PAC files on Windows.
 var sysProxyFromEnv func(*http.Request) (*url.URL, error)
 
 func ProxyFromEnvironment(req *http.Request) (*url.URL, error) {
+	mu.Lock()
+	noProxyTime := noProxyUntil
+	mu.Unlock()
+	if time.Now().Before(noProxyTime) {
+		return nil, nil
+	}
+
 	u, err := http.ProxyFromEnvironment(req)
 	if u != nil && err == nil {
 		return u, nil

+ 9 - 1
net/tshttpproxy/tshttpproxy_windows.go

@@ -71,11 +71,19 @@ func proxyFromWinHTTPOrCache(req *http.Request) (*url.URL, error) {
 		}
 
 		// See https://docs.microsoft.com/en-us/windows/win32/winhttp/error-messages
-		const ERROR_WINHTTP_AUTODETECTION_FAILED = 12180
+		const (
+			ERROR_WINHTTP_AUTODETECTION_FAILED      = 12180
+			ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT = 12167
+		)
 		if err == syscall.Errno(ERROR_WINHTTP_AUTODETECTION_FAILED) {
+			setNoProxyUntil(10 * time.Second)
 			return nil, nil
 		}
 		log.Printf("tshttpproxy: winhttp: GetProxyForURL(%q): %v/%#v", urlStr, err, err)
+		if err == syscall.Errno(ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT) {
+			setNoProxyUntil(10 * time.Second)
+			return nil, nil
+		}
 		return nil, err
 	case <-ctx.Done():
 		cachedProxy.Lock()

+ 1 - 1
wgengine/router/ifconfig_windows.go

@@ -242,7 +242,6 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) error {
 		log.Printf("setPrivateNetwork: adapter %v not found after %d tries, giving up", guid, tries)
 	}()
 
-	routes := []winipcfg.RouteData{}
 	var firstGateway4 *net.IP
 	var firstGateway6 *net.IP
 	addresses := make([]*net.IPNet, len(cfg.LocalAddrs))
@@ -257,6 +256,7 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) error {
 		}
 	}
 
+	var routes []winipcfg.RouteData
 	foundDefault4 := false
 	foundDefault6 := false
 	for _, route := range cfg.Routes {

+ 7 - 1
wgengine/userspace.go

@@ -33,6 +33,7 @@ import (
 	"tailscale.com/ipn/ipnstate"
 	"tailscale.com/net/interfaces"
 	"tailscale.com/net/tsaddr"
+	"tailscale.com/net/tshttpproxy"
 	"tailscale.com/tailcfg"
 	"tailscale.com/types/key"
 	"tailscale.com/types/logger"
@@ -225,7 +226,10 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) {
 	}
 	e.tundev.PreFilterOut = e.handleLocalPackets
 
-	mon, err := monitor.New(logf, func() { e.LinkChange(false) })
+	mon, err := monitor.New(logf, func() {
+		e.LinkChange(false)
+		tshttpproxy.InvalidateCache()
+	})
 	if err != nil {
 		e.tundev.Close()
 		return nil, err
@@ -349,6 +353,8 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) {
 	}
 	// TODO(danderson): we should delete this. It's pointless to apply
 	// a no-op settings here.
+	// TODO(bradfitz): counter-point: it tests the router implementation early
+	// to see if any part of it might fail.
 	if err := e.router.Set(nil); err != nil {
 		e.magicConn.Close()
 		e.wgdev.Close()