Skip to content

Commit 39199ef

Browse files
Restore connection-level limiter (#5372)
Restore connection level limiter to prevent OOM incidents. (removed in #4402) This limiter is used in addition to the request-level throttle so that once our in-flight requests reaches max_connections a 429 is returned, but if the total connections the server uses is over max_connections*1.1 the server drops the connection before the TLS handshake.
1 parent 0418108 commit 39199ef

File tree

7 files changed

+311
-76
lines changed

7 files changed

+311
-76
lines changed

NOTICE-fips.txt

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4594,6 +4594,43 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
45944594
THE SOFTWARE.
45954595

45964596

4597+
--------------------------------------------------------------------------------
4598+
Dependency : golang.org/x/net
4599+
Version: v0.41.0
4600+
Licence type (autodetected): BSD-3-Clause
4601+
--------------------------------------------------------------------------------
4602+
4603+
Contents of probable licence file $GOMODCACHE/golang.org/x/[email protected]/LICENSE:
4604+
4605+
Copyright 2009 The Go Authors.
4606+
4607+
Redistribution and use in source and binary forms, with or without
4608+
modification, are permitted provided that the following conditions are
4609+
met:
4610+
4611+
* Redistributions of source code must retain the above copyright
4612+
notice, this list of conditions and the following disclaimer.
4613+
* Redistributions in binary form must reproduce the above
4614+
copyright notice, this list of conditions and the following disclaimer
4615+
in the documentation and/or other materials provided with the
4616+
distribution.
4617+
* Neither the name of Google LLC nor the names of its
4618+
contributors may be used to endorse or promote products derived from
4619+
this software without specific prior written permission.
4620+
4621+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4622+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
4623+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
4624+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
4625+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
4626+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
4627+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
4628+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
4629+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
4630+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
4631+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4632+
4633+
45974634
--------------------------------------------------------------------------------
45984635
Dependency : golang.org/x/sync
45994636
Version: v0.16.0
@@ -9656,43 +9693,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
96569693
THE SOFTWARE.
96579694

96589695

9659-
--------------------------------------------------------------------------------
9660-
Dependency : golang.org/x/net
9661-
Version: v0.41.0
9662-
Licence type (autodetected): BSD-3-Clause
9663-
--------------------------------------------------------------------------------
9664-
9665-
Contents of probable licence file $GOMODCACHE/golang.org/x/[email protected]/LICENSE:
9666-
9667-
Copyright 2009 The Go Authors.
9668-
9669-
Redistribution and use in source and binary forms, with or without
9670-
modification, are permitted provided that the following conditions are
9671-
met:
9672-
9673-
* Redistributions of source code must retain the above copyright
9674-
notice, this list of conditions and the following disclaimer.
9675-
* Redistributions in binary form must reproduce the above
9676-
copyright notice, this list of conditions and the following disclaimer
9677-
in the documentation and/or other materials provided with the
9678-
distribution.
9679-
* Neither the name of Google LLC nor the names of its
9680-
contributors may be used to endorse or promote products derived from
9681-
this software without specific prior written permission.
9682-
9683-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
9684-
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
9685-
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
9686-
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9687-
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9688-
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9689-
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
9690-
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
9691-
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
9692-
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
9693-
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
9694-
9695-
96969696
--------------------------------------------------------------------------------
96979697
Dependency : golang.org/x/sys
96989698
Version: v0.33.0

NOTICE.txt

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4594,6 +4594,43 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
45944594
THE SOFTWARE.
45954595

45964596

4597+
--------------------------------------------------------------------------------
4598+
Dependency : golang.org/x/net
4599+
Version: v0.41.0
4600+
Licence type (autodetected): BSD-3-Clause
4601+
--------------------------------------------------------------------------------
4602+
4603+
Contents of probable licence file $GOMODCACHE/golang.org/x/[email protected]/LICENSE:
4604+
4605+
Copyright 2009 The Go Authors.
4606+
4607+
Redistribution and use in source and binary forms, with or without
4608+
modification, are permitted provided that the following conditions are
4609+
met:
4610+
4611+
* Redistributions of source code must retain the above copyright
4612+
notice, this list of conditions and the following disclaimer.
4613+
* Redistributions in binary form must reproduce the above
4614+
copyright notice, this list of conditions and the following disclaimer
4615+
in the documentation and/or other materials provided with the
4616+
distribution.
4617+
* Neither the name of Google LLC nor the names of its
4618+
contributors may be used to endorse or promote products derived from
4619+
this software without specific prior written permission.
4620+
4621+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4622+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
4623+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
4624+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
4625+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
4626+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
4627+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
4628+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
4629+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
4630+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
4631+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4632+
4633+
45974634
--------------------------------------------------------------------------------
45984635
Dependency : golang.org/x/sync
45994636
Version: v0.16.0
@@ -9723,43 +9760,6 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
97239760
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
97249761

97259762

9726-
--------------------------------------------------------------------------------
9727-
Dependency : golang.org/x/net
9728-
Version: v0.41.0
9729-
Licence type (autodetected): BSD-3-Clause
9730-
--------------------------------------------------------------------------------
9731-
9732-
Contents of probable licence file $GOMODCACHE/golang.org/x/[email protected]/LICENSE:
9733-
9734-
Copyright 2009 The Go Authors.
9735-
9736-
Redistribution and use in source and binary forms, with or without
9737-
modification, are permitted provided that the following conditions are
9738-
met:
9739-
9740-
* Redistributions of source code must retain the above copyright
9741-
notice, this list of conditions and the following disclaimer.
9742-
* Redistributions in binary form must reproduce the above
9743-
copyright notice, this list of conditions and the following disclaimer
9744-
in the documentation and/or other materials provided with the
9745-
distribution.
9746-
* Neither the name of Google LLC nor the names of its
9747-
contributors may be used to endorse or promote products derived from
9748-
this software without specific prior written permission.
9749-
9750-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
9751-
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
9752-
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
9753-
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9754-
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9755-
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9756-
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
9757-
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
9758-
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
9759-
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
9760-
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
9761-
9762-
97639763
--------------------------------------------------------------------------------
97649764
Dependency : golang.org/x/sys
97659765
Version: v0.33.0
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Kind can be one of:
2+
# - breaking-change: a change to previously-documented behavior
3+
# - deprecation: functionality that is being removed in a later release
4+
# - bug-fix: fixes a problem in a previous version
5+
# - enhancement: extends functionality but does not break or fix existing behavior
6+
# - feature: new functionality
7+
# - known-issue: problems that we are aware of in a given version
8+
# - security: impacts on the security of a product or a user’s deployment.
9+
# - upgrade: important information for someone upgrading from a prior version
10+
# - other: does not fit into any of the other categories
11+
kind: bug-fix
12+
13+
# Change summary; a 80ish characters long description of the change.
14+
summary: Restore connection limiter
15+
16+
# Long description; in case the summary is not enough to describe the change
17+
# this field accommodate a description without length limits.
18+
# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment.
19+
description: |
20+
Restore connection level limiter to prevent OOM incidents.
21+
This limiter is used in addition to the request-level throttle so that once
22+
our in-flight requests reaches max_connections a 429 is returned, but if the
23+
total connections the server uses is over max_connections*1.1 the server drops
24+
the connection before the TLS handshake.
25+
26+
27+
# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
28+
component: fleet-server
29+
30+
# PR URL; optional; the PR number that added the changeset.
31+
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
32+
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
33+
# Please provide it if you are adding a fragment for a different PR.
34+
pr: https://github.com/elastic/fleet-server/pull/5372
35+
36+
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
37+
# If not present is automatically filled by the tooling with the issue linked to the PR number.
38+
#issue: https://github.com/owner/repo/1234

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ require (
3737
go.elastic.co/apm/v2 v2.7.1
3838
go.elastic.co/ecszerolog v0.2.0
3939
go.uber.org/zap v1.27.0
40+
golang.org/x/net v0.41.0
4041
golang.org/x/sync v0.16.0
4142
golang.org/x/time v0.12.0
4243
google.golang.org/grpc v1.75.0
@@ -92,7 +93,6 @@ require (
9293
go.uber.org/multierr v1.11.0 // indirect
9394
golang.org/x/crypto v0.39.0 // indirect
9495
golang.org/x/mod v0.25.0 // indirect
95-
golang.org/x/net v0.41.0 // indirect
9696
golang.org/x/sys v0.33.0 // indirect
9797
golang.org/x/text v0.26.0 // indirect
9898
golang.org/x/tools v0.33.0 // indirect

internal/pkg/api/server.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ import (
1616
"github.com/elastic/elastic-agent-libs/logp"
1717
"github.com/elastic/elastic-agent-libs/transport/tlscommon"
1818

19+
"github.com/rs/zerolog"
20+
1921
"github.com/elastic/fleet-server/v7/internal/pkg/config"
22+
"github.com/elastic/fleet-server/v7/internal/pkg/limit"
2023
"github.com/elastic/fleet-server/v7/internal/pkg/logger/ecs"
2124
"github.com/elastic/fleet-server/v7/internal/pkg/logger/zap"
22-
"github.com/rs/zerolog"
2325
)
2426

2527
type server struct {
@@ -32,6 +34,7 @@ type server struct {
3234
// NewServer creates a new HTTP api for the passed addr.
3335
//
3436
// The server has an http request limit and endpoint specific rate-limits.
37+
// There is also a connection limit that will drop connections if too many connections are formed.
3538
// The underlying API structs (such as *CheckinT) may be shared between servers.
3639
func NewServer(addr string, cfg *config.Server, opts ...APIOpt) *server {
3740
a := &apiServer{}
@@ -79,6 +82,13 @@ func (s *server) Run(ctx context.Context) error {
7982
}
8083
}()
8184

85+
// Conn Limiter must be before the TLS handshake in the stack;
86+
// The server should not eat the cost of the handshake if there
87+
// is no capacity to service the connection.
88+
// Also, it appears the HTTP2 implementation depends on the tls.Listener
89+
// being at the top of the stack.
90+
ln = wrapConnLimitter(ctx, ln, s.cfg)
91+
8292
if s.cfg.TLS != nil && s.cfg.TLS.IsEnabled() {
8393
commonTLSCfg, err := tlscommon.LoadTLSServerConfig(s.cfg.TLS, s.logger)
8494
if err != nil {
@@ -161,3 +171,21 @@ func errLogger(ctx context.Context) *slog.Logger {
161171
stub := &stubLogger{*log}
162172
return slog.New(stub, "", 0)
163173
}
174+
175+
// wrapConnLimitter will drop connections once the connection count is max_connections*1.1
176+
// This means that once the limit is reached, the server will resturn 429 responses until the connection count reaches the threshold, then the server will drop connections before the TLS handshake.
177+
func wrapConnLimitter(ctx context.Context, ln net.Listener, cfg *config.Server) net.Listener {
178+
hardLimit := int(float64(cfg.Limits.MaxConnections) * 1.1)
179+
180+
if hardLimit != 0 {
181+
zerolog.Ctx(ctx).Info().
182+
Int("hardConnLimit", hardLimit).
183+
Msg("server hard connection limiter installed")
184+
185+
ln = limit.Listener(ln, hardLimit, zerolog.Ctx(ctx))
186+
} else {
187+
zerolog.Ctx(ctx).Info().Msg("server hard connection limiter disabled")
188+
}
189+
190+
return ln
191+
}

internal/pkg/limit/listener.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package limit
6+
7+
import (
8+
"net"
9+
"sync"
10+
11+
"github.com/rs/zerolog"
12+
"github.com/rs/zerolog/log"
13+
14+
"github.com/elastic/fleet-server/v7/internal/pkg/logger/ecs"
15+
)
16+
17+
// Derived from netutil.LimitListener but works slightly differently.
18+
// Instead of blocking on the semaphore before acception connection,
19+
// this implementation immediately accepts connections and if cannot
20+
// acquire the semaphore it forces the connection closed.
21+
// Ideally, this limiter is run *before* the TLS handshake occurs
22+
// to prevent DDOS attack that eats all the server's CPU.
23+
// The downside to this is that it will Close() valid connections
24+
// indiscriminately.
25+
26+
func Listener(l net.Listener, n int, log *zerolog.Logger) net.Listener {
27+
return &limitListener{
28+
Listener: l,
29+
sem: make(chan struct{}, n),
30+
done: make(chan struct{}),
31+
log: log,
32+
}
33+
}
34+
35+
type limitListener struct {
36+
net.Listener
37+
sem chan struct{}
38+
closeOnce sync.Once // ensures the done chan is only closed once
39+
done chan struct{} // no values sent; closed when Close is called
40+
log *zerolog.Logger
41+
}
42+
43+
func (l *limitListener) acquire() bool {
44+
select {
45+
case <-l.done:
46+
return false
47+
case l.sem <- struct{}{}:
48+
return true
49+
default:
50+
return false
51+
}
52+
}
53+
func (l *limitListener) release() { <-l.sem }
54+
55+
func (l *limitListener) Accept() (net.Conn, error) {
56+
57+
// Accept the connection irregardless
58+
c, err := l.Listener.Accept()
59+
if err != nil {
60+
return nil, err
61+
}
62+
63+
// If we cannot acquire the semaphore, close the connection
64+
if acquired := l.acquire(); !acquired {
65+
zlog := log.Warn()
66+
67+
var err error
68+
if c != nil {
69+
err = c.Close()
70+
zlog.Str(ecs.ServerAddress, c.LocalAddr().String())
71+
zlog.Str(ecs.ClientAddress, c.RemoteAddr().String())
72+
zlog.Err(err)
73+
}
74+
zlog.Int("max", cap(l.sem)).Msg("Connection closed due to max limit")
75+
76+
return c, nil
77+
}
78+
79+
return &limitListenerConn{Conn: c, release: l.release}, nil
80+
}
81+
82+
func (l *limitListener) Close() error {
83+
err := l.Listener.Close()
84+
l.closeOnce.Do(func() { close(l.done) })
85+
return err
86+
}
87+
88+
type limitListenerConn struct {
89+
net.Conn
90+
releaseOnce sync.Once
91+
release func()
92+
}
93+
94+
func (l *limitListenerConn) Close() error {
95+
err := l.Conn.Close()
96+
l.releaseOnce.Do(l.release)
97+
return err
98+
}

0 commit comments

Comments
 (0)