1
1
package discovery
2
2
3
3
import (
4
+ "context"
4
5
"errors"
5
6
"sync"
6
7
"sync/atomic"
@@ -11,6 +12,7 @@ import (
11
12
"github.com/lightningnetwork/lnd/lnwire"
12
13
"github.com/lightningnetwork/lnd/routing/route"
13
14
"github.com/lightningnetwork/lnd/ticker"
15
+ "golang.org/x/time/rate"
14
16
)
15
17
16
18
const (
@@ -25,6 +27,21 @@ const (
25
27
26
28
// filterSemaSize is the capacity of gossipFilterSema.
27
29
filterSemaSize = 5
30
+
31
+ // DefaultMsgBytesBurst is the allotted burst in bytes we'll permit.
32
+ // This is the most that can be sent in a given go. Requests beyond
33
+ // this, will block indefinitely. Once tokens (bytes are depleted),
34
+ // they'll be refilled at the DefaultMsgBytesPerSecond rate.
35
+ DefaultMsgBytesBurst = 2 * 100 * 1_024
36
+
37
+ // DefaultMsgBytesPerSecond is the max bytes/s we'll permit for outgoing
38
+ // messages. Once tokens (bytes) have been taken from the bucket,
39
+ // they'll be refilled at this rate.
40
+ DefaultMsgBytesPerSecond = 100 * 1_024
41
+
42
+ // assumedMsgSize is the assumed size of a message if we can't compute
43
+ // its serialized size. This comes out to 1 KB.
44
+ assumedMsgSize = 1_024
28
45
)
29
46
30
47
var (
@@ -110,6 +127,15 @@ type SyncManagerCfg struct {
110
127
// updates for a channel and returns true if the channel should be
111
128
// considered a zombie based on these timestamps.
112
129
IsStillZombieChannel func (time.Time , time.Time ) bool
130
+
131
+ // AllotedMsgBytesPerSecond is the allotted bandwidth rate, expressed in
132
+ // bytes/second that the gossip manager can consume. Once we exceed this
133
+ // rate, message sending will block until we're below the rate.
134
+ AllotedMsgBytesPerSecond uint64
135
+
136
+ // AllotedMsgBytesBurst is the amount of burst bytes we'll permit, if
137
+ // we've exceeded the hard upper limit.
138
+ AllotedMsgBytesBurst uint64
113
139
}
114
140
115
141
// SyncManager is a subsystem of the gossiper that manages the gossip syncers
@@ -168,6 +194,12 @@ type SyncManager struct {
168
194
// queries.
169
195
gossipFilterSema chan struct {}
170
196
197
+ // rateLimiter dictates the frequency with which we will reply to gossip
198
+ // queries from a peer. This is used to delay responses to peers to
199
+ // prevent DOS vulnerabilities if they are spamming with an unreasonable
200
+ // number of queries.
201
+ rateLimiter * rate.Limiter
202
+
171
203
wg sync.WaitGroup
172
204
quit chan struct {}
173
205
}
@@ -180,8 +212,25 @@ func newSyncManager(cfg *SyncManagerCfg) *SyncManager {
180
212
filterSema <- struct {}{}
181
213
}
182
214
215
+ bytesPerSecond := cfg .AllotedMsgBytesPerSecond
216
+ if bytesPerSecond == 0 {
217
+ bytesPerSecond = DefaultMsgBytesPerSecond
218
+ }
219
+
220
+ bytesBurst := cfg .AllotedMsgBytesBurst
221
+ if bytesBurst == 0 {
222
+ bytesBurst = DefaultMsgBytesBurst
223
+ }
224
+
225
+ // We'll use this rate limiter to limit our total outbound bandwidth for
226
+ // gossip queries peers.
227
+ rateLimiter := rate .NewLimiter (
228
+ rate .Limit (bytesPerSecond ), int (bytesBurst ),
229
+ )
230
+
183
231
return & SyncManager {
184
232
cfg : * cfg ,
233
+ rateLimiter : rateLimiter ,
185
234
newSyncers : make (chan * newSyncer ),
186
235
staleSyncers : make (chan * staleSyncer ),
187
236
activeSyncers : make (
@@ -494,6 +543,95 @@ func (m *SyncManager) isPinnedSyncer(s *GossipSyncer) bool {
494
543
return isPinnedSyncer
495
544
}
496
545
546
+ // deriveRateLimitReservation will take the current message and derive a
547
+ // reservation that can be used to wait on the rate limiter.
548
+ func (m * SyncManager ) deriveRateLimitReservation (msg lnwire.Message ,
549
+ ) (* rate.Reservation , error ) {
550
+
551
+ var (
552
+ msgSize uint32
553
+ err error
554
+ )
555
+
556
+ // Figure out the serialized size of the message. If we can't easily
557
+ // compute it, then we'll used the assumed msg size.
558
+ if sMsg , ok := msg .(lnwire.SizeableMessage ); ok {
559
+ msgSize , err = sMsg .SerializedSize ()
560
+ if err != nil {
561
+ return nil , err
562
+ }
563
+ } else {
564
+ log .Warnf ("Unable to compute serialized size of %T" , msg )
565
+
566
+ msgSize = assumedMsgSize
567
+ }
568
+
569
+ return m .rateLimiter .ReserveN (time .Now (), int (msgSize )), nil
570
+ }
571
+
572
+ // waitMsgDelay takes a delay, and waits until it has finished.
573
+ func (m * SyncManager ) waitMsgDelay (ctx context.Context , peerPub [33 ]byte ,
574
+ limitReservation * rate.Reservation ) error {
575
+
576
+ // If we've already replied a handful of times, we will start to delay
577
+ // responses back to the remote peer. This can help prevent DOS attacks
578
+ // where the remote peer spams us endlessly.
579
+ //
580
+ // We skip checking for reservation.OK() here, as during config
581
+ // validation, we ensure that the burst is enough for a single message
582
+ // to be sent.
583
+ delay := limitReservation .Delay ()
584
+ if delay > 0 {
585
+ log .Infof ("GossipSyncer(%x): rate limiting gossip replies, " +
586
+ "responding in %s" , peerPub , delay )
587
+
588
+ select {
589
+ case <- time .After (delay ):
590
+
591
+ case <- ctx .Done ():
592
+ limitReservation .Cancel ()
593
+
594
+ return ErrGossipSyncerExiting
595
+
596
+ case <- m .quit :
597
+ limitReservation .Cancel ()
598
+
599
+ return ErrGossipSyncerExiting
600
+ }
601
+ }
602
+
603
+ return nil
604
+ }
605
+
606
+ // maybeRateLimitMsg takes a message, and may wait a period of time to rate
607
+ // limit the msg.
608
+ func (m * SyncManager ) maybeRateLimitMsg (ctx context.Context , peerPub [33 ]byte ,
609
+ msg lnwire.Message ) error {
610
+
611
+ delay , err := m .deriveRateLimitReservation (msg )
612
+ if err != nil {
613
+ return nil
614
+ }
615
+
616
+ return m .waitMsgDelay (ctx , peerPub , delay )
617
+ }
618
+
619
+ // sendMessages sends a set of messages to the remote peer.
620
+ func (m * SyncManager ) sendMessages (ctx context.Context , sync bool ,
621
+ peer lnpeer.Peer , nodeID route.Vertex , msgs ... lnwire.Message ) error {
622
+
623
+ for _ , msg := range msgs {
624
+ if err := m .maybeRateLimitMsg (ctx , nodeID , msg ); err != nil {
625
+ return err
626
+ }
627
+ if err := peer .SendMessageLazy (sync , msg ); err != nil {
628
+ return err
629
+ }
630
+ }
631
+
632
+ return nil
633
+ }
634
+
497
635
// createGossipSyncer creates the GossipSyncer for a newly connected peer.
498
636
func (m * SyncManager ) createGossipSyncer (peer lnpeer.Peer ) * GossipSyncer {
499
637
nodeID := route .Vertex (peer .PubKey ())
@@ -507,20 +645,22 @@ func (m *SyncManager) createGossipSyncer(peer lnpeer.Peer) *GossipSyncer {
507
645
encodingType : encoding ,
508
646
chunkSize : encodingTypeToChunkSize [encoding ],
509
647
batchSize : requestBatchSize ,
510
- sendToPeer : func (msgs ... lnwire.Message ) error {
511
- return peer .SendMessageLazy (false , msgs ... )
648
+ sendToPeer : func (ctx context.Context ,
649
+ msgs ... lnwire.Message ) error {
650
+
651
+ return m .sendMessages (ctx , false , peer , nodeID , msgs ... )
512
652
},
513
- sendToPeerSync : func (msgs ... lnwire.Message ) error {
514
- return peer .SendMessageLazy (true , msgs ... )
653
+ sendToPeerSync : func (ctx context.Context ,
654
+ msgs ... lnwire.Message ) error {
655
+
656
+ return m .sendMessages (ctx , true , peer , nodeID , msgs ... )
515
657
},
516
- ignoreHistoricalFilters : m .cfg .IgnoreHistoricalFilters ,
517
- maxUndelayedQueryReplies : DefaultMaxUndelayedQueryReplies ,
518
- delayedQueryReplyInterval : DefaultDelayedQueryReplyInterval ,
519
- bestHeight : m .cfg .BestHeight ,
520
- markGraphSynced : m .markGraphSynced ,
521
- maxQueryChanRangeReplies : maxQueryChanRangeReplies ,
522
- noTimestampQueryOption : m .cfg .NoTimestampQueries ,
523
- isStillZombieChannel : m .cfg .IsStillZombieChannel ,
658
+ ignoreHistoricalFilters : m .cfg .IgnoreHistoricalFilters ,
659
+ bestHeight : m .cfg .BestHeight ,
660
+ markGraphSynced : m .markGraphSynced ,
661
+ maxQueryChanRangeReplies : maxQueryChanRangeReplies ,
662
+ noTimestampQueryOption : m .cfg .NoTimestampQueries ,
663
+ isStillZombieChannel : m .cfg .IsStillZombieChannel ,
524
664
}, m .gossipFilterSema )
525
665
526
666
// Gossip syncers are initialized by default in a PassiveSync type
0 commit comments