Something went wrong. Try again.
forked from tailscale/tailscale
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238// Copyright (c) Tailscale Inc & contributors// SPDX-License-Identifier: BSD-3-Clause
// Package tshttpproxy contains Tailscale additions to httpproxy not available// in golang.org/x/net/http/httpproxy. Notably, it aims to support Windows better.package tshttpproxy
import ( "context" "errors" "fmt" "log" "net" "net/http" "net/url" "os" "runtime" "strings" "sync" "time"
"golang.org/x/net/http/httpproxy" "tailscale.com/util/mak")
// 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 config *httpproxy.Config // used to create proxyFunc proxyFunc func(*url.URL) (*url.URL, error))
// SetProxyFunc can be used by clients to set a platform-specific function for proxy resolution.// If config is set when this function is called, an error will be returned.// The provided function should return a proxy URL for the given request URL,// nil if no proxy is enabled for the request URL, or an error if proxy settings cannot be resolved.func SetProxyFunc(fn func(*url.URL) (*url.URL, error)) error { mu.Lock() defer mu.Unlock()
// Allow override only if config is not set if config != nil { return errors.New("tshttpproxy: SetProxyFunc can only be called when config is not set") }
proxyFunc = fn return nil}
func getProxyFunc() func(*url.URL) (*url.URL, error) { // Create config/proxyFunc if it's not created mu.Lock() defer mu.Unlock() if config == nil { config = httpproxy.FromEnvironment() } if proxyFunc == nil { proxyFunc = config.ProxyFunc() } return proxyFunc}
// setNoProxyUntil stops calls to sysProxyEnv (if any) for the provided duration.func setNoProxyUntil(d time.Duration) { mu.Lock() defer mu.Unlock() noProxyUntil = time.Now().Add(d)}
var _ = setNoProxyUntil // quiet staticcheck; Windows uses the above, more might later
// SetSelfProxy configures this package to avoid proxying through any of the// provided addresses–e.g. if they refer to proxies being run by this process.func SetSelfProxy(addrs ...string) { mu.Lock() defer mu.Unlock()
// Ensure we have a valid config if config == nil { config = httpproxy.FromEnvironment() }
normalizeHostPort := func(s string) string { host, portStr, err := net.SplitHostPort(s) if err != nil { return s }
// Normalize the localhost IP into "localhost", to avoid IPv4/IPv6 confusion. if host == "127.0.0.1" || host == "::1" { return "localhost:" + portStr }
// On Linux, all 127.0.0.1/8 IPs are also localhost. if runtime.GOOS == "linux" && strings.HasPrefix(host, "127.0.0.") { return "localhost:" + portStr }
return s }
normHTTP := normalizeHostPort(config.HTTPProxy) normHTTPS := normalizeHostPort(config.HTTPSProxy)
// If any of our proxy variables point to one of the configured // addresses, ignore them. for _, addr := range addrs { normAddr := normalizeHostPort(addr) if normHTTP != "" && normHTTP == normAddr { log.Printf("tshttpproxy: skipping HTTP_PROXY pointing to self: %q", addr) config.HTTPProxy = "" normHTTP = "" } if normHTTPS != "" && normHTTPS == normAddr { log.Printf("tshttpproxy: skipping HTTPS_PROXY pointing to self: %q", addr) config.HTTPSProxy = "" normHTTPS = "" } }
// Invalidate to cause it to get re-created proxyFunc = nil}
// 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)
// These variables track whether we've printed a log message for a given proxy// URL; we only print them once to avoid log spam.var ( logMessageMu sync.Mutex logMessagePrinted map[string]bool)
// ProxyFromEnvironment is like the standard library's http.ProxyFromEnvironment// but additionally does OS-specific proxy lookups if the environment variables// alone don't specify a proxy.func ProxyFromEnvironment(req *http.Request) (ret *url.URL, _ error) { defer func() { if ret == nil { return }
ss := ret.String()
logMessageMu.Lock() defer logMessageMu.Unlock() if logMessagePrinted[ss] { return } log.Printf("tshttpproxy: using proxy %q for URL: %q", ss, req.URL.String()) mak.Set(&logMessagePrinted, ss, true) }()
localProxyFunc := getProxyFunc() u, err := localProxyFunc(req.URL) if u != nil && err == nil { return u, nil }
mu.Lock() noProxyTime := noProxyUntil mu.Unlock() if time.Now().Before(noProxyTime) { return nil, nil }
if sysProxyFromEnv != nil { u, err := sysProxyFromEnv(req) if u != nil && err == nil { return u, nil } }
return nil, err}
var sysAuthHeader func(*url.URL) (string, error)
// GetAuthHeader returns the Authorization header value to send to proxy u.func GetAuthHeader(u *url.URL) (string, error) { if fake := os.Getenv("TS_DEBUG_FAKE_PROXY_AUTH"); fake != "" { return fake, nil } if user := u.User.Username(); user != "" { pass, ok := u.User.Password() if !ok { return "", nil }
req := &http.Request{Header: make(http.Header)} req.SetBasicAuth(user, pass) return req.Header.Get("Authorization"), nil } if sysAuthHeader != nil { return sysAuthHeader(u) } return "", nil}
const proxyAuthHeader = "Proxy-Authorization"
// SetTransportGetProxyConnectHeader sets the provided Transport's// GetProxyConnectHeader field, and adds logging of the received response.func SetTransportGetProxyConnectHeader(tr *http.Transport) { tr.GetProxyConnectHeader = func(ctx context.Context, proxyURL *url.URL, target string) (http.Header, error) { v, err := GetAuthHeader(proxyURL) if err != nil { log.Printf("failed to get proxy Auth header for %v; ignoring: %v", proxyURL, err) return nil, nil } if v == "" { return nil, nil } return http.Header{proxyAuthHeader: []string{v}}, nil } tr.OnProxyConnectResponse = func(ctx context.Context, proxyURL *url.URL, connectReq *http.Request, res *http.Response) error { auth := connectReq.Header.Get(proxyAuthHeader) const truncLen = 20 if len(auth) > truncLen { auth = fmt.Sprintf("%s...(%d total bytes)", auth[:truncLen], len(auth)) } log.Printf("tshttpproxy: CONNECT response from %v for target %q (auth %q): %v", proxyURL, connectReq.Host, auth, res.Status) return nil }}