Skip to content

Commit cfe2be4

Browse files
authored
fix: do not close relay connection after WebRTC upgrade (#3205)
We try to open a connection without informing the connection manager, but the upgrader emits a `connection:open` event which means the connection manager finds out about the connection anyway, so don't try to be clever with the connection and instead submit it to the same connection management rules as ever other connection.
1 parent c1a8640 commit cfe2be4

File tree

7 files changed

+320
-169
lines changed

7 files changed

+320
-169
lines changed

packages/integration-tests/.aegir.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,51 @@ export default {
6969
}
7070
})
7171

72+
const libp2pLimitedRelay = await createLibp2p({
73+
logger: prefixLogger('limited-relay'),
74+
connectionManager: {
75+
inboundConnectionThreshold: Infinity
76+
},
77+
addresses: {
78+
listen: [
79+
'/ip4/127.0.0.1/tcp/0/ws',
80+
'/ip4/127.0.0.1/tcp/0/ws',
81+
'/ip4/0.0.0.0/udp/0/webrtc-direct',
82+
'/ip4/0.0.0.0/udp/0/webrtc-direct'
83+
]
84+
},
85+
transports: [
86+
circuitRelayTransport(),
87+
webSockets(),
88+
webRTCDirect()
89+
],
90+
streamMuxers: [
91+
yamux(),
92+
() => mockMuxer(),
93+
mplex()
94+
],
95+
connectionEncrypters: [
96+
noise(),
97+
plaintext()
98+
],
99+
services: {
100+
identify: identify(),
101+
relay: circuitRelayServer({
102+
reservations: {
103+
maxReservations: Infinity
104+
}
105+
}),
106+
echo: echo({
107+
maxInboundStreams: 5
108+
}),
109+
ping: ping()
110+
},
111+
connectionMonitor: {
112+
enabled: false
113+
}
114+
})
115+
116+
72117
const goLibp2pRelay = await createGoLibp2pRelay()
73118
const wsAddresses = libp2p.getMultiaddrs().filter(ma => WebSockets.exactMatch(ma))
74119
const webRTCDirectPorts = new Set()
@@ -88,16 +133,19 @@ export default {
88133

89134
return WebRTCDirect.exactMatch(ma)
90135
})
136+
const limitedWsAddresses = libp2pLimitedRelay.getMultiaddrs().filter(ma => WebSockets.exactMatch(ma))
91137

92138
return {
93139
libp2p,
94140
goLibp2pRelay,
141+
libp2pLimitedRelay,
95142
env: {
96143
RELAY_MULTIADDR: wsAddresses[0],
97144
RELAY_WS_MULTIADDR_0: wsAddresses[0],
98145
RELAY_WS_MULTIADDR_1: wsAddresses[1],
99146
RELAY_WEBRTC_DIRECT_MULTIADDR_0: webRTCDirectAddresses[0],
100147
RELAY_WEBRTC_DIRECT_MULTIADDR_1: webRTCDirectAddresses[1],
148+
LIMITED_RELAY_MULTIADDR: limitedWsAddresses[0],
101149
GO_RELAY_PEER: goLibp2pRelay.peerId,
102150
GO_RELAY_MULTIADDRS: goLibp2pRelay.multiaddrs,
103151
GO_RELAY_APIADDR: goLibp2pRelay.apiAddr
@@ -107,6 +155,7 @@ export default {
107155
after: async (_, before) => {
108156
await before.libp2p.stop()
109157
await before.goLibp2pRelay.proc.kill()
158+
await before.libp2pLimitedRelay.stop()
110159
}
111160
}
112161
}

packages/integration-tests/test/circuit-relay.spec.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { identify } from '@libp2p/identify'
77
import { mplex } from '@libp2p/mplex'
88
import { plaintext } from '@libp2p/plaintext'
99
import { webSockets } from '@libp2p/websockets'
10-
import { Circuit } from '@multiformats/mafmt'
10+
import { Circuit } from '@multiformats/multiaddr-matcher'
1111
import { expect } from 'aegir/chai'
1212
import { createLibp2p } from 'libp2p'
1313
import { pEvent } from 'p-event'
@@ -110,4 +110,66 @@ describe('circuit-relay', () => {
110110
// connection should have been to remote
111111
expect(event.detail.remotePeer.toString()).to.equal(remote.peerId.toString())
112112
})
113+
114+
it('should deduplicate relayed connections', async () => {
115+
remote = await createLibp2p({
116+
addresses: {
117+
listen: [
118+
`${process.env.LIMITED_RELAY_MULTIADDR}/p2p-circuit`
119+
]
120+
},
121+
transports: [
122+
circuitRelayTransport(),
123+
webSockets()
124+
],
125+
streamMuxers: [
126+
yamux()
127+
],
128+
connectionEncrypters: [
129+
plaintext()
130+
],
131+
connectionGater: {
132+
denyDialMultiaddr: () => false
133+
},
134+
services: {
135+
identify: identify()
136+
}
137+
})
138+
139+
local = await createLibp2p({
140+
transports: [
141+
circuitRelayTransport(),
142+
webSockets()
143+
],
144+
streamMuxers: [
145+
yamux()
146+
],
147+
connectionEncrypters: [
148+
plaintext()
149+
],
150+
connectionGater: {
151+
denyDialMultiaddr: () => false
152+
},
153+
services: {
154+
identify: identify()
155+
}
156+
})
157+
158+
const relayedAddress = remote.getMultiaddrs().filter(ma => Circuit.exactMatch(ma))[0]
159+
160+
if (relayedAddress == null) {
161+
throw new Error('Did not have relay address')
162+
}
163+
164+
const limitedConn = await local.dial(relayedAddress)
165+
expect(limitedConn).to.have.property('limits').that.is.ok()
166+
expect(Circuit.exactMatch(limitedConn.remoteAddr)).to.be.true()
167+
168+
const otherLimitedConn = await local.dial(relayedAddress)
169+
expect(otherLimitedConn).to.have.property('limits').that.is.ok()
170+
expect(Circuit.exactMatch(otherLimitedConn.remoteAddr)).to.be.true()
171+
172+
expect(limitedConn).to.equal(otherLimitedConn)
173+
expect(limitedConn).to.have.property('id', otherLimitedConn.id)
174+
})
113175
})

packages/integration-tests/test/webrtc-private-to-private.spec.ts

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,29 @@
44
import { yamux } from '@chainsafe/libp2p-yamux'
55
import { circuitRelayTransport } from '@libp2p/circuit-relay-v2'
66
import { identify } from '@libp2p/identify'
7+
import { stop } from '@libp2p/interface'
78
import { plaintext } from '@libp2p/plaintext'
8-
import { webRTC } from '@libp2p/webrtc'
9+
import { webRTC, webRTCDirect } from '@libp2p/webrtc'
910
import { webSockets } from '@libp2p/websockets'
10-
import { WebRTC } from '@multiformats/multiaddr-matcher'
11+
import { Circuit, WebRTC } from '@multiformats/multiaddr-matcher'
1112
import { expect } from 'aegir/chai'
1213
import { createLibp2p } from 'libp2p'
14+
import { isWebWorker } from 'wherearewe'
1315
import type { Libp2p } from '@libp2p/interface'
1416

1517
describe('webrtc private-to-private', () => {
18+
if (isWebWorker) {
19+
it.skip('tests are skipped because WebWorkers cannot use WebRTC', () => {
20+
21+
})
22+
return
23+
}
24+
1625
let local: Libp2p
26+
let remote: Libp2p
1727

1828
afterEach(async () => {
19-
await local?.stop()
29+
await stop(local, remote)
2030
})
2131

2232
it('should listen on two webrtc addresses', async () => {
@@ -53,4 +63,68 @@ describe('webrtc private-to-private', () => {
5363
expect(local.getMultiaddrs().filter(WebRTC.exactMatch))
5464
.to.have.property('length').that.is.greaterThan(1)
5565
})
66+
67+
it('should dial WebRTC with existing relayed connection', async () => {
68+
remote = await createLibp2p({
69+
addresses: {
70+
listen: [
71+
`${process.env.LIMITED_RELAY_MULTIADDR}/p2p-circuit`,
72+
'/webrtc'
73+
]
74+
},
75+
transports: [
76+
circuitRelayTransport(),
77+
webSockets(),
78+
webRTC()
79+
],
80+
streamMuxers: [
81+
yamux()
82+
],
83+
connectionEncrypters: [
84+
plaintext()
85+
],
86+
connectionGater: {
87+
denyDialMultiaddr: () => false
88+
},
89+
services: {
90+
identify: identify()
91+
}
92+
})
93+
94+
local = await createLibp2p({
95+
transports: [
96+
circuitRelayTransport(),
97+
webSockets(),
98+
webRTC(),
99+
webRTCDirect()
100+
],
101+
streamMuxers: [
102+
yamux()
103+
],
104+
connectionEncrypters: [
105+
plaintext()
106+
],
107+
connectionGater: {
108+
denyDialMultiaddr: () => false
109+
},
110+
services: {
111+
identify: identify()
112+
}
113+
})
114+
115+
const relayedAddress = remote.getMultiaddrs().filter(ma => Circuit.exactMatch(ma)).pop()
116+
const webRTCAddress = remote.getMultiaddrs().filter(ma => WebRTC.exactMatch(ma)).pop()
117+
118+
if (relayedAddress == null || webRTCAddress == null) {
119+
throw new Error('Did not have relay and/or WebRTC address')
120+
}
121+
122+
const limitedConn = await local.dial(relayedAddress)
123+
expect(limitedConn).to.have.property('limits').that.is.ok()
124+
expect(WebRTC.exactMatch(limitedConn.remoteAddr)).to.be.false()
125+
126+
const webRTCConn = await local.dial(webRTCAddress)
127+
expect(webRTCConn).to.have.property('limits').that.is.not.ok()
128+
expect(WebRTC.exactMatch(webRTCConn.remoteAddr)).to.be.true()
129+
})
56130
})

packages/libp2p/src/connection-manager/dial-queue.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,17 @@ export class DialQueue {
136136
async dial (peerIdOrMultiaddr: PeerId | Multiaddr | Multiaddr[], options: OpenConnectionOptions = {}): Promise<Connection> {
137137
const { peerId, multiaddrs } = getPeerAddress(peerIdOrMultiaddr)
138138

139-
// make sure we don't have an existing connection to any of the addresses we
140-
// are about to dial
139+
// make sure we don't have an existing non-limited connection to any of the
140+
// addresses we are about to dial
141141
const existingConnection = Array.from(this.connections.values()).flat().find(conn => {
142142
if (options.force === true) {
143143
return false
144144
}
145145

146+
if (conn.limits != null) {
147+
return false
148+
}
149+
146150
if (conn.remotePeer.equals(peerId)) {
147151
return true
148152
}

0 commit comments

Comments
 (0)