net/tshttpproxy: if winhttp.GetProxyForURL blocks too long, use previous value

We currently have a chickend-and-egg situation in some environments
where we can set up routes that WinHTTP's WPAD/PAC resolution service
needs to download the PAC file to evaluate GetProxyForURL, but the PAC
file is behind a route for which we need to call GetProxyForURL to
e.g. dial a DERP server.

As a short-term fix, just assume that the most recently returned proxy
is good enough for such situations.
pull/734/head
Brad Fitzpatrick 4 years ago
parent a570c27577
commit b026a638c7

@ -5,12 +5,14 @@
package tshttpproxy package tshttpproxy
import ( import (
"context"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"sync"
"syscall" "syscall"
"time" "time"
"unsafe" "unsafe"
@ -27,53 +29,91 @@ var (
) )
func init() { func init() {
sysProxyFromEnv = proxyFromWinHTTP sysProxyFromEnv = proxyFromWinHTTPOrCache
sysAuthHeader = sysAuthHeaderWindows sysAuthHeader = sysAuthHeaderWindows
} }
func proxyFromWinHTTP(req *http.Request) (*url.URL, error) { var cachedProxy struct {
sync.Mutex
val *url.URL
}
func proxyFromWinHTTPOrCache(req *http.Request) (*url.URL, error) {
if req.URL == nil { if req.URL == nil {
return nil, nil return nil, nil
} }
urlStr := req.URL.String() urlStr := req.URL.String()
ctx, cancel := context.WithTimeout(req.Context(), 5*time.Second)
defer cancel()
type result struct {
proxy *url.URL
err error
}
resc := make(chan result, 1)
go func() {
proxy, err := proxyFromWinHTTP(ctx, urlStr)
resc <- result{proxy, err}
}()
select {
case res := <-resc:
err := res.err
if err == nil {
cachedProxy.Lock()
defer cachedProxy.Unlock()
if was, now := fmt.Sprint(cachedProxy.val), fmt.Sprint(res.proxy); want != now {
log.Printf("tshttpproxy: winhttp: updating cached proxy setting from %v to %v", was, now)
}
cachedProxy.val = res.proxy
return res.proxy, nil
}
// See https://docs.microsoft.com/en-us/windows/win32/winhttp/error-messages
const ERROR_WINHTTP_AUTODETECTION_FAILED = 12180
if err == syscall.Errno(ERROR_WINHTTP_AUTODETECTION_FAILED) {
return nil, nil
}
log.Printf("tshttpproxy: winhttp: GetProxyForURL(%q): %v/%#v", urlStr, err, err)
return nil, err
case <-ctx.Done():
cachedProxy.Lock()
defer cachedProxy.Unlock()
log.Printf("tshttpproxy: winhttp: GetProxyForURL(%q): timeout; using cached proxy %v", urlStr, cachedProxy.val)
return cachedProxy.val, nil
}
}
func proxyFromWinHTTP(ctx context.Context, urlStr string) (proxy *url.URL, err error) {
whi, err := winHTTPOpen() whi, err := winHTTPOpen()
if err != nil { if err != nil {
// Log but otherwise ignore the error.
log.Printf("winhttp: Open: %v", err) log.Printf("winhttp: Open: %v", err)
return nil, nil return nil, err
} }
defer whi.Close() defer whi.Close()
t0 := time.Now() t0 := time.Now()
v, err := whi.GetProxyForURL(urlStr) v, err := whi.GetProxyForURL(urlStr)
td := time.Since(t0) td := time.Since(t0).Round(time.Millisecond)
if td >= 250*time.Millisecond { if err := ctx.Err(); err != nil {
log.Printf("tshttpproxy: winhttp: GetProxyForURL(%q) = len %d, %v (after %v)", urlStr, len(v), err, td.Round(time.Millisecond)) log.Printf("tshttpproxy: winhttp: context canceled, ignoring GetProxyForURL(%q) after %v", urlStr, td)
return nil, err
} }
if err != nil { if err != nil {
// See https://docs.microsoft.com/en-us/windows/win32/winhttp/error-messages return nil, err
const ERROR_WINHTTP_AUTODETECTION_FAILED = 12180 }
if err == syscall.Errno(ERROR_WINHTTP_AUTODETECTION_FAILED) { if v == "" {
return nil, nil
}
log.Printf("tshttpproxy: winhttp: GetProxyForURL(%q): %v (%T, %#v)", urlStr, err, err, err)
return nil, nil return nil, nil
} }
if v != "" { // Discard all but first proxy value for now.
// Discard all but first proxy value for now. if i := strings.Index(v, ";"); i != -1 {
if i := strings.Index(v, ";"); i != -1 { v = v[:i]
v = v[:i]
}
if !strings.HasPrefix(v, "https://") {
v = "http://" + v
}
if u, err := url.Parse(v); err == nil {
return u, nil
}
} }
if !strings.HasPrefix(v, "https://") {
return nil, nil v = "http://" + v
}
return url.Parse(v)
} }
var userAgent = windows.StringToUTF16Ptr("Tailscale") var userAgent = windows.StringToUTF16Ptr("Tailscale")

Loading…
Cancel
Save