ipn/ipnlocal: maintain a proxy handler per backend (#6804)

By default, `http.Transport` keeps idle connections open hoping to re-use them in the future. Combined with a separate transport per request in HTTP proxy this results in idle connection leak.

Fixes #6773
This commit is contained in:
Anton Tolchanov
2022-12-21 18:36:58 +00:00
committed by GitHub
parent 1011e64ad7
commit 82b9689e25
2 changed files with 77 additions and 17 deletions
+28 -16
View File
@@ -211,7 +211,6 @@ func (b *LocalBackend) SetServeConfig(config *ipn.ServeConfig) error {
}
b.setTCPPortsInterceptedFromNetmapAndPrefsLocked(b.pm.CurrentPrefs())
return nil
}
@@ -389,6 +388,30 @@ func (b *LocalBackend) getServeHandler(r *http.Request) (_ ipn.HTTPHandlerView,
}
}
// proxyHandlerForBackend creates a new HTTP reverse proxy for a particular backend that
// we serve requests for. `backend` is a HTTPHandler.Proxy string (url, hostport or just port).
func (b *LocalBackend) proxyHandlerForBackend(backend string) (*httputil.ReverseProxy, error) {
targetURL, insecure := expandProxyArg(backend)
u, err := url.Parse(targetURL)
if err != nil {
return nil, fmt.Errorf("invalid url %s: %w", targetURL, err)
}
rp := httputil.NewSingleHostReverseProxy(u)
rp.Transport = &http.Transport{
DialContext: b.dialer.SystemDial,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: insecure,
},
// Values for the following parameters have been copied from http.DefaultTransport.
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
return rp, nil
}
func (b *LocalBackend) serveWebHandler(w http.ResponseWriter, r *http.Request) {
h, mountPoint, ok := b.getServeHandler(r)
if !ok {
@@ -405,23 +428,12 @@ func (b *LocalBackend) serveWebHandler(w http.ResponseWriter, r *http.Request) {
return
}
if v := h.Proxy(); v != "" {
// TODO(bradfitz): this is a lot of setup per HTTP request. We should
// build the whole http.Handler with all the muxing and child handlers
// only on start/config change. But this works for now (2022-11-09).
targetURL, insecure := expandProxyArg(v)
u, err := url.Parse(targetURL)
if err != nil {
http.Error(w, "bad proxy config", http.StatusInternalServerError)
p, ok := b.serveProxyHandlers.Load(v)
if !ok {
http.Error(w, "unknown proxy destination", http.StatusInternalServerError)
return
}
rp := httputil.NewSingleHostReverseProxy(u)
rp.Transport = &http.Transport{
DialContext: b.dialer.SystemDial,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: insecure,
},
}
rp.ServeHTTP(w, r)
p.(http.Handler).ServeHTTP(w, r)
return
}