Skip to content

Commit 9667cd8

Browse files
committed
add a wait before dialing different IPs
1 parent adf26bd commit 9667cd8

File tree

3 files changed

+98
-24
lines changed

3 files changed

+98
-24
lines changed

p2p/protocol/autonatv2/options.go

+21-13
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,24 @@ import "time"
44

55
// autoNATSettings is used to configure AutoNAT
66
type autoNATSettings struct {
7-
allowPrivateAddrs bool
8-
serverRPM int
9-
serverPerPeerRPM int
10-
serverDialDataRPM int
11-
dataRequestPolicy dataRequestPolicyFunc
12-
now func() time.Time
7+
allowPrivateAddrs bool
8+
serverRPM int
9+
serverPerPeerRPM int
10+
serverDialDataRPM int
11+
dataRequestPolicy dataRequestPolicyFunc
12+
now func() time.Time
13+
amplificatonAttackPreventionDialWait time.Duration
1314
}
1415

1516
func defaultSettings() *autoNATSettings {
1617
return &autoNATSettings{
17-
allowPrivateAddrs: false,
18-
// TODO: confirm rate limiting defaults
19-
serverRPM: 20,
20-
serverPerPeerRPM: 2,
21-
serverDialDataRPM: 5,
22-
dataRequestPolicy: amplificationAttackPrevention,
23-
now: time.Now,
18+
allowPrivateAddrs: false,
19+
serverRPM: 60, // 1 every second
20+
serverPerPeerRPM: 12, // 1 every 5 seconds
21+
serverDialDataRPM: 12, // 1 every 5 seconds
22+
dataRequestPolicy: amplificationAttackPrevention,
23+
amplificatonAttackPreventionDialWait: 3 * time.Second,
24+
now: time.Now,
2425
}
2526
}
2627

@@ -46,3 +47,10 @@ func allowPrivateAddrs(s *autoNATSettings) error {
4647
s.allowPrivateAddrs = true
4748
return nil
4849
}
50+
51+
func withAmplificationAttackPreventionDialWait(d time.Duration) AutoNATOption {
52+
return func(s *autoNATSettings) error {
53+
s.amplificatonAttackPreventionDialWait = d
54+
return nil
55+
}
56+
}

p2p/protocol/autonatv2/server.go

+26-9
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ import (
1515
"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2/pb"
1616
"github.com/libp2p/go-msgio/pbio"
1717

18+
"math/rand"
19+
1820
ma "github.com/multiformats/go-multiaddr"
1921
manet "github.com/multiformats/go-multiaddr/net"
20-
"golang.org/x/exp/rand"
2122
)
2223

2324
type dataRequestPolicyFunc = func(s network.Stream, dialAddr ma.Multiaddr) bool
@@ -32,7 +33,8 @@ type server struct {
3233

3334
// dialDataRequestPolicy is used to determine whether dialing the address requires receiving
3435
// dial data. It is set to amplification attack prevention by default.
35-
dialDataRequestPolicy dataRequestPolicyFunc
36+
dialDataRequestPolicy dataRequestPolicyFunc
37+
amplificatonAttackPreventionDialWait time.Duration
3638

3739
// for tests
3840
now func() time.Time
@@ -41,10 +43,11 @@ type server struct {
4143

4244
func newServer(host, dialer host.Host, s *autoNATSettings) *server {
4345
return &server{
44-
dialerHost: dialer,
45-
host: host,
46-
dialDataRequestPolicy: s.dataRequestPolicy,
47-
allowPrivateAddrs: s.allowPrivateAddrs,
46+
dialerHost: dialer,
47+
host: host,
48+
dialDataRequestPolicy: s.dataRequestPolicy,
49+
amplificatonAttackPreventionDialWait: s.amplificatonAttackPreventionDialWait,
50+
allowPrivateAddrs: s.allowPrivateAddrs,
4851
limiter: &rateLimiter{
4952
RPM: s.serverRPM,
5053
PerPeerRPM: s.serverPerPeerRPM,
@@ -81,6 +84,9 @@ func (as *server) handleDialRequest(s network.Stream) {
8184
}
8285
defer s.Scope().ReleaseMemory(maxMsgSize)
8386

87+
deadline := as.now().Add(streamTimeout)
88+
ctx, cancel := context.WithDeadline(context.Background(), deadline)
89+
defer cancel()
8490
s.SetDeadline(as.now().Add(streamTimeout))
8591
defer s.Close()
8692

@@ -183,9 +189,20 @@ func (as *server) handleDialRequest(s network.Stream) {
183189
log.Debugf("%s refused dial data request: %s", p, err)
184190
return
185191
}
192+
// wait for a bit to prevent thundering herd style attacks on a victim
193+
waitTime := time.Duration(rand.Intn(int(as.amplificatonAttackPreventionDialWait) + 1)) // the range is [0, n)
194+
t := time.NewTimer(waitTime)
195+
defer t.Stop()
196+
select {
197+
case <-ctx.Done():
198+
s.Reset()
199+
log.Debugf("rejecting request without dialing: %s %p ", p, ctx.Err())
200+
return
201+
case <-t.C:
202+
}
186203
}
187204

188-
dialStatus := as.dialBack(s.Conn().RemotePeer(), dialAddr, nonce)
205+
dialStatus := as.dialBack(ctx, s.Conn().RemotePeer(), dialAddr, nonce)
189206
msg = pb.Message{
190207
Msg: &pb.Message_DialResponse{
191208
DialResponse: &pb.DialResponse{
@@ -252,8 +269,8 @@ func readDialData(numBytes int, r io.Reader) error {
252269
return nil
253270
}
254271

255-
func (as *server) dialBack(p peer.ID, addr ma.Multiaddr, nonce uint64) pb.DialStatus {
256-
ctx, cancel := context.WithTimeout(context.Background(), dialBackDialTimeout)
272+
func (as *server) dialBack(ctx context.Context, p peer.ID, addr ma.Multiaddr, nonce uint64) pb.DialStatus {
273+
ctx, cancel := context.WithTimeout(ctx, dialBackDialTimeout)
257274
ctx = network.WithForceDirectDial(ctx, "autonatv2")
258275
as.dialerHost.Peerstore().AddAddr(p, addr, peerstore.TempAddrTTL)
259276
defer func() {

p2p/protocol/autonatv2/server_test.go

+51-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func newTestRequests(addrs []ma.Multiaddr, sendDialData bool) (reqs []Request) {
3333
}
3434

3535
func TestServerInvalidAddrsRejected(t *testing.T) {
36-
c := newAutoNAT(t, nil, allowPrivateAddrs)
36+
c := newAutoNAT(t, nil, allowPrivateAddrs, withAmplificationAttackPreventionDialWait(0))
3737
defer c.Close()
3838
defer c.host.Close()
3939

@@ -46,7 +46,6 @@ func TestServerInvalidAddrsRejected(t *testing.T) {
4646
idAndWait(t, c, an)
4747

4848
res, err := c.GetReachability(context.Background(), newTestRequests(c.host.Addrs(), true))
49-
fmt.Println(res, err)
5049
require.ErrorIs(t, err, ErrDialRefused)
5150
require.Equal(t, Result{}, res)
5251
})
@@ -151,6 +150,7 @@ func TestServerDataRequest(t *testing.T) {
151150
return false
152151
}),
153152
WithServerRateLimit(10, 10, 10),
153+
withAmplificationAttackPreventionDialWait(0),
154154
)
155155
defer an.Close()
156156
defer an.host.Close()
@@ -187,6 +187,55 @@ func TestServerDataRequest(t *testing.T) {
187187
_, err = c.GetReachability(context.Background(), []Request{{Addr: quicAddr, SendDialData: true}, {Addr: tcpAddr}})
188188
require.Error(t, err)
189189
}
190+
func TestServerDataRequestJitter(t *testing.T) {
191+
// server will skip all tcp addresses
192+
dialer := bhost.NewBlankHost(swarmt.GenSwarm(t, swarmt.OptDisableTCP))
193+
// ask for dial data for quic address
194+
an := newAutoNAT(t, dialer, allowPrivateAddrs, withDataRequestPolicy(
195+
func(s network.Stream, dialAddr ma.Multiaddr) bool {
196+
if _, err := dialAddr.ValueForProtocol(ma.P_QUIC_V1); err == nil {
197+
return true
198+
}
199+
return false
200+
}),
201+
WithServerRateLimit(10, 10, 10),
202+
withAmplificationAttackPreventionDialWait(5*time.Second),
203+
)
204+
defer an.Close()
205+
defer an.host.Close()
206+
207+
c := newAutoNAT(t, nil, allowPrivateAddrs)
208+
defer c.Close()
209+
defer c.host.Close()
210+
211+
idAndWait(t, c, an)
212+
213+
var quicAddr, tcpAddr ma.Multiaddr
214+
for _, a := range c.host.Addrs() {
215+
if _, err := a.ValueForProtocol(ma.P_QUIC_V1); err == nil {
216+
quicAddr = a
217+
} else if _, err := a.ValueForProtocol(ma.P_TCP); err == nil {
218+
tcpAddr = a
219+
}
220+
}
221+
222+
for i := 0; i < 10; i++ {
223+
st := time.Now()
224+
res, err := c.GetReachability(context.Background(), []Request{{Addr: quicAddr, SendDialData: true}, {Addr: tcpAddr}})
225+
took := time.Since(st)
226+
require.NoError(t, err)
227+
228+
require.Equal(t, Result{
229+
Addr: quicAddr,
230+
Reachability: network.ReachabilityPublic,
231+
Status: pb.DialStatus_OK,
232+
}, res)
233+
if took > 500*time.Millisecond {
234+
return
235+
}
236+
}
237+
t.Fatalf("expected server to delay at least 1 dial")
238+
}
190239

191240
func TestServerDial(t *testing.T) {
192241
an := newAutoNAT(t, nil, WithServerRateLimit(10, 10, 10), allowPrivateAddrs)

0 commit comments

Comments
 (0)