tshttpproxy.go 5.8 KB

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