Skip to content

Commit 0936035

Browse files
authored
Improve IDONTWANT Flood Protection (#590)
In this PR we add in a new config parameter called `MaxIDontWantLength` which would be very similarly used as `MaxIHaveLength` has been used of `IHAVE` messgaes . This parameter has been set as the value of `10` now. The main purpose is to bring how IDONTWANT messages are handled in line with how IHAVE have been handled. We add the relevant changes to the `handleIDontWant` method along with adding in a new regression test for this check.
1 parent 3536508 commit 0936035

File tree

2 files changed

+63
-0
lines changed

2 files changed

+63
-0
lines changed

gossipsub.go

+15
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ var (
6868
GossipSubGraftFloodThreshold = 10 * time.Second
6969
GossipSubMaxIHaveLength = 5000
7070
GossipSubMaxIHaveMessages = 10
71+
GossipSubMaxIDontWantLength = 10
7172
GossipSubMaxIDontWantMessages = 1000
7273
GossipSubIWantFollowupTime = 3 * time.Second
7374
GossipSubIDontWantMessageThreshold = 1024 // 1KB
@@ -218,6 +219,10 @@ type GossipSubParams struct {
218219
// MaxIHaveMessages is the maximum number of IHAVE messages to accept from a peer within a heartbeat.
219220
MaxIHaveMessages int
220221

222+
// MaxIDontWantLength is the maximum number of messages to include in an IDONTWANT message. Also controls
223+
// the maximum number of IDONTWANT ids we will accept to protect against IDONTWANT floods. This value
224+
// should be adjusted if your system anticipates a larger amount than specified per heartbeat.
225+
MaxIDontWantLength int
221226
// MaxIDontWantMessages is the maximum number of IDONTWANT messages to accept from a peer within a heartbeat.
222227
MaxIDontWantMessages int
223228

@@ -303,6 +308,7 @@ func DefaultGossipSubParams() GossipSubParams {
303308
GraftFloodThreshold: GossipSubGraftFloodThreshold,
304309
MaxIHaveLength: GossipSubMaxIHaveLength,
305310
MaxIHaveMessages: GossipSubMaxIHaveMessages,
311+
MaxIDontWantLength: GossipSubMaxIDontWantLength,
306312
MaxIDontWantMessages: GossipSubMaxIDontWantMessages,
307313
IWantFollowupTime: GossipSubIWantFollowupTime,
308314
IDontWantMessageThreshold: GossipSubIDontWantMessageThreshold,
@@ -1009,9 +1015,18 @@ func (gs *GossipSubRouter) handleIDontWant(p peer.ID, ctl *pb.ControlMessage) {
10091015
}
10101016
gs.peerdontwant[p]++
10111017

1018+
totalUnwantedIds := 0
10121019
// Remember all the unwanted message ids
1020+
mainIDWLoop:
10131021
for _, idontwant := range ctl.GetIdontwant() {
10141022
for _, mid := range idontwant.GetMessageIDs() {
1023+
// IDONTWANT flood protection
1024+
if totalUnwantedIds >= gs.params.MaxIDontWantLength {
1025+
log.Debugf("IDONWANT: peer %s has advertised too many ids (%d) within this message; ignoring", p, totalUnwantedIds)
1026+
break mainIDWLoop
1027+
}
1028+
1029+
totalUnwantedIds++
10151030
gs.unwanted[p][computeChecksum(mid)] = gs.params.IDontWantMessageTTL
10161031
}
10171032
}

gossipsub_spam_test.go

+48
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"crypto/rand"
66
"encoding/base64"
7+
"fmt"
78
"strconv"
89
"sync"
910
"testing"
@@ -891,6 +892,53 @@ func TestGossipsubAttackSpamIDONTWANT(t *testing.T) {
891892
<-ctx.Done()
892893
}
893894

895+
func TestGossipsubHandleIDontwantSpam(t *testing.T) {
896+
ctx, cancel := context.WithCancel(context.Background())
897+
defer cancel()
898+
hosts := getDefaultHosts(t, 2)
899+
900+
msgID := func(pmsg *pb.Message) string {
901+
// silly content-based test message-ID: just use the data as whole
902+
return base64.URLEncoding.EncodeToString(pmsg.Data)
903+
}
904+
905+
psubs := make([]*PubSub, 2)
906+
psubs[0] = getGossipsub(ctx, hosts[0], WithMessageIdFn(msgID))
907+
psubs[1] = getGossipsub(ctx, hosts[1], WithMessageIdFn(msgID))
908+
909+
connect(t, hosts[0], hosts[1])
910+
911+
topic := "foobar"
912+
for _, ps := range psubs {
913+
_, err := ps.Subscribe(topic)
914+
if err != nil {
915+
t.Fatal(err)
916+
}
917+
}
918+
exceededIDWLength := GossipSubMaxIDontWantLength + 1
919+
var idwIds []string
920+
for i := 0; i < exceededIDWLength; i++ {
921+
idwIds = append(idwIds, fmt.Sprintf("idontwant-%d", i))
922+
}
923+
rPid := hosts[1].ID()
924+
ctrlMessage := &pb.ControlMessage{Idontwant: []*pb.ControlIDontWant{{MessageIDs: idwIds}}}
925+
grt := psubs[0].rt.(*GossipSubRouter)
926+
grt.handleIDontWant(rPid, ctrlMessage)
927+
928+
if grt.peerdontwant[rPid] != 1 {
929+
t.Errorf("Wanted message count of %d but received %d", 1, grt.peerdontwant[rPid])
930+
}
931+
mid := fmt.Sprintf("idontwant-%d", GossipSubMaxIDontWantLength-1)
932+
if _, ok := grt.unwanted[rPid][computeChecksum(mid)]; !ok {
933+
t.Errorf("Desired message id was not stored in the unwanted map: %s", mid)
934+
}
935+
936+
mid = fmt.Sprintf("idontwant-%d", GossipSubMaxIDontWantLength)
937+
if _, ok := grt.unwanted[rPid][computeChecksum(mid)]; ok {
938+
t.Errorf("Unwanted message id was stored in the unwanted map: %s", mid)
939+
}
940+
}
941+
894942
type mockGSOnRead func(writeMsg func(*pb.RPC), irpc *pb.RPC)
895943

896944
func newMockGS(ctx context.Context, t *testing.T, attacker host.Host, onReadMsg mockGSOnRead) {

0 commit comments

Comments
 (0)