Skip to content

Commit b82a39c

Browse files
authored
transport: rate limit new connections (#3283)
This rate limits new connections to prevent DoS attacks. For effectively rate limiting QUIC connections, we now gate QUIC connection attempts before the handshake, so that we don't spend compute on handshakes for connections that will eventually be cancelled. We can only set a single ConnContext per quic-go Transport, as there's only 1 listener per quic-go Transport. So we cannot set a different ConnContext for listeners on the same address. As we're now gating QUIC connections before the handshake, we use source address verification to ensure that spoofed IPs cannot DoS new connections from a particular IP. This is done by ensuring that some of the connection attempts always verify the source address. We get DoS protection at the expense of increased latency of source address verification.
1 parent fb1d951 commit b82a39c

30 files changed

+912
-217
lines changed

.golangci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ linters:
1010
- prealloc
1111
disable:
1212
- errcheck
13+
- staticcheck
1314

1415
settings:
1516
revive:

config/config.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,28 @@ func (cfg *Config) addTransports() ([]fx.Option, error) {
380380
fxopts = append(fxopts, cfg.QUICReuse...)
381381
} else {
382382
fxopts = append(fxopts,
383-
fx.Provide(func(key quic.StatelessResetKey, tokenGenerator quic.TokenGeneratorKey, lifecycle fx.Lifecycle) (*quicreuse.ConnManager, error) {
384-
var opts []quicreuse.Option
383+
fx.Provide(func(key quic.StatelessResetKey, tokenGenerator quic.TokenGeneratorKey, rcmgr network.ResourceManager, lifecycle fx.Lifecycle) (*quicreuse.ConnManager, error) {
384+
opts := []quicreuse.Option{
385+
quicreuse.ConnContext(func(ctx context.Context, clientInfo *quic.ClientInfo) (context.Context, error) {
386+
// even if creating the quic maddr fails, let the rcmgr decide what to do with the connection
387+
addr, err := quicreuse.ToQuicMultiaddr(clientInfo.RemoteAddr, quic.Version1)
388+
if err != nil {
389+
addr = nil
390+
}
391+
scope, err := rcmgr.OpenConnection(network.DirInbound, false, addr)
392+
if err != nil {
393+
return ctx, err
394+
}
395+
ctx = network.WithConnManagementScope(ctx, scope)
396+
context.AfterFunc(ctx, func() {
397+
scope.Done()
398+
})
399+
return ctx, nil
400+
}),
401+
quicreuse.VerifySourceAddress(func(addr net.Addr) bool {
402+
return rcmgr.VerifySourceAddress(addr)
403+
}),
404+
}
385405
if !cfg.DisableMetrics {
386406
opts = append(opts, quicreuse.EnableMetrics(cfg.PrometheusRegisterer))
387407
}

core/network/mocks/mock_resource_manager.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/network/rcmgr.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package network
22

33
import (
4+
"context"
5+
"errors"
6+
"net"
7+
48
"github.com/libp2p/go-libp2p/core/peer"
59
"github.com/libp2p/go-libp2p/core/protocol"
610
"github.com/multiformats/go-multiaddr"
@@ -87,6 +91,10 @@ type ResourceManager interface {
8791
// the end of the scope's span.
8892
OpenConnection(dir Direction, usefd bool, endpoint multiaddr.Multiaddr) (ConnManagementScope, error)
8993

94+
// VerifySourceAddress tells the transport to verify the source address for an incoming connection
95+
// before gating the connection with OpenConnection.
96+
VerifySourceAddress(addr net.Addr) bool
97+
9098
// OpenStream creates a new stream scope, initially unnegotiated.
9199
// An unnegotiated stream will be initially unattached to any protocol scope
92100
// and constrained by the transient scope.
@@ -269,9 +277,30 @@ type ScopeStat struct {
269277
Memory int64
270278
}
271279

280+
// connManagementScopeKey is the key to store Scope in contexts
281+
type connManagementScopeKey struct{}
282+
283+
func WithConnManagementScope(ctx context.Context, scope ConnManagementScope) context.Context {
284+
return context.WithValue(ctx, connManagementScopeKey{}, scope)
285+
}
286+
287+
func UnwrapConnManagementScope(ctx context.Context) (ConnManagementScope, error) {
288+
v := ctx.Value(connManagementScopeKey{})
289+
if v == nil {
290+
return nil, errors.New("context has no ConnManagementScope")
291+
}
292+
scope, ok := v.(ConnManagementScope)
293+
if !ok {
294+
return nil, errors.New("context has no ConnManagementScope")
295+
}
296+
return scope, nil
297+
}
298+
272299
// NullResourceManager is a stub for tests and initialization of default values
273300
type NullResourceManager struct{}
274301

302+
var _ ResourceManager = (*NullResourceManager)(nil)
303+
275304
var _ ResourceScope = (*NullScope)(nil)
276305
var _ ResourceScopeSpan = (*NullScope)(nil)
277306
var _ ServiceScope = (*NullScope)(nil)
@@ -306,6 +335,10 @@ func (n *NullResourceManager) OpenConnection(dir Direction, usefd bool, endpoint
306335
func (n *NullResourceManager) OpenStream(p peer.ID, dir Direction) (StreamManagementScope, error) {
307336
return &NullScope{}, nil
308337
}
338+
func (*NullResourceManager) VerifySourceAddress(_ net.Addr) bool {
339+
return false
340+
}
341+
309342
func (n *NullResourceManager) Close() error {
310343
return nil
311344
}
@@ -324,3 +357,4 @@ func (n *NullScope) ProtocolScope() ProtocolScope { return &NullScop
324357
func (n *NullScope) SetProtocol(proto protocol.ID) error { return nil }
325358
func (n *NullScope) ServiceScope() ServiceScope { return &NullScope{} }
326359
func (n *NullScope) SetService(srv string) error { return nil }
360+
func (n *NullScope) VerifySourceAddress(_ net.Addr) bool { return false }

go.mod

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,18 @@ require (
5353
github.com/pion/webrtc/v4 v4.0.14
5454
github.com/prometheus/client_golang v1.21.0
5555
github.com/prometheus/client_model v0.6.1
56-
github.com/quic-go/quic-go v0.50.0
56+
github.com/quic-go/quic-go v0.52.0
5757
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66
5858
github.com/stretchr/testify v1.10.0
5959
go.uber.org/fx v1.23.0
6060
go.uber.org/goleak v1.3.0
61-
go.uber.org/mock v0.5.0
61+
go.uber.org/mock v0.5.2
6262
go.uber.org/zap v1.27.0
63-
golang.org/x/crypto v0.35.0
64-
golang.org/x/sync v0.11.0
65-
golang.org/x/sys v0.30.0
63+
golang.org/x/crypto v0.37.0
64+
golang.org/x/sync v0.14.0
65+
golang.org/x/sys v0.33.0
6666
golang.org/x/time v0.11.0
67-
golang.org/x/tools v0.30.0
67+
golang.org/x/tools v0.32.0
6868
google.golang.org/protobuf v1.36.5
6969
)
7070

@@ -74,7 +74,7 @@ require (
7474
github.com/davecgh/go-spew v1.1.1 // indirect
7575
github.com/francoispqt/gojay v1.2.13 // indirect
7676
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
77-
github.com/google/pprof v0.0.0-20250208200701-d0013a598941 // indirect
77+
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a // indirect
7878
github.com/google/uuid v1.6.0 // indirect
7979
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
8080
github.com/mattn/go-isatty v0.0.20 // indirect
@@ -83,7 +83,7 @@ require (
8383
github.com/minio/sha256-simd v1.0.1 // indirect
8484
github.com/multiformats/go-base36 v0.2.0 // indirect
8585
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
86-
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
86+
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
8787
github.com/pion/dtls/v2 v2.2.12 // indirect
8888
github.com/pion/dtls/v3 v3.0.4 // indirect
8989
github.com/pion/interceptor v0.1.37 // indirect
@@ -103,12 +103,13 @@ require (
103103
github.com/quic-go/qpack v0.5.1 // indirect
104104
github.com/spaolacci/murmur3 v1.1.0 // indirect
105105
github.com/wlynxg/anet v0.0.5 // indirect
106+
go.uber.org/automaxprocs v1.6.0 // indirect
106107
go.uber.org/dig v1.18.0 // indirect
107108
go.uber.org/multierr v1.11.0 // indirect
108109
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect
109-
golang.org/x/mod v0.23.0 // indirect
110-
golang.org/x/net v0.35.0 // indirect
111-
golang.org/x/text v0.22.0 // indirect
110+
golang.org/x/mod v0.24.0 // indirect
111+
golang.org/x/net v0.39.0 // indirect
112+
golang.org/x/text v0.24.0 // indirect
112113
gopkg.in/yaml.v3 v3.0.1 // indirect
113114
lukechampine.com/blake3 v1.4.0 // indirect
114115
)

0 commit comments

Comments
 (0)