Merge pull request #1880 from cfromknecht/rate-limit-gossip-queries
discovery: Traffic shaping of gossip query replies
This commit is contained in:
3 files changed
+42
-18
No files matched your search
Generated
+9
@@ -416,6 +416,14 @@
|
|||||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||||
version = "v0.3.0"
|
version = "v0.3.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:c9e7a4b4d47c0ed205d257648b0e5b0440880cb728506e318f8ac7cd36270bc4"
|
||||||
|
name = "golang.org/x/time"
|
||||||
|
packages = ["rate"]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "fbb02b2291d28baffd63558aa44b4b56f178d650"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:c2dee8dbcc504d1a7858f5dbaed7c8b256c512c5e9e81480158c30185bbd2792"
|
digest = "1:c2dee8dbcc504d1a7858f5dbaed7c8b256c512c5e9e81480158c30185bbd2792"
|
||||||
name = "google.golang.org/genproto"
|
name = "google.golang.org/genproto"
|
||||||
@@ -532,6 +540,7 @@
|
|||||||
"golang.org/x/crypto/ssh/terminal",
|
"golang.org/x/crypto/ssh/terminal",
|
||||||
"golang.org/x/net/context",
|
"golang.org/x/net/context",
|
||||||
"golang.org/x/net/proxy",
|
"golang.org/x/net/proxy",
|
||||||
|
"golang.org/x/time/rate",
|
||||||
"google.golang.org/genproto/googleapis/api/annotations",
|
"google.golang.org/genproto/googleapis/api/annotations",
|
||||||
"google.golang.org/grpc",
|
"google.golang.org/grpc",
|
||||||
"google.golang.org/grpc/codes",
|
"google.golang.org/grpc/codes",
|
||||||
|
|||||||
@@ -98,6 +98,10 @@
|
|||||||
name = "golang.org/x/sys"
|
name = "golang.org/x/sys"
|
||||||
revision = "ebe1bf3edb3325c393447059974de898d5133eb8"
|
revision = "ebe1bf3edb3325c393447059974de898d5133eb8"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "golang.org/x/time"
|
||||||
|
revision = "fbb02b2291d28baffd63558aa44b4b56f178d650"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "google.golang.org/genproto"
|
name = "google.golang.org/genproto"
|
||||||
revision = "df60624c1e9b9d2973e889c7a1cff73155da81c4"
|
revision = "df60624c1e9b9d2973e889c7a1cff73155da81c4"
|
||||||
|
|||||||
+29
-18
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
// syncerState is an enum that represents the current state of the
|
// syncerState is an enum that represents the current state of the
|
||||||
@@ -56,12 +57,12 @@ const (
|
|||||||
const (
|
const (
|
||||||
// DefaultMaxUndelayedQueryReplies specifies how many gossip queries we
|
// DefaultMaxUndelayedQueryReplies specifies how many gossip queries we
|
||||||
// will respond to immediately before starting to delay responses.
|
// will respond to immediately before starting to delay responses.
|
||||||
DefaultMaxUndelayedQueryReplies = 5
|
DefaultMaxUndelayedQueryReplies = 10
|
||||||
|
|
||||||
// DefaultDelayedQueryReplyInterval is the length of time we will wait
|
// DefaultDelayedQueryReplyInterval is the length of time we will wait
|
||||||
// before responding to gossip queries after replying to
|
// before responding to gossip queries after replying to
|
||||||
// maxUndelayedQueryReplies queries.
|
// maxUndelayedQueryReplies queries.
|
||||||
DefaultDelayedQueryReplyInterval = 30 * time.Second
|
DefaultDelayedQueryReplyInterval = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns a human readable string describing the target syncerState.
|
// String returns a human readable string describing the target syncerState.
|
||||||
@@ -238,10 +239,11 @@ type gossipSyncer struct {
|
|||||||
|
|
||||||
cfg gossipSyncerCfg
|
cfg gossipSyncerCfg
|
||||||
|
|
||||||
// replyCount records how many query replies we've responded to. This is
|
// rateLimiter dictates the frequency with which we will reply to gossip
|
||||||
// used to determine when to start delaying responses to peers to
|
// queries from a peer. This is used to delay responses to peers to
|
||||||
// prevent DOS vulnerabilities.
|
// prevent DOS vulnerabilities if they are spamming with an unreasonable
|
||||||
replyCount int
|
// number of queries.
|
||||||
|
rateLimiter *rate.Limiter
|
||||||
|
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
|
|
||||||
@@ -259,15 +261,25 @@ func newGossiperSyncer(cfg gossipSyncerCfg) *gossipSyncer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If no parameter was specified for delayed query reply interval, set
|
// If no parameter was specified for delayed query reply interval, set
|
||||||
// to the default of 30 seconds.
|
// to the default of 5 seconds.
|
||||||
if cfg.delayedQueryReplyInterval <= 0 {
|
if cfg.delayedQueryReplyInterval <= 0 {
|
||||||
cfg.delayedQueryReplyInterval = DefaultDelayedQueryReplyInterval
|
cfg.delayedQueryReplyInterval = DefaultDelayedQueryReplyInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Construct a rate limiter that will govern how frequently we reply to
|
||||||
|
// gossip queries from this peer. The limiter will automatically adjust
|
||||||
|
// during periods of quiescence, and increase the reply interval under
|
||||||
|
// load.
|
||||||
|
interval := rate.Every(cfg.delayedQueryReplyInterval)
|
||||||
|
rateLimiter := rate.NewLimiter(
|
||||||
|
interval, cfg.maxUndelayedQueryReplies,
|
||||||
|
)
|
||||||
|
|
||||||
return &gossipSyncer{
|
return &gossipSyncer{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
gossipMsgs: make(chan lnwire.Message, 100),
|
rateLimiter: rateLimiter,
|
||||||
quit: make(chan struct{}),
|
gossipMsgs: make(chan lnwire.Message, 100),
|
||||||
|
quit: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -629,23 +641,22 @@ func (g *gossipSyncer) genChanRangeQuery() (*lnwire.QueryChannelRange, error) {
|
|||||||
// replyPeerQueries is called in response to any query by the remote peer.
|
// replyPeerQueries is called in response to any query by the remote peer.
|
||||||
// We'll examine our state and send back our best response.
|
// We'll examine our state and send back our best response.
|
||||||
func (g *gossipSyncer) replyPeerQueries(msg lnwire.Message) error {
|
func (g *gossipSyncer) replyPeerQueries(msg lnwire.Message) error {
|
||||||
|
reservation := g.rateLimiter.Reserve()
|
||||||
|
delay := reservation.Delay()
|
||||||
|
|
||||||
// If we've already replied a handful of times, we will start to delay
|
// If we've already replied a handful of times, we will start to delay
|
||||||
// responses back to the remote peer. This can help prevent DOS attacks
|
// responses back to the remote peer. This can help prevent DOS attacks
|
||||||
// where the remote peer spams us endlessly.
|
// where the remote peer spams us endlessly.
|
||||||
switch {
|
if delay > 0 {
|
||||||
case g.replyCount == g.cfg.maxUndelayedQueryReplies:
|
log.Infof("gossipSyncer(%x): rate limiting gossip replies, ",
|
||||||
log.Infof("gossipSyncer(%x): entering delayed gossip replies",
|
"responding in %s", g.peerPub[:], delay)
|
||||||
g.peerPub[:])
|
|
||||||
fallthrough
|
|
||||||
|
|
||||||
case g.replyCount > g.cfg.maxUndelayedQueryReplies:
|
|
||||||
select {
|
select {
|
||||||
case <-time.After(g.cfg.delayedQueryReplyInterval):
|
case <-time.After(delay):
|
||||||
case <-g.quit:
|
case <-g.quit:
|
||||||
return ErrGossipSyncerExiting
|
return ErrGossipSyncerExiting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.replyCount++
|
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user