Revert LingeringTimeout and change default value for ReadTimeout

Co-authored-by: Romain <rtribotte@users.noreply.github.com>
This commit is contained in:
Kevin Pollet 2024-04-11 15:48:04 +02:00 committed by GitHub
parent d53f5f01a0
commit 099c7e9444
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 125 additions and 389 deletions

View file

@ -578,7 +578,7 @@ the maximum user-defined router priority value is:
- `(MaxInt32 - 1000)` for 32-bit platforms, - `(MaxInt32 - 1000)` for 32-bit platforms,
- `(MaxInt64 - 1000)` for 64-bit platforms. - `(MaxInt64 - 1000)` for 64-bit platforms.
### <EntryPoint>.Transport.RespondingTimeouts.<Timeout> ### EntryPoint.Transport.RespondingTimeouts.<Timeout>
Starting with `v2.11.1` the following timeout options are deprecated: Starting with `v2.11.1` the following timeout options are deprecated:
@ -592,7 +592,7 @@ They have been replaced by:
- `<entryPoint>.transport.respondingTimeouts.http.writeTimeout` - `<entryPoint>.transport.respondingTimeouts.http.writeTimeout`
- `<entryPoint>.transport.respondingTimeouts.http.idleTimeout` - `<entryPoint>.transport.respondingTimeouts.http.idleTimeout`
### <EntryPoint>.Transport.RespondingTimeouts.TCP.LingeringTimeout ### EntryPoint.Transport.RespondingTimeouts.TCP.LingeringTimeout
Starting with `v2.11.1` a new `lingeringTimeout` entryPoints option has been introduced, with a default value of 2s. Starting with `v2.11.1` a new `lingeringTimeout` entryPoints option has been introduced, with a default value of 2s.
@ -609,3 +609,34 @@ Increasing the `lingeringTimeout` value could be the solution notably if you are
- TCP: `Error while handling TCP connection: readfrom tcp X.X.X.X:X->X.X.X.X:X: read tcp X.X.X.X:X->X.X.X.X:X: i/o timeout` - TCP: `Error while handling TCP connection: readfrom tcp X.X.X.X:X->X.X.X.X:X: read tcp X.X.X.X:X->X.X.X.X:X: i/o timeout`
- HTTP: `'499 Client Closed Request' caused by: context canceled` - HTTP: `'499 Client Closed Request' caused by: context canceled`
- HTTP: `ReverseProxy read error during body copy: read tcp X.X.X.X:X->X.X.X.X:X: use of closed network connection` - HTTP: `ReverseProxy read error during body copy: read tcp X.X.X.X:X->X.X.X.X:X: use of closed network connection`
## v2.11.2
### LingeringTimeout
Starting with `v2.11.2` the `<entrypoint>.transport.respondingTimeouts.tcp.lingeringTimeout` introduced in `v2.11.1` has been removed.
### RespondingTimeouts.TCP and RespondingTimeouts.HTTP
Starting with `v2.11.2` the `respondingTimeouts.tcp` and `respondingTimeouts.http` sections introduced in `v2.11.1` have been removed.
To configure responding timeouts
### EntryPoint.Transport.RespondingTimeouts.ReadTimeout
Starting with `v2.11.2` the entryPoints `readTimeout` option default value changed to 5 seconds.
For HTTP, this option defines the maximum duration for reading the entire request, including the body.
For TCP, this option defines the maximum duration for the first bytes to be read on the connection.
The default value was previously set to zero, which means no timeout.
This change has been done to avoid Traefik instances with the default configuration to be hanging forever while waiting for bytes to be read on the connection.
We suggest to adapt this value accordingly to your situation, as the new default value is purposely narrowed,
it can make the connection be closed too early.
Increasing the `readTimeout` value could be the solution notably if you are dealing with the following errors:
- TCP: `Error while handling TCP connection: readfrom tcp X.X.X.X:X->X.X.X.X:X: read tcp X.X.X.X:X->X.X.X.X:X: i/o timeout`
- HTTP: `'499 Client Closed Request' caused by: context canceled`
- HTTP: `ReverseProxy read error during body copy: read tcp X.X.X.X:X->X.X.X.X:X: use of closed network connection`

View file

@ -183,26 +183,14 @@ Duration to give active requests a chance to finish before Traefik stops. (Defau
`--entrypoints.<name>.transport.lifecycle.requestacceptgracetimeout`: `--entrypoints.<name>.transport.lifecycle.requestacceptgracetimeout`:
Duration to keep accepting requests before Traefik initiates the graceful shutdown procedure. (Default: ```0```) Duration to keep accepting requests before Traefik initiates the graceful shutdown procedure. (Default: ```0```)
`--entrypoints.<name>.transport.respondingtimeouts.http.idletimeout`: `--entrypoints.<name>.transport.respondingtimeouts.idletimeout`:
IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set. (Default: ```180```) IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set. (Default: ```180```)
`--entrypoints.<name>.transport.respondingtimeouts.http.readtimeout`:
ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set. (Default: ```0```)
`--entrypoints.<name>.transport.respondingtimeouts.http.writetimeout`:
WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```)
`--entrypoints.<name>.transport.respondingtimeouts.idletimeout`:
(Deprecated) IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set. (Default: ```0```)
`--entrypoints.<name>.transport.respondingtimeouts.readtimeout`: `--entrypoints.<name>.transport.respondingtimeouts.readtimeout`:
(Deprecated) ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set. (Default: ```0```) ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set. (Default: ```5```)
`--entrypoints.<name>.transport.respondingtimeouts.tcp.lingeringtimeout`:
LingeringTimeout is the maximum duration between each TCP read operation on the connection. If zero, no timeout is set. (Default: ```2```)
`--entrypoints.<name>.transport.respondingtimeouts.writetimeout`: `--entrypoints.<name>.transport.respondingtimeouts.writetimeout`:
(Deprecated) WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```) WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```)
`--entrypoints.<name>.udp.timeout`: `--entrypoints.<name>.udp.timeout`:
Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```) Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```)

View file

@ -183,26 +183,14 @@ Duration to give active requests a chance to finish before Traefik stops. (Defau
`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_LIFECYCLE_REQUESTACCEPTGRACETIMEOUT`: `TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_LIFECYCLE_REQUESTACCEPTGRACETIMEOUT`:
Duration to keep accepting requests before Traefik initiates the graceful shutdown procedure. (Default: ```0```) Duration to keep accepting requests before Traefik initiates the graceful shutdown procedure. (Default: ```0```)
`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_HTTP_IDLETIMEOUT`: `TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_IDLETIMEOUT`:
IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set. (Default: ```180```) IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set. (Default: ```180```)
`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_HTTP_READTIMEOUT`:
ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set. (Default: ```0```)
`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_HTTP_WRITETIMEOUT`:
WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```)
`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_IDLETIMEOUT`:
(Deprecated) IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set. (Default: ```0```)
`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_READTIMEOUT`: `TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_READTIMEOUT`:
(Deprecated) ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set. (Default: ```0```) ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set. (Default: ```5```)
`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_TCP_LINGERINGTIMEOUT`:
LingeringTimeout is the maximum duration between each TCP read operation on the connection. If zero, no timeout is set. (Default: ```2```)
`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_WRITETIMEOUT`: `TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_WRITETIMEOUT`:
(Deprecated) WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```) WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```)
`TRAEFIK_ENTRYPOINTS_<NAME>_UDP_TIMEOUT`: `TRAEFIK_ENTRYPOINTS_<NAME>_UDP_TIMEOUT`:
Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```) Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```)

View file

@ -26,12 +26,6 @@
readTimeout = "42s" readTimeout = "42s"
writeTimeout = "42s" writeTimeout = "42s"
idleTimeout = "42s" idleTimeout = "42s"
[entryPoints.EntryPoint0.transport.respondingTimeouts.http]
readTimeout = "42s"
writeTimeout = "42s"
idleTimeout = "42s"
[entryPoints.EntryPoint0.transport.respondingTimeouts.tcp]
lingeringTimeout = "42s"
[entryPoints.EntryPoint0.proxyProtocol] [entryPoints.EntryPoint0.proxyProtocol]
insecure = true insecure = true
trustedIPs = ["foobar", "foobar"] trustedIPs = ["foobar", "foobar"]

View file

@ -24,12 +24,6 @@ entryPoints:
readTimeout: 42s readTimeout: 42s
writeTimeout: 42s writeTimeout: 42s
idleTimeout: 42s idleTimeout: 42s
http:
readTimeout: 42s
writeTimeout: 42s
idleTimeout: 42s
tcp:
lingeringTimeout: 42s
keepAliveMaxTime: 42s keepAliveMaxTime: 42s
keepAliveMaxRequests: 42 keepAliveMaxRequests: 42
proxyProtocol: proxyProtocol:

View file

@ -397,19 +397,19 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward
#### `respondingTimeouts` #### `respondingTimeouts`
##### `http` `respondingTimeouts` are timeouts for incoming requests to the Traefik instance.
Setting them has no effect for UDP entryPoints.
`respondingTimeouts.http` are timeouts for incoming requests to the Traefik instance. ??? info "`transport.respondingTimeouts.readTimeout`"
??? info "`transport.respondingTimeouts.http.readTimeout`" _Optional, Default=5s_
_Optional, Default=0s_
`readTimeout` is the maximum duration for reading the entire request, including the body. `readTimeout` is the maximum duration for reading the entire request, including the body.
If zero, no timeout exists. If zero, no timeout exists.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits). Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds. If no units are provided, the value is parsed assuming seconds.
For requests with large payloads, this timeout value might be increased.
```yaml tab="File (YAML)" ```yaml tab="File (YAML)"
## Static configuration ## Static configuration
@ -418,7 +418,6 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward
address: ":8888" address: ":8888"
transport: transport:
respondingTimeouts: respondingTimeouts:
http:
readTimeout: 42 readTimeout: 42
``` ```
@ -427,17 +426,18 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward
[entryPoints] [entryPoints]
[entryPoints.name] [entryPoints.name]
address = ":8888" address = ":8888"
[entryPoints.name.transport.respondingTimeouts.http] [entryPoints.name.transport]
[entryPoints.name.transport.respondingTimeouts]
readTimeout = 42 readTimeout = 42
``` ```
```bash tab="CLI" ```bash tab="CLI"
## Static configuration ## Static configuration
--entryPoints.name.address=:8888 --entryPoints.name.address=:8888
--entryPoints.name.transport.respondingTimeouts.http.readTimeout=42 --entryPoints.name.transport.respondingTimeouts.readTimeout=42
``` ```
??? info "`transport.respondingTimeouts.http.writeTimeout`" ??? info "`transport.respondingTimeouts.writeTimeout`"
_Optional, Default=0s_ _Optional, Default=0s_
@ -455,7 +455,6 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward
address: ":8888" address: ":8888"
transport: transport:
respondingTimeouts: respondingTimeouts:
http:
writeTimeout: 42 writeTimeout: 42
``` ```
@ -464,17 +463,18 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward
[entryPoints] [entryPoints]
[entryPoints.name] [entryPoints.name]
address = ":8888" address = ":8888"
[entryPoints.name.transport.respondingTimeouts.http] [entryPoints.name.transport]
[entryPoints.name.transport.respondingTimeouts]
writeTimeout = 42 writeTimeout = 42
``` ```
```bash tab="CLI" ```bash tab="CLI"
## Static configuration ## Static configuration
--entryPoints.name.address=:8888 --entryPoints.name.address=:8888
--entryPoints.name.transport.respondingTimeouts.http.writeTimeout=42 --entryPoints.name.transport.respondingTimeouts.writeTimeout=42
``` ```
??? info "`transport.respondingTimeouts.http.idleTimeout`" ??? info "`transport.respondingTimeouts.idleTimeout`"
_Optional, Default=180s_ _Optional, Default=180s_
@ -491,7 +491,6 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward
address: ":8888" address: ":8888"
transport: transport:
respondingTimeouts: respondingTimeouts:
http:
idleTimeout: 42 idleTimeout: 42
``` ```
@ -500,54 +499,15 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward
[entryPoints] [entryPoints]
[entryPoints.name] [entryPoints.name]
address = ":8888" address = ":8888"
[entryPoints.name.transport.respondingTimeouts.http] [entryPoints.name.transport]
[entryPoints.name.transport.respondingTimeouts]
idleTimeout = 42 idleTimeout = 42
``` ```
```bash tab="CLI" ```bash tab="CLI"
## Static configuration ## Static configuration
--entryPoints.name.address=:8888 --entryPoints.name.address=:8888
--entryPoints.name.transport.respondingTimeouts.http.idleTimeout=42 --entryPoints.name.transport.respondingTimeouts.idleTimeout=42
##### `tcp`
`respondingTimeouts.tcp` are timeouts for client connections to the Traefik instance.
??? info "`transport.respondingTimeouts.tcp.lingeringTimeout`"
_Optional, Default=2s_
`lingeringTimeout` is the maximum duration between each TCP read operation on the connection.
As a layer 4 timeout, it also applies during HTTP handling, but respect the configured HTTP server `readTimeout`.
If zero, the lingering is disabled.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds.
```yaml tab="File (YAML)"
## Static configuration
entryPoints:
name:
address: ":8888"
transport:
respondingTimeouts:
tcp:
lingeringTimeout: 42
```
```toml tab="File (TOML)"
## Static configuration
[entryPoints]
[entryPoints.name]
address = ":8888"
[entryPoints.name.transport.respondingTimeouts.tcp]
lingeringTimeout = 42
```
```bash tab="CLI"
## Static configuration
--entryPoints.name.address=:8888
--entryPoints.name.transport.respondingTimeouts.tcp.lingeringTimeout=42
``` ```
#### `lifeCycle` #### `lifeCycle`

View file

@ -13,7 +13,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v2/pkg/config/runtime" "github.com/traefik/traefik/v2/pkg/config/runtime"
"github.com/traefik/traefik/v2/pkg/config/static" "github.com/traefik/traefik/v2/pkg/config/static"
) )
@ -56,11 +55,9 @@ func TestHandler_EntryPoints(t *testing.T) {
GraceTimeOut: 2, GraceTimeOut: 2,
}, },
RespondingTimeouts: &static.RespondingTimeouts{ RespondingTimeouts: &static.RespondingTimeouts{
HTTP: &static.HTTPRespondingTimeouts{ ReadTimeout: 3,
ReadTimeout: paerserDurationPtr(3), WriteTimeout: 4,
WriteTimeout: paerserDurationPtr(4), IdleTimeout: 5,
IdleTimeout: paerserDurationPtr(5),
},
}, },
}, },
ProxyProtocol: &static.ProxyProtocol{ ProxyProtocol: &static.ProxyProtocol{
@ -80,11 +77,9 @@ func TestHandler_EntryPoints(t *testing.T) {
GraceTimeOut: 20, GraceTimeOut: 20,
}, },
RespondingTimeouts: &static.RespondingTimeouts{ RespondingTimeouts: &static.RespondingTimeouts{
HTTP: &static.HTTPRespondingTimeouts{ ReadTimeout: 30,
ReadTimeout: paerserDurationPtr(3), WriteTimeout: 40,
WriteTimeout: paerserDurationPtr(4), IdleTimeout: 50,
IdleTimeout: paerserDurationPtr(5),
},
}, },
}, },
ProxyProtocol: &static.ProxyProtocol{ ProxyProtocol: &static.ProxyProtocol{
@ -268,8 +263,3 @@ func generateEntryPoints(nb int) map[string]*static.EntryPoint {
return eps return eps
} }
func paerserDurationPtr(duration int) *ptypes.Duration {
d := ptypes.Duration(duration)
return &d
}

View file

@ -23,13 +23,11 @@
"requestAcceptGraceTimeout": "1ns" "requestAcceptGraceTimeout": "1ns"
}, },
"respondingTimeouts": { "respondingTimeouts": {
"http": {
"idleTimeout": "5ns", "idleTimeout": "5ns",
"readTimeout": "3ns", "readTimeout": "3ns",
"writeTimeout": "4ns" "writeTimeout": "4ns"
} }
} }
}
}, },
{ {
"address": ":443", "address": ":443",
@ -55,11 +53,9 @@
"requestAcceptGraceTimeout": "10ns" "requestAcceptGraceTimeout": "10ns"
}, },
"respondingTimeouts": { "respondingTimeouts": {
"http": { "idleTimeout": "50ns",
"idleTimeout": "5ns", "readTimeout": "30ns",
"readTimeout": "3ns", "writeTimeout": "40ns"
"writeTimeout": "4ns"
}
} }
} }
} }

View file

@ -50,15 +50,15 @@ const (
// DefaultIdleTimeout before closing an idle connection. // DefaultIdleTimeout before closing an idle connection.
DefaultIdleTimeout = 180 * time.Second DefaultIdleTimeout = 180 * time.Second
// DefaultReadTimeout defines the default maximum duration for reading the entire request, including the body.
DefaultReadTimeout = 5 * time.Second
// DefaultAcmeCAServer is the default ACME API endpoint. // DefaultAcmeCAServer is the default ACME API endpoint.
DefaultAcmeCAServer = "https://acme-v02.api.letsencrypt.org/directory" DefaultAcmeCAServer = "https://acme-v02.api.letsencrypt.org/directory"
// DefaultUDPTimeout defines how long to wait by default on an idle session, // DefaultUDPTimeout defines how long to wait by default on an idle session,
// before releasing all resources related to that session. // before releasing all resources related to that session.
DefaultUDPTimeout = 3 * time.Second DefaultUDPTimeout = 3 * time.Second
// defaultLingeringTimeout defines the default maximum duration between each read operation on the connection.
defaultLingeringTimeout = 2 * time.Second
) )
// Configuration is the static configuration. // Configuration is the static configuration.
@ -121,44 +121,17 @@ func (a *API) SetDefaults() {
a.Dashboard = true a.Dashboard = true
} }
// RespondingTimeouts contains timeout configurations. // RespondingTimeouts contains timeout configurations for incoming requests to the Traefik instance.
type RespondingTimeouts struct { type RespondingTimeouts struct {
// Deprecated: please use `respondingTimeouts.http.readTimeout` instead. ReadTimeout ptypes.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set." json:"readTimeout,omitempty" toml:"readTimeout,omitempty" yaml:"readTimeout,omitempty" export:"true"`
ReadTimeout *ptypes.Duration `description:"(Deprecated) ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set." json:"readTimeout,omitempty" toml:"readTimeout,omitempty" yaml:"readTimeout,omitempty" export:"true"` WriteTimeout ptypes.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set." json:"writeTimeout,omitempty" toml:"writeTimeout,omitempty" yaml:"writeTimeout,omitempty" export:"true"`
// Deprecated: please use `respondingTimeouts.http.writeTimeout` instead. IdleTimeout ptypes.Duration `description:"IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set." json:"idleTimeout,omitempty" toml:"idleTimeout,omitempty" yaml:"idleTimeout,omitempty" export:"true"`
WriteTimeout *ptypes.Duration `description:"(Deprecated) WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set." json:"writeTimeout,omitempty" toml:"writeTimeout,omitempty" yaml:"writeTimeout,omitempty" export:"true"`
// Deprecated: please use `respondingTimeouts.http.idleTimeout` instead.
IdleTimeout *ptypes.Duration `description:"(Deprecated) IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set." json:"idleTimeout,omitempty" toml:"idleTimeout,omitempty" yaml:"idleTimeout,omitempty" export:"true"`
HTTP *HTTPRespondingTimeouts `description:"Defines the HTTP responding timeouts." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"`
TCP *TCPRespondingTimeouts `description:"Defines the TCP responding timeouts." json:"tcp,omitempty" toml:"tcp,omitempty" yaml:"tcp,omitempty" export:"true"`
}
// HTTPRespondingTimeouts contains HTTP timeout configurations for incoming requests to the Traefik instance.
type HTTPRespondingTimeouts struct {
ReadTimeout *ptypes.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set." json:"readTimeout,omitempty" toml:"readTimeout,omitempty" yaml:"readTimeout,omitempty" export:"true"`
WriteTimeout *ptypes.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set." json:"writeTimeout,omitempty" toml:"writeTimeout,omitempty" yaml:"writeTimeout,omitempty" export:"true"`
IdleTimeout *ptypes.Duration `description:"IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set." json:"idleTimeout,omitempty" toml:"idleTimeout,omitempty" yaml:"idleTimeout,omitempty" export:"true"`
}
// TCPRespondingTimeouts contains TCP timeout configurations for client connections to the Traefik instance.
type TCPRespondingTimeouts struct {
LingeringTimeout ptypes.Duration `description:"LingeringTimeout is the maximum duration between each TCP read operation on the connection. If zero, no timeout is set." json:"lingeringTimeout,omitempty" toml:"lingeringTimeout,omitempty" yaml:"lingeringTimeout,omitempty" export:"true"`
} }
// SetDefaults sets the default values. // SetDefaults sets the default values.
func (a *RespondingTimeouts) SetDefaults() { func (a *RespondingTimeouts) SetDefaults() {
noTimeout := ptypes.Duration(0) a.ReadTimeout = ptypes.Duration(DefaultReadTimeout)
defaultIdleTimeout := ptypes.Duration(DefaultIdleTimeout) a.IdleTimeout = ptypes.Duration(DefaultIdleTimeout)
a.HTTP = &HTTPRespondingTimeouts{
ReadTimeout: &noTimeout,
WriteTimeout: &noTimeout,
IdleTimeout: &defaultIdleTimeout,
}
a.TCP = &TCPRespondingTimeouts{
LingeringTimeout: ptypes.Duration(defaultLingeringTimeout),
}
} }
// ForwardingTimeouts contains timeout configurations for forwarding requests to the backend servers. // ForwardingTimeouts contains timeout configurations for forwarding requests to the backend servers.
@ -242,39 +215,6 @@ func (c *Configuration) SetEffectiveConfiguration() {
c.EntryPoints["http"] = ep c.EntryPoints["http"] = ep
} }
for _, entrypoint := range c.EntryPoints {
if entrypoint.Transport == nil ||
entrypoint.Transport.RespondingTimeouts == nil {
continue
}
respondingTimeouts := entrypoint.Transport.RespondingTimeouts
if respondingTimeouts.ReadTimeout != nil &&
respondingTimeouts.HTTP != nil &&
respondingTimeouts.HTTP.ReadTimeout == nil {
log.WithoutContext().Warnf("Option `respondingTimeouts.readTimeout` is deprecated, please use `respondingTimeouts.http.readTimeout` instead.")
respondingTimeouts.HTTP.ReadTimeout = respondingTimeouts.ReadTimeout
respondingTimeouts.ReadTimeout = nil
}
if respondingTimeouts.WriteTimeout != nil &&
respondingTimeouts.HTTP != nil &&
respondingTimeouts.HTTP.WriteTimeout == nil {
log.WithoutContext().Warnf("Option `respondingTimeouts.writeTimeout` is deprecated, please use `respondingTimeouts.http.writeTimeout` instead.")
respondingTimeouts.HTTP.WriteTimeout = respondingTimeouts.WriteTimeout
respondingTimeouts.WriteTimeout = nil
}
if respondingTimeouts.IdleTimeout != nil &&
respondingTimeouts.HTTP != nil &&
respondingTimeouts.HTTP.IdleTimeout == nil {
log.WithoutContext().Warnf("Option `respondingTimeouts.idleTimeout` is deprecated, please use `respondingTimeouts.http.idleTimeout` instead.")
respondingTimeouts.HTTP.IdleTimeout = respondingTimeouts.IdleTimeout
respondingTimeouts.IdleTimeout = nil
}
}
// Creates the internal traefik entry point if needed // Creates the internal traefik entry point if needed
if (c.API != nil && c.API.Insecure) || if (c.API != nil && c.API.Insecure) ||
(c.Ping != nil && !c.Ping.ManualRouting && c.Ping.EntryPoint == DefaultInternalEntryPointName) || (c.Ping != nil && !c.Ping.ManualRouting && c.Ping.EntryPoint == DefaultInternalEntryPointName) ||
@ -380,31 +320,6 @@ func (c *Configuration) ValidateConfiguration() error {
return errors.New("Nomad provider cannot have both namespace and namespaces options configured") return errors.New("Nomad provider cannot have both namespace and namespaces options configured")
} }
for epName, entrypoint := range c.EntryPoints {
if entrypoint.Transport == nil ||
entrypoint.Transport.RespondingTimeouts == nil ||
entrypoint.Transport.RespondingTimeouts.HTTP == nil {
continue
}
respondingTimeouts := entrypoint.Transport.RespondingTimeouts
if respondingTimeouts.ReadTimeout != nil &&
respondingTimeouts.HTTP.ReadTimeout != nil {
return fmt.Errorf("entrypoint %q has `readTimeout` option is defined multiple times (`respondingTimeouts.readTimeout` is deprecated)", epName)
}
if respondingTimeouts.WriteTimeout != nil &&
respondingTimeouts.HTTP.WriteTimeout != nil {
return fmt.Errorf("entrypoint %q has `writeTimeout` option is defined multiple times (`respondingTimeouts.writeTimeout` is deprecated)", epName)
}
if respondingTimeouts.IdleTimeout != nil &&
respondingTimeouts.HTTP.IdleTimeout != nil {
return fmt.Errorf("entrypoint %q has `idleTimeout` option is defined multiple times (`respondingTimeouts.idleTimeout` is deprecated)", epName)
}
}
return nil return nil
} }

View file

@ -520,8 +520,6 @@ func TestDo_staticConfiguration(t *testing.T) {
}, },
} }
paerserDuration := ptypes.Duration(111 * time.Second)
config.EntryPoints = static.EntryPoints{ config.EntryPoints = static.EntryPoints{
"foobar": { "foobar": {
Address: "foo Address", Address: "foo Address",
@ -531,14 +529,9 @@ func TestDo_staticConfiguration(t *testing.T) {
GraceTimeOut: ptypes.Duration(111 * time.Second), GraceTimeOut: ptypes.Duration(111 * time.Second),
}, },
RespondingTimeouts: &static.RespondingTimeouts{ RespondingTimeouts: &static.RespondingTimeouts{
HTTP: &static.HTTPRespondingTimeouts{ ReadTimeout: ptypes.Duration(111 * time.Second),
ReadTimeout: &paerserDuration, WriteTimeout: ptypes.Duration(111 * time.Second),
WriteTimeout: &paerserDuration, IdleTimeout: ptypes.Duration(111 * time.Second),
IdleTimeout: &paerserDuration,
},
TCP: &static.TCPRespondingTimeouts{
LingeringTimeout: ptypes.Duration(111 * time.Second),
},
}, },
}, },
ProxyProtocol: &static.ProxyProtocol{ ProxyProtocol: &static.ProxyProtocol{

View file

@ -26,14 +26,9 @@
"graceTimeOut": "1m51s" "graceTimeOut": "1m51s"
}, },
"respondingTimeouts": { "respondingTimeouts": {
"http": {
"readTimeout": "1m51s", "readTimeout": "1m51s",
"writeTimeout": "1m51s", "writeTimeout": "1m51s",
"idleTimeout": "1m51s" "idleTimeout": "1m51s"
},
"tcp": {
"lingeringTimeout": "1m51s"
}
} }
}, },
"proxyProtocol": { "proxyProtocol": {

View file

@ -9,6 +9,7 @@ import (
"net" "net"
"net/http" "net/http"
"slices" "slices"
"time"
"github.com/go-acme/lego/v4/challenge/tlsalpn01" "github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/traefik/traefik/v2/pkg/log" "github.com/traefik/traefik/v2/pkg/log"
@ -116,6 +117,17 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
return return
} }
// Remove read/write deadline and delegate this to underlying tcp server (for now only handled by HTTP Server)
err = conn.SetReadDeadline(time.Time{})
if err != nil {
log.WithoutContext().Errorf("Error while setting read deadline: %v", err)
}
err = conn.SetWriteDeadline(time.Time{})
if err != nil {
log.WithoutContext().Errorf("Error while setting write deadline: %v", err)
}
connData, err := tcpmuxer.NewConnData(hello.serverName, conn, hello.protos) connData, err := tcpmuxer.NewConnData(hello.serverName, conn, hello.protos)
if err != nil { if err != nil {
log.WithoutContext().Errorf("Error while reading TCP connection data: %v", err) log.WithoutContext().Errorf("Error while reading TCP connection data: %v", err)

View file

@ -244,15 +244,24 @@ func (e *TCPEntryPoint) Start(ctx context.Context) {
panic(err) panic(err)
} }
if e.transportConfiguration != nil && safe.Go(func() {
e.transportConfiguration.RespondingTimeouts != nil && // Enforce read/write deadlines at the connection level,
e.transportConfiguration.RespondingTimeouts.TCP != nil && // because when we're peeking the first byte to determine whether we are doing TLS,
e.transportConfiguration.RespondingTimeouts.TCP.LingeringTimeout > 0 { // the deadlines at the server level are not taken into account.
lingeringTimeout := time.Duration(e.transportConfiguration.RespondingTimeouts.TCP.LingeringTimeout) if e.transportConfiguration.RespondingTimeouts.ReadTimeout > 0 {
writeCloser = newLingeringConnection(writeCloser, lingeringTimeout) err := writeCloser.SetReadDeadline(time.Now().Add(time.Duration(e.transportConfiguration.RespondingTimeouts.ReadTimeout)))
if err != nil {
logger.Errorf("Error while setting read deadline: %v", err)
}
}
if e.transportConfiguration.RespondingTimeouts.WriteTimeout > 0 {
err = writeCloser.SetWriteDeadline(time.Now().Add(time.Duration(e.transportConfiguration.RespondingTimeouts.WriteTimeout)))
if err != nil {
logger.Errorf("Error while setting write deadline: %v", err)
}
} }
safe.Go(func() {
e.switcher.ServeTCP(newTrackedConnection(writeCloser, e.tracker)) e.switcher.ServeTCP(newTrackedConnection(writeCloser, e.tracker))
}) })
} }
@ -382,55 +391,6 @@ func writeCloser(conn net.Conn) (tcp.WriteCloser, error) {
} }
} }
// lingeringConn represents a writeCloser with lingeringTimeout handling.
type lingeringConn struct {
tcp.WriteCloser
lingeringTimeout time.Duration
rdlMu sync.RWMutex
// readDeadline is the current readDeadline set by an upper caller.
// In case of HTTP, the HTTP go server manipulates deadlines on the connection.
readDeadline time.Time
}
// newLingeringConnection returns the given writeCloser augmented with lingeringTimeout handling.
func newLingeringConnection(conn tcp.WriteCloser, timeout time.Duration) tcp.WriteCloser {
return &lingeringConn{
WriteCloser: conn,
lingeringTimeout: timeout,
}
}
// Read reads data from the connection and postpones the connection readDeadline according to the lingeringTimeout config.
// It also ensures that the upper level set readDeadline is enforced.
func (l *lingeringConn) Read(b []byte) (int, error) {
if l.lingeringTimeout > 0 {
deadline := time.Now().Add(l.lingeringTimeout)
l.rdlMu.RLock()
if !l.readDeadline.IsZero() && deadline.After(l.readDeadline) {
deadline = l.readDeadline
}
l.rdlMu.RUnlock()
if err := l.WriteCloser.SetReadDeadline(deadline); err != nil {
return 0, err
}
}
return l.WriteCloser.Read(b)
}
// SetReadDeadline sets and save the read deadline.
func (l *lingeringConn) SetReadDeadline(t time.Time) error {
l.rdlMu.Lock()
l.readDeadline = t
l.rdlMu.Unlock()
return l.WriteCloser.SetReadDeadline(t)
}
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. // connections.
type tcpKeepAliveListener struct { type tcpKeepAliveListener struct {
@ -459,7 +419,7 @@ func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
} }
func buildProxyProtocolListener(ctx context.Context, entryPoint *static.EntryPoint, listener net.Listener) (net.Listener, error) { func buildProxyProtocolListener(ctx context.Context, entryPoint *static.EntryPoint, listener net.Listener) (net.Listener, error) {
timeout := *entryPoint.Transport.RespondingTimeouts.HTTP.ReadTimeout timeout := entryPoint.Transport.RespondingTimeouts.ReadTimeout
// proxyproto use 200ms if ReadHeaderTimeout is set to 0 and not no timeout // proxyproto use 200ms if ReadHeaderTimeout is set to 0 and not no timeout
if timeout == 0 { if timeout == 0 {
timeout = -1 timeout = -1
@ -628,9 +588,9 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
serverHTTP := &http.Server{ serverHTTP := &http.Server{
Handler: handler, Handler: handler,
ErrorLog: httpServerLogger, ErrorLog: httpServerLogger,
ReadTimeout: time.Duration(*configuration.Transport.RespondingTimeouts.HTTP.ReadTimeout), ReadTimeout: time.Duration(configuration.Transport.RespondingTimeouts.ReadTimeout),
WriteTimeout: time.Duration(*configuration.Transport.RespondingTimeouts.HTTP.WriteTimeout), WriteTimeout: time.Duration(configuration.Transport.RespondingTimeouts.WriteTimeout),
IdleTimeout: time.Duration(*configuration.Transport.RespondingTimeouts.HTTP.IdleTimeout), IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout),
} }
if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) { if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) {
serverHTTP.ConnContext = func(ctx context.Context, c net.Conn) context.Context { serverHTTP.ConnContext = func(ctx context.Context, c net.Conn) context.Context {

View file

@ -69,10 +69,8 @@ func testShutdown(t *testing.T, router *tcprouter.Router) {
epConfig.LifeCycle.RequestAcceptGraceTimeout = 0 epConfig.LifeCycle.RequestAcceptGraceTimeout = 0
epConfig.LifeCycle.GraceTimeOut = ptypes.Duration(5 * time.Second) epConfig.LifeCycle.GraceTimeOut = ptypes.Duration(5 * time.Second)
readTimeout := ptypes.Duration(5 * time.Second) epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(5 * time.Second)
epConfig.RespondingTimeouts.HTTP.ReadTimeout = &readTimeout epConfig.RespondingTimeouts.WriteTimeout = ptypes.Duration(5 * time.Second)
writeTimeout := ptypes.Duration(5 * time.Second)
epConfig.RespondingTimeouts.HTTP.WriteTimeout = &writeTimeout
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
// We explicitly use an IPV4 address because on Alpine, with an IPV6 address // We explicitly use an IPV4 address because on Alpine, with an IPV6 address
@ -159,8 +157,7 @@ func startEntrypoint(entryPoint *TCPEntryPoint, router *tcprouter.Router) (net.C
func TestReadTimeoutWithoutFirstByte(t *testing.T) { func TestReadTimeoutWithoutFirstByte(t *testing.T) {
epConfig := &static.EntryPointsTransport{} epConfig := &static.EntryPointsTransport{}
epConfig.SetDefaults() epConfig.SetDefaults()
readTimeout := ptypes.Duration(2 * time.Second) epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(2 * time.Second)
epConfig.RespondingTimeouts.HTTP.ReadTimeout = &readTimeout
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
Address: ":0", Address: ":0",
@ -197,84 +194,7 @@ func TestReadTimeoutWithoutFirstByte(t *testing.T) {
func TestReadTimeoutWithFirstByte(t *testing.T) { func TestReadTimeoutWithFirstByte(t *testing.T) {
epConfig := &static.EntryPointsTransport{} epConfig := &static.EntryPointsTransport{}
epConfig.SetDefaults() epConfig.SetDefaults()
readTimeout := ptypes.Duration(2 * time.Second) epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(2 * time.Second)
epConfig.RespondingTimeouts.HTTP.ReadTimeout = &readTimeout
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
Address: ":0",
Transport: epConfig,
ForwardedHeaders: &static.ForwardedHeaders{},
HTTP2: &static.HTTP2Config{},
}, nil)
require.NoError(t, err)
router := &tcprouter.Router{}
router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
}))
conn, err := startEntrypoint(entryPoint, router)
require.NoError(t, err)
_, err = conn.Write([]byte("GET /some HTTP/1.1\r\n"))
require.NoError(t, err)
errChan := make(chan error)
go func() {
b := make([]byte, 2048)
_, err := conn.Read(b)
errChan <- err
}()
select {
case err := <-errChan:
require.Equal(t, io.EOF, err)
case <-time.Tick(5 * time.Second):
t.Error("Timeout while read")
}
}
func TestLingeringTimeoutWithoutFirstByte(t *testing.T) {
epConfig := &static.EntryPointsTransport{}
epConfig.SetDefaults()
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
Address: ":0",
Transport: epConfig,
ForwardedHeaders: &static.ForwardedHeaders{},
HTTP2: &static.HTTP2Config{},
}, nil)
require.NoError(t, err)
router := &tcprouter.Router{}
router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
}))
conn, err := startEntrypoint(entryPoint, router)
require.NoError(t, err)
errChan := make(chan error)
go func() {
b := make([]byte, 2048)
_, err := conn.Read(b)
errChan <- err
}()
select {
case err := <-errChan:
require.Equal(t, io.EOF, err)
case <-time.Tick(5 * time.Second):
t.Error("Timeout while read")
}
}
func TestLingeringTimeoutWithFirstByte(t *testing.T) {
epConfig := &static.EntryPointsTransport{}
epConfig.SetDefaults()
epConfig.RespondingTimeouts.TCP.LingeringTimeout = ptypes.Duration(time.Second)
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
Address: ":0", Address: ":0",