@libp2p/webrtc 5.1.1 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/dist/index.min.js +3 -3
- package/dist/src/index.d.ts +15 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +15 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/private-to-private/listener.d.ts +1 -0
- package/dist/src/private-to-private/listener.d.ts.map +1 -1
- package/dist/src/private-to-private/listener.js +2 -0
- package/dist/src/private-to-private/listener.js.map +1 -1
- package/dist/src/private-to-public/listener.browser.d.ts +1 -0
- package/dist/src/private-to-public/listener.browser.d.ts.map +1 -1
- package/dist/src/private-to-public/listener.browser.js +2 -0
- package/dist/src/private-to-public/listener.browser.js.map +1 -1
- package/dist/src/private-to-public/listener.d.ts +3 -1
- package/dist/src/private-to-public/listener.d.ts.map +1 -1
- package/dist/src/private-to-public/listener.js +86 -29
- package/dist/src/private-to-public/listener.js.map +1 -1
- package/package.json +13 -11
- package/src/index.ts +15 -2
- package/src/private-to-private/listener.ts +4 -0
- package/src/private-to-public/listener.browser.ts +4 -0
- package/src/private-to-public/listener.ts +109 -33
@@ -1,8 +1,8 @@
|
|
1
1
|
import { networkInterfaces } from 'node:os'
|
2
2
|
import { isIPv4, isIPv6 } from '@chainsafe/is-ip'
|
3
3
|
import { TypedEventEmitter } from '@libp2p/interface'
|
4
|
-
import { multiaddr, protocols } from '@multiformats/multiaddr'
|
5
|
-
import { IP4 } from '@multiformats/multiaddr-matcher'
|
4
|
+
import { multiaddr, protocols, fromStringTuples } from '@multiformats/multiaddr'
|
5
|
+
import { IP4, WebRTCDirect } from '@multiformats/multiaddr-matcher'
|
6
6
|
import { Crypto } from '@peculiar/webcrypto'
|
7
7
|
import getPort from 'get-port'
|
8
8
|
import pWaitFor from 'p-wait-for'
|
@@ -22,6 +22,8 @@ const crypto = new Crypto()
|
|
22
22
|
* The time to wait, in milliseconds, for the data channel handshake to complete
|
23
23
|
*/
|
24
24
|
const HANDSHAKE_TIMEOUT_MS = 10_000
|
25
|
+
const CODEC_WEBRTC_DIRECT = 0x0118
|
26
|
+
const CODEC_CERTHASH = 0x01d2
|
25
27
|
|
26
28
|
export interface WebRTCDirectListenerComponents {
|
27
29
|
peerId: PeerId
|
@@ -47,8 +49,15 @@ const UDP_PROTOCOL = protocols('udp')
|
|
47
49
|
const IP4_PROTOCOL = protocols('ip4')
|
48
50
|
const IP6_PROTOCOL = protocols('ip6')
|
49
51
|
|
52
|
+
interface UDPMuxServer {
|
53
|
+
server: Promise<StunServer>
|
54
|
+
isIPv4: boolean
|
55
|
+
isIPv6: boolean
|
56
|
+
port: number
|
57
|
+
}
|
58
|
+
|
50
59
|
export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> implements Listener {
|
51
|
-
private
|
60
|
+
private readonly servers: UDPMuxServer[]
|
52
61
|
private readonly multiaddrs: Multiaddr[]
|
53
62
|
private certificate?: TransportCertificate
|
54
63
|
private readonly connections: Map<string, DirectRTCPeerConnection>
|
@@ -63,6 +72,7 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
|
|
63
72
|
this.init = init
|
64
73
|
this.components = components
|
65
74
|
this.multiaddrs = []
|
75
|
+
this.servers = []
|
66
76
|
this.connections = new Map()
|
67
77
|
this.log = components.logger.forComponent('libp2p:webrtc-direct:listener')
|
68
78
|
this.certificate = init.certificates?.[0]
|
@@ -89,7 +99,7 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
|
|
89
99
|
if (host == null) {
|
90
100
|
throw new Error('IP4/6 host must be specified in webrtc-direct mulitaddr')
|
91
101
|
}
|
92
|
-
|
102
|
+
const port = parseInt(parts
|
93
103
|
.filter(([code, value]) => code === UDP_PROTOCOL.code)
|
94
104
|
.pop()?.[1] ?? '')
|
95
105
|
|
@@ -97,41 +107,73 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
|
|
97
107
|
throw new Error('UDP port must be specified in webrtc-direct mulitaddr')
|
98
108
|
}
|
99
109
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
const keyPair = await crypto.subtle.generateKey({
|
117
|
-
name: 'ECDSA',
|
118
|
-
namedCurve: 'P-256'
|
119
|
-
}, true, ['sign', 'verify'])
|
120
|
-
|
121
|
-
certificate = this.certificate = await generateTransportCertificate(keyPair, {
|
122
|
-
days: 365
|
123
|
-
})
|
110
|
+
// have to do this before any async work happens so starting two listeners
|
111
|
+
// for the same port concurrently (e.g. ipv4/ipv6 both port 0) results in a
|
112
|
+
// single mux listener. This is necessary because libjuice binds to all
|
113
|
+
// interfaces for a given port so we we need to key on just the port number
|
114
|
+
// not the host + the port number
|
115
|
+
let existingServer = this.servers.find(s => s.port === port)
|
116
|
+
|
117
|
+
// if the server has not been started yet, or the port is a wildcard port
|
118
|
+
// and there is already a wildcard port for this address family, start a new
|
119
|
+
// UDP mux server
|
120
|
+
const wildcardPorts = port === 0 && existingServer?.port === 0
|
121
|
+
const sameAddressFamily = (existingServer?.isIPv4 === true && isIPv4(host)) || (existingServer?.isIPv6 === true && isIPv6(host))
|
122
|
+
|
123
|
+
if (existingServer == null || (wildcardPorts && sameAddressFamily)) {
|
124
|
+
existingServer = this.startUDPMuxServer(host, port)
|
125
|
+
this.servers.push(existingServer)
|
124
126
|
}
|
125
127
|
|
126
|
-
const
|
128
|
+
const server = await existingServer.server
|
129
|
+
const address = server.address()
|
127
130
|
|
128
|
-
getNetworkAddresses(
|
129
|
-
this.multiaddrs.push(multiaddr(`${ma}/webrtc-direct/certhash/${certificate
|
131
|
+
getNetworkAddresses(host, address.port, ipVersion).forEach((ma) => {
|
132
|
+
this.multiaddrs.push(multiaddr(`${ma}/webrtc-direct/certhash/${this.certificate?.certhash}`))
|
130
133
|
})
|
131
134
|
|
132
135
|
this.safeDispatchEvent('listening')
|
133
136
|
}
|
134
137
|
|
138
|
+
private startUDPMuxServer (host: string, port: number): UDPMuxServer {
|
139
|
+
return {
|
140
|
+
port,
|
141
|
+
isIPv4: isIPv4(host),
|
142
|
+
isIPv6: isIPv6(host),
|
143
|
+
server: Promise.resolve()
|
144
|
+
.then(async (): Promise<StunServer> => {
|
145
|
+
if (port === 0) {
|
146
|
+
// libjuice doesn't map 0 to a random free port so we have to do it
|
147
|
+
// ourselves
|
148
|
+
port = await getPort()
|
149
|
+
}
|
150
|
+
|
151
|
+
// ensure we have a certificate
|
152
|
+
if (this.certificate == null) {
|
153
|
+
const keyPair = await crypto.subtle.generateKey({
|
154
|
+
name: 'ECDSA',
|
155
|
+
namedCurve: 'P-256'
|
156
|
+
}, true, ['sign', 'verify'])
|
157
|
+
|
158
|
+
const certificate = await generateTransportCertificate(keyPair, {
|
159
|
+
days: 365 * 10
|
160
|
+
})
|
161
|
+
|
162
|
+
if (this.certificate == null) {
|
163
|
+
this.certificate = certificate
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
return stunListener(host, port, this.log, (ufrag, remoteHost, remotePort) => {
|
168
|
+
this.incomingConnection(ufrag, remoteHost, remotePort)
|
169
|
+
.catch(err => {
|
170
|
+
this.log.error('error processing incoming STUN request', err)
|
171
|
+
})
|
172
|
+
})
|
173
|
+
})
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
135
177
|
private async incomingConnection (ufrag: string, remoteHost: string, remotePort: number): Promise<void> {
|
136
178
|
const key = `${remoteHost}:${remotePort}:${ufrag}`
|
137
179
|
let peerConnection = this.connections.get(key)
|
@@ -184,12 +226,46 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
|
|
184
226
|
return this.multiaddrs
|
185
227
|
}
|
186
228
|
|
229
|
+
updateAnnounceAddrs (multiaddrs: Multiaddr[]): void {
|
230
|
+
for (let i = 0; i < multiaddrs.length; i++) {
|
231
|
+
let ma = multiaddrs[i]
|
232
|
+
|
233
|
+
if (!WebRTCDirect.exactMatch(ma)) {
|
234
|
+
continue
|
235
|
+
}
|
236
|
+
|
237
|
+
// add the certhash if it is missing
|
238
|
+
const tuples = ma.stringTuples()
|
239
|
+
|
240
|
+
for (let j = 0; j < tuples.length; j++) {
|
241
|
+
if (tuples[j][0] !== CODEC_WEBRTC_DIRECT) {
|
242
|
+
continue
|
243
|
+
}
|
244
|
+
|
245
|
+
const certhashIndex = j + 1
|
246
|
+
|
247
|
+
if (tuples[certhashIndex] == null || tuples[certhashIndex][0] !== CODEC_CERTHASH) {
|
248
|
+
tuples.splice(certhashIndex, 0, [CODEC_CERTHASH, this.certificate?.certhash])
|
249
|
+
|
250
|
+
ma = fromStringTuples(tuples)
|
251
|
+
multiaddrs[i] = ma
|
252
|
+
}
|
253
|
+
}
|
254
|
+
}
|
255
|
+
}
|
256
|
+
|
187
257
|
async close (): Promise<void> {
|
188
258
|
for (const connection of this.connections.values()) {
|
189
259
|
connection.close()
|
190
260
|
}
|
191
261
|
|
192
|
-
|
262
|
+
// stop all UDP mux listeners
|
263
|
+
await Promise.all(
|
264
|
+
this.servers.map(async p => {
|
265
|
+
const server = await p.server
|
266
|
+
await server.close()
|
267
|
+
})
|
268
|
+
)
|
193
269
|
|
194
270
|
// RTCPeerConnections will be removed from the connections map when their
|
195
271
|
// connection state changes to 'closed'/'disconnected'/'failed
|
@@ -202,7 +278,7 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
|
|
202
278
|
}
|
203
279
|
|
204
280
|
function getNetworkAddresses (host: string, port: number, version: 4 | 6): string[] {
|
205
|
-
if (host === '0.0.0.0' || host === '::
|
281
|
+
if (host === '0.0.0.0' || host === '::') {
|
206
282
|
// return all ip4 interfaces
|
207
283
|
return Object.entries(networkInterfaces())
|
208
284
|
.flatMap(([_, addresses]) => addresses)
|