@libp2p/webrtc 5.1.1 → 5.2.0-1ab50cc0d

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.
@@ -1,8 +1,8 @@
1
1
  import { networkInterfaces } from 'node:os'
2
2
  import { isIPv4, isIPv6 } from '@chainsafe/is-ip'
3
- import { TypedEventEmitter } from '@libp2p/interface'
4
- import { multiaddr, protocols } from '@multiformats/multiaddr'
5
- import { IP4 } from '@multiformats/multiaddr-matcher'
3
+ import { InvalidPeerIdError, TypedEventEmitter } from '@libp2p/interface'
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,18 @@ 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
+ owner: WebRTCDirectListener
58
+ peerId: PeerId
59
+ }
60
+
61
+ let UDP_MUX_LISTENERS: UDPMuxServer[] = []
62
+
50
63
  export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> implements Listener {
51
- private server?: StunServer
52
64
  private readonly multiaddrs: Multiaddr[]
53
65
  private certificate?: TransportCertificate
54
66
  private readonly connections: Map<string, DirectRTCPeerConnection>
@@ -89,7 +101,7 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
89
101
  if (host == null) {
90
102
  throw new Error('IP4/6 host must be specified in webrtc-direct mulitaddr')
91
103
  }
92
- let port = parseInt(parts
104
+ const port = parseInt(parts
93
105
  .filter(([code, value]) => code === UDP_PROTOCOL.code)
94
106
  .pop()?.[1] ?? '')
95
107
 
@@ -97,41 +109,90 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
97
109
  throw new Error('UDP port must be specified in webrtc-direct mulitaddr')
98
110
  }
99
111
 
100
- if (port === 0 && this.init.useLibjuice !== false) {
101
- // libjuice doesn't map 0 to a random free port so we have to do it
102
- // ourselves
103
- port = await getPort()
112
+ // have to do this before any async work happens so starting two listeners
113
+ // for the same port concurrently (e.g. ipv4/ipv6 both port 0) results in a
114
+ // single mux listener. This is necessary because libjuice binds to all
115
+ // interfaces for a given port so we we need to key on just the port number
116
+ // not the host + the port number
117
+ let existingServer = UDP_MUX_LISTENERS.find(s => s.port === port)
118
+
119
+ // if the server has not been started yet, or the port is a wildcard port
120
+ // and there is already a wildcard port for this address family, start a new
121
+ // UDP mux server
122
+ const wildcardPorts = port === 0 && existingServer?.port === 0
123
+ const sameAddressFamily = (existingServer?.isIPv4 === true && isIPv4(host)) || (existingServer?.isIPv6 === true && isIPv6(host))
124
+ let createdMuxServer = false
125
+
126
+ if (existingServer == null || (wildcardPorts && sameAddressFamily)) {
127
+ this.log('starting UDP mux server on %s:%p', host, port)
128
+ existingServer = this.startUDPMuxServer(host, port)
129
+ UDP_MUX_LISTENERS.push(existingServer)
130
+ createdMuxServer = true
104
131
  }
105
132
 
106
- this.server = await stunListener(host, port, this.log, (ufrag, remoteHost, remotePort) => {
107
- this.incomingConnection(ufrag, remoteHost, remotePort)
108
- .catch(err => {
109
- this.log.error('error processing incoming STUN request', err)
110
- })
111
- })
112
-
113
- let certificate = this.certificate
133
+ if (!existingServer.peerId.equals(this.components.peerId)) {
134
+ // this would have to be another in-process peer so we are likely in a
135
+ // testing environment
136
+ throw new InvalidPeerIdError(`Another peer is already performing UDP mux on ${host}:${existingServer.port}`)
137
+ }
114
138
 
115
- if (certificate == null) {
116
- const keyPair = await crypto.subtle.generateKey({
117
- name: 'ECDSA',
118
- namedCurve: 'P-256'
119
- }, true, ['sign', 'verify'])
139
+ const server = await existingServer.server
140
+ const address = server.address()
120
141
 
121
- certificate = this.certificate = await generateTransportCertificate(keyPair, {
122
- days: 365
123
- })
142
+ if (!createdMuxServer) {
143
+ this.log('reused existing UDP mux server on %s:%p', host, address.port)
124
144
  }
125
145
 
126
- const address = this.server.address()
127
-
128
- getNetworkAddresses(address.address, address.port, ipVersion).forEach((ma) => {
129
- this.multiaddrs.push(multiaddr(`${ma}/webrtc-direct/certhash/${certificate.certhash}`))
146
+ getNetworkAddresses(host, address.port, ipVersion).forEach((ma) => {
147
+ this.multiaddrs.push(multiaddr(`${ma}/webrtc-direct/certhash/${this.certificate?.certhash}`))
130
148
  })
131
149
 
132
150
  this.safeDispatchEvent('listening')
133
151
  }
134
152
 
153
+ private startUDPMuxServer (host: string, port: number): UDPMuxServer {
154
+ return {
155
+ peerId: this.components.peerId,
156
+ owner: this,
157
+ port,
158
+ isIPv4: isIPv4(host),
159
+ isIPv6: isIPv6(host),
160
+ server: Promise.resolve()
161
+ .then(async (): Promise<StunServer> => {
162
+ // ensure we have a certificate
163
+ if (this.certificate == null) {
164
+ this.log.trace('creating TLS certificate')
165
+ const keyPair = await crypto.subtle.generateKey({
166
+ name: 'ECDSA',
167
+ namedCurve: 'P-256'
168
+ }, true, ['sign', 'verify'])
169
+
170
+ const certificate = await generateTransportCertificate(keyPair, {
171
+ days: 365 * 10
172
+ })
173
+
174
+ if (this.certificate == null) {
175
+ this.certificate = certificate
176
+ }
177
+ }
178
+
179
+ if (port === 0) {
180
+ // libjuice doesn't map 0 to a random free port so we have to do it
181
+ // ourselves
182
+ this.log.trace('searching for free port')
183
+ port = await getPort()
184
+ }
185
+
186
+ return stunListener(host, port, this.log, (ufrag, remoteHost, remotePort) => {
187
+ this.incomingConnection(ufrag, remoteHost, remotePort)
188
+ .catch(err => {
189
+ this.log.error('error processing incoming STUN request', err)
190
+ })
191
+ })
192
+ })
193
+ }
194
+ }
195
+
135
196
  private async incomingConnection (ufrag: string, remoteHost: string, remotePort: number): Promise<void> {
136
197
  const key = `${remoteHost}:${remotePort}:${ufrag}`
137
198
  let peerConnection = this.connections.get(key)
@@ -184,12 +245,51 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
184
245
  return this.multiaddrs
185
246
  }
186
247
 
248
+ updateAnnounceAddrs (multiaddrs: Multiaddr[]): void {
249
+ for (let i = 0; i < multiaddrs.length; i++) {
250
+ let ma = multiaddrs[i]
251
+
252
+ if (!WebRTCDirect.exactMatch(ma)) {
253
+ continue
254
+ }
255
+
256
+ // add the certhash if it is missing
257
+ const tuples = ma.stringTuples()
258
+
259
+ for (let j = 0; j < tuples.length; j++) {
260
+ if (tuples[j][0] !== CODEC_WEBRTC_DIRECT) {
261
+ continue
262
+ }
263
+
264
+ const certhashIndex = j + 1
265
+
266
+ if (tuples[certhashIndex] == null || tuples[certhashIndex][0] !== CODEC_CERTHASH) {
267
+ tuples.splice(certhashIndex, 0, [CODEC_CERTHASH, this.certificate?.certhash])
268
+
269
+ ma = fromStringTuples(tuples)
270
+ multiaddrs[i] = ma
271
+ }
272
+ }
273
+ }
274
+ }
275
+
187
276
  async close (): Promise<void> {
188
277
  for (const connection of this.connections.values()) {
189
278
  connection.close()
190
279
  }
191
280
 
192
- await this.server?.close()
281
+ // stop our UDP mux listeners
282
+ await Promise.all(
283
+ UDP_MUX_LISTENERS
284
+ .filter(listener => listener.owner === this)
285
+ .map(async listener => {
286
+ const server = await listener.server
287
+ await server.close()
288
+ })
289
+ )
290
+
291
+ // remove our stopped UDP mux listeners
292
+ UDP_MUX_LISTENERS = UDP_MUX_LISTENERS.filter(listener => listener.owner !== this)
193
293
 
194
294
  // RTCPeerConnections will be removed from the connections map when their
195
295
  // connection state changes to 'closed'/'disconnected'/'failed
@@ -202,7 +302,7 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
202
302
  }
203
303
 
204
304
  function getNetworkAddresses (host: string, port: number, version: 4 | 6): string[] {
205
- if (host === '0.0.0.0' || host === '::1') {
305
+ if (host === '0.0.0.0' || host === '::') {
206
306
  // return all ip4 interfaces
207
307
  return Object.entries(networkInterfaces())
208
308
  .flatMap(([_, addresses]) => addresses)
@@ -1,14 +0,0 @@
1
- {
2
- "DataChannelOptions": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.DataChannelOptions.html",
3
- ".:DataChannelOptions": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.DataChannelOptions.html",
4
- "TransportCertificate": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.TransportCertificate.html",
5
- ".:TransportCertificate": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.TransportCertificate.html",
6
- "WebRTCDirectTransportComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.WebRTCDirectTransportComponents.html",
7
- "WebRTCTransportComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.WebRTCTransportComponents.html",
8
- "WebRTCTransportDirectInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.WebRTCTransportDirectInit.html",
9
- "WebRTCTransportInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.WebRTCTransportInit.html",
10
- "webRTC": "https://libp2p.github.io/js-libp2p/functions/_libp2p_webrtc.webRTC.html",
11
- ".:webRTC": "https://libp2p.github.io/js-libp2p/functions/_libp2p_webrtc.webRTC.html",
12
- "webRTCDirect": "https://libp2p.github.io/js-libp2p/functions/_libp2p_webrtc.webRTCDirect.html",
13
- ".:webRTCDirect": "https://libp2p.github.io/js-libp2p/functions/_libp2p_webrtc.webRTCDirect.html"
14
- }