Skip to content

Commit 3227077

Browse files
authored
test(transports): dual stack 1 (#2698)
1 parent ad8ffbb commit 3227077

3 files changed

Lines changed: 190 additions & 44 deletions

File tree

tests/libp2p/transports/stream_tests.nim

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
{.used.}
55

66
import chronos, stew/byteutils, std/random
7-
import ../../../libp2p/[stream/connection, transports/transport, muxers/muxer]
7+
import
8+
../../../libp2p/[stream/connection, transports/transport, muxers/muxer, multiaddress]
89
import ../../tools/[stream, sync]
910
import ./utils
1011

@@ -57,6 +58,49 @@ template streamTransportTest*(
5758

5859
runTransportTest(transportProvider, streamProvider, addressIP6.get())
5960

61+
asyncTest "start binds both IPv4 and IPv6 addresses":
62+
if addressIP6.isNone:
63+
skip() # ipv6 not supported
64+
return
65+
66+
let server = transportProvider()
67+
await server.start(@[addressIP4, addressIP6.get()])
68+
defer:
69+
await server.stop()
70+
71+
check:
72+
server.addrs.len == 2
73+
extractPort(server.addrs.addrByFamily(IP4)) > 0
74+
extractPort(server.addrs.addrByFamily(IP6)) > 0
75+
76+
asyncTest "dual-stack server exchanges data with IPv4 and IPv6 clients":
77+
if addressIP6.isNone:
78+
skip() # ipv6 not supported
79+
return
80+
81+
proc serverStreamHandler(stream: MuxedStream) {.async: (raises: []).} =
82+
noExceptionWithStreamClose(stream):
83+
var buffer: array[clientMessage.len, byte]
84+
await stream.readExactly(addr buffer, clientMessage.len)
85+
check string.fromBytes(buffer) == clientMessage
86+
await stream.write(serverMessage)
87+
88+
proc clientStreamHandler(stream: MuxedStream) {.async: (raises: []).} =
89+
noExceptionWithStreamClose(stream):
90+
await stream.write(clientMessage)
91+
92+
var buffer: array[serverMessage.len, byte]
93+
await stream.readExactly(addr buffer, serverMessage.len)
94+
check string.fromBytes(buffer) == serverMessage
95+
96+
await dualStackStreamScenario(
97+
@[addressIP4, addressIP6.get()],
98+
transportProvider,
99+
streamProvider,
100+
serverStreamHandler,
101+
clientStreamHandler,
102+
)
103+
60104
asyncTest "read/write Lp":
61105
proc serverStreamHandler(stream: MuxedStream) {.async: (raises: []).} =
62106
noExceptionWithStreamClose(stream):
@@ -76,6 +120,35 @@ template streamTransportTest*(
76120
clientStreamHandler,
77121
)
78122

123+
asyncTest "Connection.reset aborts the initiator stream":
124+
var serverResetDone = newFuture[void]()
125+
126+
proc serverStreamHandler(stream: MuxedStream) {.async: (raises: []).} =
127+
noExceptionWithStreamClose(stream):
128+
let msg = await stream.readLp(100)
129+
check msg == fromHex("1234")
130+
await stream.reset()
131+
serverResetDone.complete()
132+
133+
proc clientStreamHandler(stream: MuxedStream) {.async: (raises: []).} =
134+
noExceptionWithStreamClose(stream):
135+
await stream.writeLp(fromHex("1234"))
136+
await serverResetDone
137+
138+
var buffer: array[1, byte]
139+
check (await stream.readOnce(addr buffer[0], 1)) == 0
140+
141+
expect LPStreamResetError:
142+
await stream.writeLp(fromHex("1234"))
143+
144+
await runSingleStreamScenario(
145+
@[addressIP4],
146+
transportProvider,
147+
streamProvider,
148+
serverStreamHandler,
149+
clientStreamHandler,
150+
)
151+
79152
asyncTest "EOF handling - first readOnce at EOF + repeated reads":
80153
var serverHandlerDone = newFuture[void]()
81154

tests/libp2p/transports/test_quic.nim

Lines changed: 61 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@
55

66
import chronos, random, stew/byteutils
77
import
8-
../../../libp2p/
9-
[transports/transport, transports/quictransport, upgrademngrs/upgrade, muxers/muxer]
8+
../../../libp2p/[
9+
transports/transport,
10+
transports/quictransport,
11+
upgrademngrs/upgrade,
12+
muxers/muxer,
13+
multiaddress,
14+
]
1015
import ../../tools/[unittest, crypto as cryptoTools, multiaddress]
1116
import ./basic_tests
1217
import ./stream_tests
@@ -50,35 +55,6 @@ suite "Quic transport":
5055
streamProvider,
5156
)
5257

53-
asyncTest "Connection.reset aborts the initiator stream":
54-
var serverResetDone = newFuture[void]()
55-
56-
proc serverStreamHandler(stream: MuxedStream) {.async: (raises: []).} =
57-
noExceptionWithStreamClose(stream):
58-
let msg = await stream.readLp(100)
59-
check msg == fromHex("1234")
60-
await stream.reset()
61-
serverResetDone.complete()
62-
63-
proc clientStreamHandler(stream: MuxedStream) {.async: (raises: []).} =
64-
noExceptionWithStreamClose(stream):
65-
await stream.writeLp(fromHex("1234"))
66-
await serverResetDone
67-
68-
var buffer: array[1, byte]
69-
check (await stream.readOnce(addr buffer[0], 1)) == 0
70-
71-
expect LPStreamResetError:
72-
await stream.writeLp(fromHex("1234"))
73-
74-
await runSingleStreamScenario(
75-
@[MultiAddress.init(addressIP4).get()],
76-
quicTransProvider,
77-
streamProvider,
78-
serverStreamHandler,
79-
clientStreamHandler,
80-
)
81-
8258
asyncTest "transport e2e - invalid cert - server":
8359
let server = await createQuicTransport(isServer = true, withInvalidCert = true)
8460
asyncSpawn createServerAcceptConn(server)()
@@ -131,9 +107,9 @@ suite "Quic transport":
131107
discard await client.dial("", server.addrs[0], Opt.some(wrongPeerId))
132108

133109
asyncTest "should allow multiple local addresses":
134-
let key = PrivateKey.random(ECDSA, rng()).tryGet()
135-
let server = QuicTransport.new(Upgrade(), key)
136-
await server.start(@[QuicAutoAddress, QuicAutoAddress])
110+
let server = await createQuicTransport(
111+
isServer = true, addresses = @[QuicAutoAddress, QuicAutoAddress]
112+
)
137113
defer:
138114
await server.stop()
139115

@@ -183,9 +159,9 @@ suite "Quic transport":
183159
extractPort(serverConn.observedAddr.get()) == clientListenPort
184160

185161
asyncTest "dial uses dial-only endpoint with multiple listener matches":
186-
let key = PrivateKey.random(ECDSA, rng()).tryGet()
187-
let client = QuicTransport.new(Upgrade(), key)
188-
await client.start(@[QuicAutoAddress, QuicAutoAddress])
162+
let client = await createQuicTransport(
163+
isServer = true, addresses = @[QuicAutoAddress, QuicAutoAddress]
164+
)
189165
let server = await createQuicTransport(isServer = true)
190166
defer:
191167
await allFutures(client.stop(), server.stop())
@@ -227,6 +203,54 @@ suite "Quic transport":
227203
extractPort(serverConn.observedAddr.get()) ==
228204
extractPort(clientConn.localAddr.get())
229205

206+
asyncTest "dual-stack dialer reuses the matching-family listener":
207+
# Dialing from the listener endpoint makes the remote observe the listen port.
208+
# Port reuse and DCUtR hole punching depend on that.
209+
let dialer = await createQuicTransport(
210+
isServer = true, addresses = @[QuicAutoAddressIP4, QuicAutoAddressIP6]
211+
)
212+
let server =
213+
await createQuicTransport(isServer = true, addresses = @[QuicAutoAddressIP6])
214+
defer:
215+
await allFutures(dialer.stop(), server.stop())
216+
217+
let dialerIPv6Port = extractPort(dialer.addrs.addrByFamily(IP6))
218+
219+
let acceptFut = server.accept()
220+
let dialerConn = await dialer.dial("", server.addrs[0])
221+
let serverConn = await acceptFut
222+
defer:
223+
await allFutures(dialerConn.close(), serverConn.close())
224+
225+
check:
226+
serverConn.observedAddr.isSome()
227+
# same port as the IPv6 listener means that listener was reused
228+
extractPort(serverConn.observedAddr.get()) == dialerIPv6Port
229+
230+
asyncTest "dial uses an IPv6 dial-only endpoint when only an IPv4 listener exists":
231+
# An IPv4 socket cannot carry an IPv6 dial, so the IPv4 listener cannot be
232+
# reused and a separate IPv6 dial-only endpoint has to be opened.
233+
let dialer =
234+
await createQuicTransport(isServer = true, addresses = @[QuicAutoAddressIP4])
235+
let server =
236+
await createQuicTransport(isServer = true, addresses = @[QuicAutoAddressIP6])
237+
defer:
238+
await allFutures(dialer.stop(), server.stop())
239+
240+
let dialerIPv4Port = extractPort(dialer.addrs[0])
241+
242+
let acceptFut = server.accept()
243+
let dialerConn = await dialer.dial("", server.addrs[0])
244+
let serverConn = await acceptFut
245+
defer:
246+
await allFutures(dialerConn.close(), serverConn.close())
247+
248+
check:
249+
dialer.addrs.len == 1
250+
serverConn.observedAddr.isSome()
251+
# a different port than the IPv4 listener means a separate endpoint was used
252+
extractPort(serverConn.observedAddr.get()) != dialerIPv4Port
253+
230254
asyncTest "server not accepting":
231255
let server = await createQuicTransport(isServer = true)
232256
# intentionally not calling createServerAcceptConn as server should not accept

tests/libp2p/transports/utils.nim

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ proc createQuicTransport*(
8080
isServer: bool = false,
8181
withInvalidCert: bool = false,
8282
privateKey: Opt[PrivateKey] = Opt.none(PrivateKey),
83-
address: MultiAddress = QuicAutoAddress,
83+
addresses: seq[MultiAddress] = @[QuicAutoAddress],
8484
): Future[QuicTransport] {.async.} =
8585
let key =
8686
if privateKey.isNone:
@@ -95,8 +95,7 @@ proc createQuicTransport*(
9595
QuicTransport.new(Upgrade(), key)
9696

9797
if isServer: # servers are started because they need to listen
98-
let ma = @[address]
99-
await trans.start(ma)
98+
await trans.start(addresses)
10099

101100
return trans
102101

@@ -109,6 +108,13 @@ type StreamProvider* =
109108

110109
type StreamHandler* = proc(stream: MuxedStream) {.async: (raises: []).}
111110

111+
proc addrByFamily*(addrs: seq[MultiAddress], family: MaPattern): MultiAddress =
112+
## First address whose leading ip component matches `family` (IP4 or IP6).
113+
for a in addrs:
114+
if family.matchPartial(a):
115+
return a
116+
raiseAssert "no address for the requested family"
117+
112118
proc extractPort*(ma: MultiAddress): int =
113119
var codec =
114120
if isTcpTransport(ma) or isWsTransport(ma):
@@ -148,15 +154,15 @@ proc serverHandlerSingleStream*(
148154
except CatchableError as exc:
149155
raiseAssert "should not fail: " & exc.msg
150156

151-
proc clientRunSingleStream*(
152-
server: Transport,
157+
proc clientRunSingleStreamTo*(
158+
address: MultiAddress,
153159
transportProvider: TransportProvider,
154160
streamProvider: StreamProvider,
155161
handler: StreamHandler,
156162
) {.async: (raises: []).} =
157163
try:
158164
let client = transportProvider()
159-
let conn = await client.dial("", server.addrs[0])
165+
let conn = await client.dial("", address)
160166
let muxer = streamProvider(conn)
161167

162168
let stream = await muxer.newStream()
@@ -168,6 +174,16 @@ proc clientRunSingleStream*(
168174
except CatchableError as exc:
169175
raiseAssert "should not fail: " & exc.msg
170176

177+
proc clientRunSingleStream*(
178+
server: Transport,
179+
transportProvider: TransportProvider,
180+
streamProvider: StreamProvider,
181+
handler: StreamHandler,
182+
) {.async: (raises: []).} =
183+
await clientRunSingleStreamTo(
184+
server.addrs[0], transportProvider, streamProvider, handler
185+
)
186+
171187
proc runSingleStreamScenario*(
172188
multiAddress: seq[MultiAddress],
173189
transportProvider: TransportProvider,
@@ -186,6 +202,39 @@ proc runSingleStreamScenario*(
186202
await allFutures(clientTask, serverTask)
187203
await server.stop()
188204

205+
proc dualStackStreamScenario*(
206+
listenAddrs: seq[MultiAddress],
207+
transportProvider: TransportProvider,
208+
streamProvider: StreamProvider,
209+
serverStreamHandler: StreamHandler,
210+
clientStreamHandler: StreamHandler,
211+
) {.async: (raises: [CancelledError, LPError]).} =
212+
## Start one server on `listenAddrs`, then dial it once per address family from
213+
## a fresh client, serving each accepted connection with `serverStreamHandler`.
214+
let server = transportProvider()
215+
await server.start(listenAddrs)
216+
defer:
217+
await server.stop()
218+
219+
# dial the server's resolved address of each family, not the wildcard input
220+
let targets = @[server.addrs.addrByFamily(IP4), server.addrs.addrByFamily(IP6)]
221+
222+
# accept() is single-consumer, so serve the clients one after another
223+
proc serveAll() {.async: (raises: []).} =
224+
for _ in targets:
225+
await serverHandlerSingleStream(server, streamProvider, serverStreamHandler)
226+
227+
let serverTask = serveAll()
228+
var clientTasks: seq[Future[void]]
229+
for target in targets:
230+
clientTasks.add(
231+
clientRunSingleStreamTo(
232+
target, transportProvider, streamProvider, clientStreamHandler
233+
)
234+
)
235+
await allFutures(clientTasks)
236+
await serverTask
237+
189238
proc countTransitions*(readOrder: seq[byte]): int =
190239
var transitions = 0
191240
for i in 1 ..< readOrder.len:

0 commit comments

Comments
 (0)