mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-27 10:47:35 +00:00
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.
This commit is contained in:
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()
|
||||||
|
|
||||||
whi, err := winHTTPOpen()
|
ctx, cancel := context.WithTimeout(req.Context(), 5*time.Second)
|
||||||
if err != nil {
|
defer cancel()
|
||||||
// Log but otherwise ignore the error.
|
|
||||||
log.Printf("winhttp: Open: %v", err)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
defer whi.Close()
|
|
||||||
|
|
||||||
t0 := time.Now()
|
type result struct {
|
||||||
v, err := whi.GetProxyForURL(urlStr)
|
proxy *url.URL
|
||||||
td := time.Since(t0)
|
err error
|
||||||
if td >= 250*time.Millisecond {
|
|
||||||
log.Printf("tshttpproxy: winhttp: GetProxyForURL(%q) = len %d, %v (after %v)", urlStr, len(v), err, td.Round(time.Millisecond))
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
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
|
// See https://docs.microsoft.com/en-us/windows/win32/winhttp/error-messages
|
||||||
const ERROR_WINHTTP_AUTODETECTION_FAILED = 12180
|
const ERROR_WINHTTP_AUTODETECTION_FAILED = 12180
|
||||||
if err == syscall.Errno(ERROR_WINHTTP_AUTODETECTION_FAILED) {
|
if err == syscall.Errno(ERROR_WINHTTP_AUTODETECTION_FAILED) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
log.Printf("tshttpproxy: winhttp: GetProxyForURL(%q): %v (%T, %#v)", urlStr, err, err, err)
|
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()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("winhttp: Open: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer whi.Close()
|
||||||
|
|
||||||
|
t0 := time.Now()
|
||||||
|
v, err := whi.GetProxyForURL(urlStr)
|
||||||
|
td := time.Since(t0).Round(time.Millisecond)
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
log.Printf("tshttpproxy: winhttp: context canceled, ignoring GetProxyForURL(%q) after %v", urlStr, td)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if v == "" {
|
||||||
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…
x
Reference in New Issue
Block a user