Skip to content
This repository was archived by the owner on Aug 12, 2021. It is now read-only.

Commit a0bfb77

Browse files
don't cache more than 1024 entries, to avoid DoS attacks
1 parent 9ebf5ec commit a0bfb77

File tree

2 files changed

+77
-1
lines changed

2 files changed

+77
-1
lines changed

client.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ const (
2626
IPv4AndIPv6 = (IPv4 | IPv6) //< Default option.
2727
)
2828

29+
// DoS protection: we won't cache more than 1024 entries when receiving entries.
30+
var maxSentEntries = 1024
31+
2932
type clientOpts struct {
3033
listenOn IPType
3134
ifaces []net.Interface
@@ -293,6 +296,12 @@ func (c *client) mainloop(ctx context.Context, params *lookupParams) {
293296
// This is also a point to possibly stop probing actively for a
294297
// service entry.
295298
params.Entries <- e
299+
// DoS protection: don't cache more than maxSentEntries entries
300+
if len(sentEntries) >= maxSentEntries {
301+
for key := range sentEntries {
302+
delete(sentEntries, key)
303+
}
304+
}
296305
sentEntries[k] = e
297306
if !params.isBrowsing {
298307
params.disableProbing()

service_test.go

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ package zeroconf
22

33
import (
44
"context"
5+
"fmt"
56
"log"
67
"testing"
78
"time"
89

910
"github.com/pkg/errors"
1011
)
1112

12-
var (
13+
const (
1314
mdnsName = "test--xxxxxxxxxxxx"
1415
mdnsService = "_test--xxxx._tcp"
1516
mdnsSubtype = "_test--xxxx._tcp,_fancy"
@@ -163,4 +164,70 @@ func TestSubtype(t *testing.T) {
163164
t.Fatalf("Expected port is %d, but got %d", mdnsPort, result.Port)
164165
}
165166
})
167+
168+
t.Run("DoS protection", func(t *testing.T) {
169+
origMaxSentEntries := maxSentEntries
170+
maxSentEntries = 10
171+
defer func() {
172+
time.Sleep(100 * time.Millisecond) // give the mainloop some time to shut down
173+
maxSentEntries = origMaxSentEntries
174+
}()
175+
176+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
177+
defer cancel()
178+
179+
const firstName = mdnsName
180+
181+
go startMDNS(ctx, mdnsPort, firstName, mdnsSubtype, mdnsDomain)
182+
time.Sleep(time.Second)
183+
184+
resolver, err := NewResolver(nil)
185+
if err != nil {
186+
t.Fatalf("Expected create resolver success, but got %v", err)
187+
}
188+
entries := make(chan *ServiceEntry, maxSentEntries+1)
189+
received := make(chan *ServiceEntry, 10)
190+
go func() {
191+
for {
192+
select {
193+
case entry := <-entries:
194+
if entry.Instance == firstName {
195+
received <- entry
196+
}
197+
case <-ctx.Done():
198+
return
199+
}
200+
}
201+
}()
202+
if err := resolver.Browse(ctx, mdnsService, mdnsDomain, entries); err != nil {
203+
t.Fatalf("Expected browse success, but got %v", err)
204+
}
205+
select {
206+
case <-received:
207+
case <-time.NewTimer(time.Second).C:
208+
t.Fatal("expected to discover service")
209+
}
210+
211+
for i := 1; i < maxSentEntries; i++ {
212+
go startMDNS(ctx, mdnsPort, fmt.Sprintf("%s-%d", mdnsName, i), mdnsSubtype, mdnsDomain)
213+
}
214+
time.Sleep(time.Second)
215+
216+
select {
217+
case entry := <-entries:
218+
t.Fatalf("didn't expect to receive an entry, got %v", entry)
219+
default:
220+
}
221+
222+
// Announcing this service will cause the map to overflow.
223+
go startMDNS(ctx, mdnsPort, fmt.Sprintf("%s-%d", mdnsName, maxSentEntries), mdnsSubtype, mdnsDomain)
224+
225+
// wait for a re-announcement of the firstName service
226+
select {
227+
case <-received:
228+
cancel()
229+
case <-ctx.Done():
230+
t.Fatal("expected to discover service")
231+
}
232+
})
166233
}

0 commit comments

Comments
 (0)