tshttpproxy.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package tshttpproxy contains Tailscale additions to httpproxy not available
  4. // in golang.org/x/net/http/httpproxy. Notably, it aims to support Windows better.
  5. package tshttpproxy
  6. import (
  7. "context"
  8. "errors"
  9. "fmt"
  10. "log"
  11. "net"
  12. "net/http"
  13. "net/url"
  14. "os"
  15. "runtime"
  16. "strings"
  17. "sync"
  18. "time"
  19. "golang.org/x/net/http/httpproxy"
  20. "tailscale.com/util/mak"
  21. )
  22. // InvalidateCache invalidates the package-level cache for ProxyFromEnvironment.
  23. //
  24. // It's intended to be called on network link/routing table changes.
  25. func InvalidateCache() {
  26. mu.Lock()
  27. defer mu.Unlock()
  28. noProxyUntil = time.Time{}
  29. }
  30. var (
  31. mu sync.Mutex
  32. noProxyUntil time.Time // if non-zero, time at which ProxyFromEnvironment should check again
  33. config *httpproxy.Config // used to create proxyFunc
  34. proxyFunc func(*url.URL) (*url.URL, error)
  35. )
  36. // SetProxyFunc can be used by clients to set a platform-specific function for proxy resolution.
  37. // If config is set when this function is called, an error will be returned.
  38. // The provided function should return a proxy URL for the given request URL,
  39. // nil if no proxy is enabled for the request URL, or an error if proxy settings cannot be resolved.
  40. func SetProxyFunc(fn func(*url.URL) (*url.URL, error)) error {
  41. mu.Lock()
  42. defer mu.Unlock()
  43. // Allow override only if config is not set
  44. if config != nil {
  45. return errors.New("tshttpproxy: SetProxyFunc can only be called when config is not set")
  46. }
  47. proxyFunc = fn
  48. return nil
  49. }
  50. func getProxyFunc() func(*url.URL) (*url.URL, error) {
  51. // Create config/proxyFunc if it's not created
  52. mu.Lock()
  53. defer mu.Unlock()
  54. if config == nil {
  55. config = httpproxy.FromEnvironment()
  56. }
  57. if proxyFunc == nil {
  58. proxyFunc = config.ProxyFunc()
  59. }
  60. return proxyFunc
  61. }
  62. // setNoProxyUntil stops calls to sysProxyEnv (if any) for the provided duration.
  63. func setNoProxyUntil(d time.Duration) {
  64. mu.Lock()
  65. defer mu.Unlock()
  66. noProxyUntil = time.Now().Add(d)
  67. }
  68. var _ = setNoProxyUntil // quiet staticcheck; Windows uses the above, more might later
  69. // SetSelfProxy configures this package to avoid proxying through any of the
  70. // provided addresses–e.g. if they refer to proxies being run by this process.
  71. func SetSelfProxy(addrs ...string) {
  72. mu.Lock()
  73. defer mu.Unlock()
  74. // Ensure we have a valid config
  75. if config == nil {
  76. config = httpproxy.FromEnvironment()
  77. }
  78. normalizeHostPort := func(s string) string {
  79. host, portStr, err := net.SplitHostPort(s)
  80. if err != nil {
  81. return s
  82. }
  83. // Normalize the localhost IP into "localhost", to avoid IPv4/IPv6 confusion.
  84. if host == "127.0.0.1" || host == "::1" {
  85. return "localhost:" + portStr
  86. }
  87. // On Linux, all 127.0.0.1/8 IPs are also localhost.
  88. if runtime.GOOS == "linux" && strings.HasPrefix(host, "127.0.0.") {
  89. return "localhost:" + portStr
  90. }
  91. return s
  92. }
  93. normHTTP := normalizeHostPort(config.HTTPProxy)
  94. normHTTPS := normalizeHostPort(config.HTTPSProxy)
  95. // If any of our proxy variables point to one of the configured
  96. // addresses, ignore them.
  97. for _, addr := range addrs {
  98. normAddr := normalizeHostPort(addr)
  99. if normHTTP != "" && normHTTP == normAddr {
  100. log.Printf("tshttpproxy: skipping HTTP_PROXY pointing to self: %q", addr)
  101. config.HTTPProxy = ""
  102. normHTTP = ""
  103. }
  104. if normHTTPS != "" && normHTTPS == normAddr {
  105. log.Printf("tshttpproxy: skipping HTTPS_PROXY pointing to self: %q", addr)
  106. config.HTTPSProxy = ""
  107. normHTTPS = ""
  108. }
  109. }
  110. // Invalidate to cause it to get re-created
  111. proxyFunc = nil
  112. }
  113. // sysProxyFromEnv, if non-nil, specifies a platform-specific ProxyFromEnvironment
  114. // func to use if http.ProxyFromEnvironment doesn't return a proxy.
  115. // For example, WPAD PAC files on Windows.
  116. var sysProxyFromEnv func(*http.Request) (*url.URL, error)
  117. // These variables track whether we've printed a log message for a given proxy
  118. // URL; we only print them once to avoid log spam.
  119. var (
  120. logMessageMu sync.Mutex
  121. logMessagePrinted map[string]bool
  122. )
  123. // ProxyFromEnvironment is like the standard library's http.ProxyFromEnvironment
  124. // but additionally does OS-specific proxy lookups if the environment variables
  125. // alone don't specify a proxy.
  126. func ProxyFromEnvironment(req *http.Request) (ret *url.URL, _ error) {
  127. defer func() {
  128. if ret == nil {
  129. return
  130. }
  131. ss := ret.String()
  132. logMessageMu.Lock()
  133. defer logMessageMu.Unlock()
  134. if logMessagePrinted[ss] {
  135. return
  136. }
  137. log.Printf("tshttpproxy: using proxy %q for URL: %q", ss, req.URL.String())
  138. mak.Set(&logMessagePrinted, ss, true)
  139. }()
  140. localProxyFunc := getProxyFunc()
  141. u, err := localProxyFunc(req.URL)
  142. if u != nil && err == nil {
  143. return u, nil
  144. }
  145. mu.Lock()
  146. noProxyTime := noProxyUntil
  147. mu.Unlock()
  148. if time.Now().Before(noProxyTime) {
  149. return nil, nil
  150. }
  151. if sysProxyFromEnv != nil {
  152. u, err := sysProxyFromEnv(req)
  153. if u != nil && err == nil {
  154. return u, nil
  155. }
  156. }
  157. return nil, err
  158. }
  159. var sysAuthHeader func(*url.URL) (string, error)
  160. // GetAuthHeader returns the Authorization header value to send to proxy u.
  161. func GetAuthHeader(u *url.URL) (string, error) {
  162. if fake := os.Getenv("TS_DEBUG_FAKE_PROXY_AUTH"); fake != "" {
  163. return fake, nil
  164. }
  165. if user := u.User.Username(); user != "" {
  166. pass, ok := u.User.Password()
  167. if !ok {
  168. return "", nil
  169. }
  170. req := &http.Request{Header: make(http.Header)}
  171. req.SetBasicAuth(user, pass)
  172. return req.Header.Get("Authorization"), nil
  173. }
  174. if sysAuthHeader != nil {
  175. return sysAuthHeader(u)
  176. }
  177. return "", nil
  178. }
  179. const proxyAuthHeader = "Proxy-Authorization"
  180. // SetTransportGetProxyConnectHeader sets the provided Transport's
  181. // GetProxyConnectHeader field, and adds logging of the received response.
  182. func SetTransportGetProxyConnectHeader(tr *http.Transport) {
  183. tr.GetProxyConnectHeader = func(ctx context.Context, proxyURL *url.URL, target string) (http.Header, error) {
  184. v, err := GetAuthHeader(proxyURL)
  185. if err != nil {
  186. log.Printf("failed to get proxy Auth header for %v; ignoring: %v", proxyURL, err)
  187. return nil, nil
  188. }
  189. if v == "" {
  190. return nil, nil
  191. }
  192. return http.Header{proxyAuthHeader: []string{v}}, nil
  193. }
  194. tr.OnProxyConnectResponse = func(ctx context.Context, proxyURL *url.URL, connectReq *http.Request, res *http.Response) error {
  195. auth := connectReq.Header.Get(proxyAuthHeader)
  196. const truncLen = 20
  197. if len(auth) > truncLen {
  198. auth = fmt.Sprintf("%s...(%d total bytes)", auth[:truncLen], len(auth))
  199. }
  200. log.Printf("tshttpproxy: CONNECT response from %v for target %q (auth %q): %v", proxyURL, connectReq.Host, auth, res.Status)
  201. return nil
  202. }
  203. }