diff --git a/lib/settings/backend.go b/lib/settings/backend.go index 8eb1fc6..7f15450 100644 --- a/lib/settings/backend.go +++ b/lib/settings/backend.go @@ -5,6 +5,7 @@ import ( "git.gammaspectra.live/git/go-away/utils" "net/http" "net/http/httputil" + "time" ) type Backend struct { @@ -32,6 +33,44 @@ type Backend struct { // Transparent Do not add extra headers onto this backend // This prevents GoAway headers from being set, or other state Transparent bool `yaml:"transparent"` + + // DialTimeout is the maximum amount of time a dial will wait for + // a connect to complete. + // + // The default is no timeout. + // + // When using TCP and dialing a host name with multiple IP + // addresses, the timeout may be divided between them. + // + // With or without a timeout, the operating system may impose + // its own earlier timeout. For instance, TCP timeouts are + // often around 3 minutes. + DialTimeout time.Duration `yaml:"dial-timeout"` + + // TLSHandshakeTimeout specifies the maximum amount of time to + // wait for a TLS handshake. Zero means no timeout. + TLSHandshakeTimeout time.Duration `yaml:"tls-handshake-timeout"` + + // IdleConnTimeout is the maximum amount of time an idle + // (keep-alive) connection will remain idle before closing + // itself. + // Zero means no limit. + IdleConnTimeout time.Duration `yaml:"idle-conn-timeout"` + + // ResponseHeaderTimeout, if non-zero, specifies the amount of + // time to wait for a server's response headers after fully + // writing the request (including its body, if any). This + // time does not include the time to read the response body. + ResponseHeaderTimeout time.Duration `yaml:"response-header-timeout"` + + // ExpectContinueTimeout, if non-zero, specifies the amount of + // time to wait for a server's first response headers after fully + // writing the request headers if the request has an + // "Expect: 100-continue" header. Zero means no timeout and + // causes the body to be sent immediately, without + // waiting for the server to approve. + // This time does not include the time to send the request header. + ExpectContinueTimeout time.Duration `yaml:"expect-continue-timeout"` } func (b Backend) Create() (*httputil.ReverseProxy, error) { @@ -39,13 +78,19 @@ func (b Backend) Create() (*httputil.ReverseProxy, error) { b.IpHeader = "" } - proxy, err := utils.MakeReverseProxy(b.URL, b.GoDNS) + proxy, err := utils.MakeReverseProxy(b.URL, b.GoDNS, b.DialTimeout) if err != nil { return nil, err } transport := proxy.Transport.(*http.Transport) + // set transport timeouts + transport.TLSHandshakeTimeout = b.TLSHandshakeTimeout + transport.IdleConnTimeout = b.IdleConnTimeout + transport.ResponseHeaderTimeout = b.ResponseHeaderTimeout + transport.ExpectContinueTimeout = b.ExpectContinueTimeout + if b.HTTP2Enabled { transport.ForceAttemptHTTP2 = true } diff --git a/utils/http.go b/utils/http.go index 06f34c8..e55e263 100644 --- a/utils/http.go +++ b/utils/http.go @@ -13,6 +13,7 @@ import ( "net/netip" "net/url" "strings" + "time" ) func NewServer(handler http.Handler, tlsConfig *tls.Config) *http.Server { @@ -69,7 +70,7 @@ func EnsureNoOpenRedirect(redirect string) (string, error) { return uri.String(), nil } -func MakeReverseProxy(target string, goDns bool) (*httputil.ReverseProxy, error) { +func MakeReverseProxy(target string, goDns bool, dialTimeout time.Duration) (*httputil.ReverseProxy, error) { u, err := url.Parse(target) if err != nil { return nil, fmt.Errorf("failed to parse target URL: %w", err) @@ -85,7 +86,9 @@ func MakeReverseProxy(target string, goDns bool) (*httputil.ReverseProxy, error) u.Path = "" // tell transport how to dial unix sockets transport.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) { - dialer := net.Dialer{} + dialer := net.Dialer{ + Timeout: dialTimeout, + } return dialer.DialContext(ctx, "unix", addr) } // tell transport how to handle the unix url scheme @@ -95,6 +98,12 @@ func MakeReverseProxy(target string, goDns bool) (*httputil.ReverseProxy, error) Resolver: &net.Resolver{ PreferGo: true, }, + Timeout: dialTimeout, + } + transport.DialContext = dialer.DialContext + } else { + dialer := &net.Dialer{ + Timeout: dialTimeout, } transport.DialContext = dialer.DialContext }