Skip to content

Commit 632e3c9

Browse files
authored
test(autonat): dual stack 4 (#2733)
1 parent 6f30064 commit 632e3c9

5 files changed

Lines changed: 159 additions & 48 deletions

File tree

libp2p/multiaddress.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ const
448448
QUIC_V1_DNS* = mapAnd(UDP_DNS, mapEq("quic-v1"))
449449
QUIC_V1* = mapOr(QUIC_V1_DNS, QUIC_V1_IP)
450450
TCP_IP4* = mapAnd(IP4, mapEq("tcp"))
451+
TCP_IP6* = mapAnd(IP6, mapEq("tcp"))
451452
UDP_IP4* = mapAnd(IP4, mapEq("udp"))
452453
QUIC_V1_IP4* = mapAnd(UDP_IP4, mapEq("quic-v1"))
453454
UNIX* = mapEq("unix")

tests/libp2p/protocols/test_autonat_v2.nim

Lines changed: 122 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
# SPDX-License-Identifier: Apache-2.0 OR MIT
44
# Copyright (c) Status Research & Development GmbH
55

6-
import chronos, net
6+
import chronos, net, sequtils
77
import
88
../../../libp2p/[
99
switch,
10+
multiaddress,
1011
transports/tcptransport,
1112
upgrademngrs/upgrade,
1213
builders,
@@ -19,16 +20,13 @@ import
1920
import ../../tools/[unittest, crypto, switch_builder, multiaddress]
2021

2122
proc setupAutonat(
22-
srcAddrs: seq[MultiAddress] = newSeq[MultiAddress](),
23+
srcAddrs: seq[MultiAddress] = @[TcpAutoAddress],
24+
dstAddrs: seq[MultiAddress] = @[TcpAutoAddress],
2325
config: AutonatV2Config = AutonatV2Config.new(),
2426
): Future[(Switch, Switch, AutonatV2Client)] {.async.} =
25-
var srcBuilder = makeStandardSwitchBuilder(TcpAutoAddress)
26-
if srcAddrs.len > 0:
27-
srcBuilder = srcBuilder.withAddresses(srcAddrs)
28-
2927
let
30-
src = srcBuilder.build()
31-
dst = makeStandardSwitchBuilder(TcpAutoAddress).withAutonatV2Server(config).build()
28+
src = makeStandardSwitchBuilder(srcAddrs).build()
29+
dst = makeStandardSwitchBuilder(dstAddrs).withAutonatV2Server(config).build()
3230
client = AutonatV2Client.new(rng())
3331

3432
client.setup(src)
@@ -70,7 +68,7 @@ suite "AutonatV2":
7068
) == Reachable
7169

7270
asyncTest "asAutonatV2Response":
73-
let addrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/4000").get()]
71+
let addrs = @[ma("/ip4/127.0.0.1/tcp/4000")]
7472
let errorDialResp = DialResponse(
7573
status: ResponseStatus.Ok,
7674
addrIdx: Opt.none(AddrIdx),
@@ -137,8 +135,7 @@ suite "AutonatV2":
137135
# use ip address other than 127.0.0.1 for client
138136
let
139137
listenAddrs = @[
140-
MultiAddress.init("/ip4/" & checkedGetIPAddress() & "/tcp/4040").get(),
141-
MultiAddress.init("/ip4/127.0.0.1/tcp/4040").get(),
138+
ma("/ip4/" & checkedGetIPAddress() & "/tcp/4040"), ma("/ip4/127.0.0.1/tcp/4040")
142139
]
143140
reqAddrs = @[listenAddrs[0]]
144141
(src, dst, client) = await setupAutonat(srcAddrs = listenAddrs)
@@ -163,9 +160,7 @@ suite "AutonatV2":
163160
await allFutures(src.stop(), dst.stop())
164161

165162
check (
166-
await client.sendDialRequest(
167-
dst.peerInfo.peerId, @[MultiAddress.init("/ip4/1.1.1.1/tcp/4040").get()]
168-
)
163+
await client.sendDialRequest(dst.peerInfo.peerId, @[ma("/ip4/1.1.1.1/tcp/4040")])
169164
) ==
170165
AutonatV2Response(
171166
reachability: NotReachable,
@@ -174,17 +169,16 @@ suite "AutonatV2":
174169
dialStatus: Opt.some(DialStatus.EDialError),
175170
addrIdx: Opt.some(0.AddrIdx),
176171
),
177-
addrs: Opt.some(MultiAddress.init("/ip4/1.1.1.1/tcp/4040").get()),
172+
addrs: Opt.some(ma("/ip4/1.1.1.1/tcp/4040")),
178173
)
179174

180175
asyncTest "Failed DialRequest with amplification attack prevention":
181176
# use ip address other than 127.0.0.1 for client
182177
let
183178
listenAddrs = @[
184-
MultiAddress.init("/ip4/" & checkedGetIPAddress() & "/tcp/4040").get(),
185-
MultiAddress.init("/ip4/127.0.0.1/tcp/4040").get(),
179+
ma("/ip4/" & checkedGetIPAddress() & "/tcp/4040"), ma("/ip4/127.0.0.1/tcp/4040")
186180
]
187-
reqAddrs = @[MultiAddress.init("/ip4/1.1.1.1/tcp/4040").get()]
181+
reqAddrs = @[ma("/ip4/1.1.1.1/tcp/4040")]
188182
(src, dst, client) = await setupAutonat(
189183
srcAddrs = listenAddrs, config = AutonatV2Config.new(dialTimeout = 1.seconds)
190184
)
@@ -202,13 +196,122 @@ suite "AutonatV2":
202196
addrs: Opt.some(reqAddrs[0]),
203197
)
204198

199+
asyncTest "DialRequest with IPv6 addr refused by IPv4-only server":
200+
# server listens on IPv4 only, so it refuses to dial any IPv6 addr
201+
let (src, dst, client) = await setupAutonat()
202+
defer:
203+
await allFutures(src.stop(), dst.stop())
204+
205+
let reqAddrs = @[ma("/ip6/2001:db8::1/tcp/4040")]
206+
check (await client.sendDialRequest(dst.peerInfo.peerId, reqAddrs)) ==
207+
AutonatV2Response(
208+
reachability: Unknown,
209+
dialResp: DialResponse(
210+
status: EDialRefused,
211+
addrIdx: Opt.none(AddrIdx),
212+
dialStatus: Opt.none(DialStatus),
213+
),
214+
addrs: Opt.none(MultiAddress),
215+
)
216+
217+
asyncTest "DialRequest with IPv4 addr refused by IPv6-only server":
218+
# server listens on IPv6 only, so it refuses to dial any IPv4 addr
219+
let (src, dst, client) = await setupAutonat(dstAddrs = @[TcpAutoAddressIP6])
220+
defer:
221+
await allFutures(src.stop(), dst.stop())
222+
223+
let reqAddrs = @[ma("/ip4/1.1.1.1/tcp/4040")]
224+
check (await client.sendDialRequest(dst.peerInfo.peerId, reqAddrs)) ==
225+
AutonatV2Response(
226+
reachability: Unknown,
227+
dialResp: DialResponse(
228+
status: EDialRefused,
229+
addrIdx: Opt.none(AddrIdx),
230+
dialStatus: Opt.none(DialStatus),
231+
),
232+
addrs: Opt.none(MultiAddress),
233+
)
234+
235+
asyncTest "DialRequest with private IPv6 addr succeeds despite allowPrivateAddresses=false":
236+
# TODO: nim-libp2p#2710
237+
# isPrivate classifies every IPv6 address as non-private
238+
# the server therefore dials back the loopback IPv6 addr instead of refusing it
239+
let
240+
dualStackAddrs = @[TcpAutoAddressIP4, TcpAutoAddressIP6]
241+
(src, dst, client) =
242+
await setupAutonat(srcAddrs = dualStackAddrs, dstAddrs = dualStackAddrs)
243+
defer:
244+
await allFutures(src.stop(), dst.stop())
245+
246+
# request only the IPv6 listen addr of the client
247+
let reqAddrs = src.peerInfo.addrs.filterIt(TCP_IP6.match(it))
248+
check reqAddrs.len == 1
249+
250+
check (await client.sendDialRequest(dst.peerInfo.peerId, reqAddrs)) ==
251+
AutonatV2Response(
252+
reachability: Reachable,
253+
dialResp: DialResponse(
254+
status: ResponseStatus.Ok,
255+
dialStatus: Opt.some(DialStatus.Ok),
256+
addrIdx: Opt.some(0.AddrIdx),
257+
),
258+
addrs: Opt.some(reqAddrs[0]),
259+
)
260+
261+
asyncTest "Amplification attack prevention skipped when observed IPv4 addr matches a requested addr":
262+
# the requested addr matches the observed addr of the client,
263+
# so the server dials back right away without demanding dial data
264+
# dialDataSize is set above what the client accepts to prove the skip:
265+
# a DialDataRequest would have failed the request with AutonatV2Error
266+
let (src, dst, client) = await setupAutonat(
267+
config = AutonatV2Config.new(
268+
dialDataSize = (MaxAcceptedDialDataRequest + 1).uint64,
269+
allowPrivateAddresses = true,
270+
)
271+
)
272+
defer:
273+
await allFutures(src.stop(), dst.stop())
274+
275+
check (await client.sendDialRequest(dst.peerInfo.peerId, src.peerInfo.addrs)) ==
276+
AutonatV2Response(
277+
reachability: Reachable,
278+
dialResp: DialResponse(
279+
status: ResponseStatus.Ok,
280+
dialStatus: Opt.some(DialStatus.Ok),
281+
addrIdx: Opt.some(0.AddrIdx),
282+
),
283+
addrs: Opt.some(src.peerInfo.addrs[0]),
284+
)
285+
286+
asyncTest "Amplification attack prevention not skipped when observed IPv6 addr matches a requested addr":
287+
# TODO: nim-libp2p#2731
288+
# the requested addr matches the observed addr of the client,
289+
# yet the server demands dial data before the dial back
290+
# dialDataSize is set above what the client accepts to prove the demand:
291+
# the DialDataRequest fails the request with AutonatV2Error
292+
let
293+
ipv6Addrs = @[TcpAutoAddressIP6]
294+
(src, dst, client) = await setupAutonat(
295+
srcAddrs = ipv6Addrs,
296+
dstAddrs = ipv6Addrs,
297+
config = AutonatV2Config.new(
298+
dialDataSize = (MaxAcceptedDialDataRequest + 1).uint64,
299+
allowPrivateAddresses = true,
300+
),
301+
)
302+
defer:
303+
await allFutures(src.stop(), dst.stop())
304+
305+
expect(AutonatV2Error):
306+
discard await client.sendDialRequest(dst.peerInfo.peerId, src.peerInfo.addrs)
307+
205308
asyncTest "Server responding with invalid messages":
206309
let
207310
src = makeStandardSwitch()
208311
dst = makeStandardSwitch()
209312
client = AutonatV2Client.new(rng())
210313
autonatV2Mock = AutonatV2Mock.new()
211-
reqAddrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/4040").get()]
314+
reqAddrs = @[ma("/ip4/127.0.0.1/tcp/4040")]
212315

213316
client.setup(src)
214317
dst.mount(autonatV2Mock)

tests/libp2p/protocols/test_identify.nim

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@ import
2222
]
2323
import ../../tools/[unittest, crypto, multiaddress, switch_builder]
2424

25-
const
26-
IPv4Tcp = mapAnd(IP4, mapEq("tcp"))
27-
IPv6Tcp = mapAnd(IP6, mapEq("tcp"))
28-
2925
suite "Identify":
3026
teardown:
3127
checkTrackers()
@@ -198,10 +194,10 @@ suite "Identify":
198194

199195
check:
200196
# ensure both IPv4 and IPv6 addresses are used in switch.
201-
countAddressesWithPattern(switch1.peerInfo.addrs, IPv4Tcp) > 1
202-
countAddressesWithPattern(switch1.peerInfo.addrs, IPv6Tcp) > 1
203-
countAddressesWithPattern(switch2.peerInfo.addrs, IPv4Tcp) > 1
204-
countAddressesWithPattern(switch2.peerInfo.addrs, IPv6Tcp) > 1
197+
countAddressesWithPattern(switch1.peerInfo.addrs, TCP_IP4) > 1
198+
countAddressesWithPattern(switch1.peerInfo.addrs, TCP_IP6) > 1
199+
countAddressesWithPattern(switch2.peerInfo.addrs, TCP_IP4) > 1
200+
countAddressesWithPattern(switch2.peerInfo.addrs, TCP_IP6) > 1
205201

206202
# ensure all addresses are advertized.
207203
# that is, peer store will have all address of other peer
@@ -307,7 +303,7 @@ suite "Identify":
307303
await client.stop()
308304

309305
check:
310-
countAddressesWithPattern(server.peerInfo.addrs, IPv4Tcp) == 1
306+
countAddressesWithPattern(server.peerInfo.addrs, TCP_IP4) == 1
311307
countAddressesWithPattern(server.peerInfo.addrs, QUIC_V1) == 1
312308

313309
# Connect and request identify

tests/tools/switch_builder.nim

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,42 @@ import ./[crypto, multiaddress]
88

99
export builders
1010

11-
proc makeStandardSwitchBuilder*(
12-
address: MultiAddress = QuicAutoAddress
13-
): SwitchBuilder =
11+
proc makeStandardSwitchBuilder*(addresses: seq[MultiAddress]): SwitchBuilder =
1412
## Helper that creates a SwitchBuilder with standard configurations.
15-
## Transport is added automatically to match the listen `address`.
16-
17-
var b = SwitchBuilder.new().withRng(rng()).withNoise().withAddress(address)
18-
19-
# address will decide which transport to use
20-
if QUIC_V1.match(address):
13+
## Transports are added automatically to match the listen `addresses`.
14+
15+
var b = SwitchBuilder.new().withRng(rng()).withNoise().withAddresses(addresses)
16+
17+
# the listen addresses decide which transports to use, deduped across addresses
18+
var hasQuic, hasTcp, hasWs, hasMemory = false
19+
for address in addresses:
20+
if QUIC_V1.match(address):
21+
hasQuic = true
22+
elif TCP.match(address):
23+
hasTcp = true
24+
elif WebSockets.match(address):
25+
hasWs = true
26+
elif Memory.match(address):
27+
hasMemory = true
28+
else:
29+
raiseAssert "could not infer transport from address: " & $address
30+
31+
if hasQuic:
2132
b = b.withQuicTransport()
22-
elif TCP.match(address):
33+
if hasTcp:
2334
b = b.withTcpTransport().withMplex()
24-
elif WebSockets.match(address):
35+
if hasWs:
2536
b = b.withWsTransport().withMplex()
26-
elif Memory.match(address):
37+
if hasMemory:
2738
b = b.withMemoryTransport().withMplex()
28-
else:
29-
raiseAssert "could not infer transport from address"
3039

3140
b
3241

42+
proc makeStandardSwitchBuilder*(
43+
address: MultiAddress = QuicAutoAddress
44+
): SwitchBuilder =
45+
makeStandardSwitchBuilder(@[address])
46+
3347
proc makeStandardSwitch*(
3448
address: MultiAddress = QuicAutoAddress
3549
): Switch {.raises: [LPError].} =

tests/tools/test_multiaddress.nim

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,9 @@ suite "MultiAddress testing tools":
1717
MultiAddress.init("/ip4/127.0.0.1/tcp/55449").get(),
1818
MultiAddress.init("/ip4/192.168.1.24/tcp/55449").get(),
1919
]
20-
const
21-
IPv4Tcp = mapAnd(IP4, mapEq("tcp"))
22-
IPv6Tcp = mapAnd(IP6, mapEq("tcp"))
23-
IPv4Ws = mapAnd(IP4, mapEq("ws"))
20+
const IPv4Ws = mapAnd(IP4, mapEq("ws"))
2421

2522
check:
26-
countAddressesWithPattern(ma, IPv4Tcp) == 4
27-
countAddressesWithPattern(ma, IPv6Tcp) == 2
23+
countAddressesWithPattern(ma, TCP_IP4) == 4
24+
countAddressesWithPattern(ma, TCP_IP6) == 2
2825
countAddressesWithPattern(ma, IPv4Ws) == 0

0 commit comments

Comments
 (0)