@libp2p/webrtc 6.0.33 → 6.1.0-4a416447c

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.
Files changed (64) hide show
  1. package/README.md +35 -1
  2. package/dist/index.min.js +9 -9
  3. package/dist/index.min.js.map +4 -4
  4. package/dist/src/constants.d.ts +2 -1
  5. package/dist/src/constants.d.ts.map +1 -1
  6. package/dist/src/constants.js +2 -1
  7. package/dist/src/constants.js.map +1 -1
  8. package/dist/src/index.d.ts +35 -1
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/index.js +35 -1
  11. package/dist/src/index.js.map +1 -1
  12. package/dist/src/private-to-public/listener.d.ts.map +1 -1
  13. package/dist/src/private-to-public/listener.js +9 -7
  14. package/dist/src/private-to-public/listener.js.map +1 -1
  15. package/dist/src/private-to-public/transport.browser.d.ts +9 -0
  16. package/dist/src/private-to-public/transport.browser.d.ts.map +1 -1
  17. package/dist/src/private-to-public/transport.browser.js +33 -5
  18. package/dist/src/private-to-public/transport.browser.js.map +1 -1
  19. package/dist/src/private-to-public/utils/connect.d.ts +16 -0
  20. package/dist/src/private-to-public/utils/connect.d.ts.map +1 -1
  21. package/dist/src/private-to-public/utils/connect.js +77 -16
  22. package/dist/src/private-to-public/utils/connect.js.map +1 -1
  23. package/dist/src/private-to-public/utils/get-rtcpeerconnection.d.ts +4 -1
  24. package/dist/src/private-to-public/utils/get-rtcpeerconnection.d.ts.map +1 -1
  25. package/dist/src/private-to-public/utils/get-rtcpeerconnection.js +18 -11
  26. package/dist/src/private-to-public/utils/get-rtcpeerconnection.js.map +1 -1
  27. package/dist/src/private-to-public/utils/sdp.d.ts +3 -1
  28. package/dist/src/private-to-public/utils/sdp.d.ts.map +1 -1
  29. package/dist/src/private-to-public/utils/sdp.js +16 -2
  30. package/dist/src/private-to-public/utils/sdp.js.map +1 -1
  31. package/dist/src/private-to-public/utils/stun-listener.browser.d.ts +9 -0
  32. package/dist/src/private-to-public/utils/stun-listener.browser.d.ts.map +1 -0
  33. package/dist/src/private-to-public/utils/stun-listener.browser.js +6 -0
  34. package/dist/src/private-to-public/utils/stun-listener.browser.js.map +1 -0
  35. package/dist/src/private-to-public/utils/stun-listener.d.ts +2 -4
  36. package/dist/src/private-to-public/utils/stun-listener.d.ts.map +1 -1
  37. package/dist/src/private-to-public/utils/stun-listener.js +8 -2
  38. package/dist/src/private-to-public/utils/stun-listener.js.map +1 -1
  39. package/dist/src/private-to-public/utils/stun.d.ts +34 -0
  40. package/dist/src/private-to-public/utils/stun.d.ts.map +1 -0
  41. package/dist/src/private-to-public/utils/stun.js +107 -0
  42. package/dist/src/private-to-public/utils/stun.js.map +1 -0
  43. package/dist/src/util.d.ts +3 -5
  44. package/dist/src/util.d.ts.map +1 -1
  45. package/dist/src/util.js +3 -24
  46. package/dist/src/util.js.map +1 -1
  47. package/dist/src/webrtc/node-datachannel.browser.d.ts +7 -0
  48. package/dist/src/webrtc/node-datachannel.browser.d.ts.map +1 -0
  49. package/dist/src/webrtc/node-datachannel.browser.js +16 -0
  50. package/dist/src/webrtc/node-datachannel.browser.js.map +1 -0
  51. package/package.json +22 -11
  52. package/src/constants.ts +2 -1
  53. package/src/index.ts +35 -1
  54. package/src/private-to-public/listener.ts +9 -7
  55. package/src/private-to-public/transport.browser.ts +48 -5
  56. package/src/private-to-public/utils/connect.ts +112 -17
  57. package/src/private-to-public/utils/get-rtcpeerconnection.ts +20 -11
  58. package/src/private-to-public/utils/sdp.ts +21 -2
  59. package/src/private-to-public/utils/stun-listener.browser.ts +14 -0
  60. package/src/private-to-public/utils/stun-listener.ts +9 -7
  61. package/src/private-to-public/utils/stun.ts +128 -0
  62. package/src/util.ts +5 -32
  63. package/src/webrtc/node-datachannel.browser.ts +17 -0
  64. package/dist/typedoc-urls.json +0 -14
@@ -1,6 +1,7 @@
1
1
  import { isIPv4 } from '@chainsafe/is-ip'
2
2
  import { IceUdpMuxListener } from 'node-datachannel'
3
- import { handleStunRequest } from '../../util.ts'
3
+ import { handleStunRequest } from './stun.ts'
4
+ import type { StunRequestCallback } from './stun.ts'
4
5
  import type { Logger } from '@libp2p/interface'
5
6
  import type { AddressInfo } from 'node:net'
6
7
 
@@ -9,14 +10,15 @@ export interface StunServer {
9
10
  address(): AddressInfo
10
11
  }
11
12
 
12
- export interface Callback {
13
- (ufrag: string, remoteHost: string, remotePort: number): void
14
- }
15
-
16
- export async function stunListener (host: string, port: number, log: Logger, cb: Callback): Promise<StunServer> {
13
+ export async function stunListener (host: string, port: number, log: Logger, cb: StunRequestCallback): Promise<StunServer> {
17
14
  const listener = new IceUdpMuxListener(port, host)
18
15
  listener.onUnhandledStunRequest(request => {
19
- handleStunRequest(request, log, cb)
16
+ // this runs inside a native callback where a throw would crash the process
17
+ try {
18
+ handleStunRequest(request, log, cb)
19
+ } catch (err) {
20
+ log.error('error handling STUN request from %s:%d - %e', request.host, request.port, err)
21
+ }
20
22
  })
21
23
 
22
24
  return {
@@ -0,0 +1,128 @@
1
+ import { UFRAG_PREFIX_V1, UFRAG_PREFIX_V2 } from '../../constants.ts'
2
+ import type { Logger } from '@libp2p/interface'
3
+ import type { IceUdpMuxRequest } from 'node-datachannel'
4
+
5
+ // ICE credential charset and length, per RFC 8839 section 5.4
6
+ // (ice-char = ALPHA / DIGIT / "+" / "/"; ufrag = 4*256ice-char;
7
+ // password = 22*256ice-char).
8
+ //
9
+ // ice-char is ASCII-only, so any string that passes ICE_CHAR_REGEX has exactly
10
+ // one UTF-16 code unit per character. The String.length checks below therefore
11
+ // equal both the ice-char count and the byte length that go-libp2p measures with
12
+ // len(), so the two implementations accept the same credentials; non-ice-char
13
+ // input (including any multi-byte character) is rejected by both regardless of
14
+ // how each counts length.
15
+ const ICE_CHAR_REGEX = /^[A-Za-z0-9+/]+$/
16
+ const ICE_UFRAG_MIN_LENGTH = 4
17
+ const ICE_PWD_MIN_LENGTH = 22
18
+ const ICE_CREDENTIAL_MAX_LENGTH = 256
19
+
20
+ /**
21
+ * True if `value` is a valid ICE username fragment
22
+ * (RFC 8839 section 5.4: ufrag = 4*256ice-char).
23
+ */
24
+ export function isIceUfrag (value: string): boolean {
25
+ return value.length >= ICE_UFRAG_MIN_LENGTH && value.length <= ICE_CREDENTIAL_MAX_LENGTH && ICE_CHAR_REGEX.test(value)
26
+ }
27
+
28
+ /**
29
+ * True if `value` is a valid ICE password
30
+ * (RFC 8839 section 5.4: password = 22*256ice-char).
31
+ */
32
+ export function isIcePwd (value: string): boolean {
33
+ return value.length >= ICE_PWD_MIN_LENGTH && value.length <= ICE_CREDENTIAL_MAX_LENGTH && ICE_CHAR_REGEX.test(value)
34
+ }
35
+
36
+ export function parseStunUsernameUfrags (serverUfrag: string, clientUfrag: string): { serverUfrag: string, clientUfrag: string } | undefined {
37
+ // Both fragments come from the attacker-controlled STUN USERNAME
38
+ // ("<remote-ufrag>:<local-ufrag>", RFC 8445 section 7.2.2) and are ICE username
39
+ // fragments. Reject anything outside ice-char or the length bounds in RFC 8839
40
+ // section 5.4 before it goes into an SDP offer.
41
+ if (!isIceUfrag(serverUfrag) || !isIceUfrag(clientUfrag)) {
42
+ return undefined
43
+ }
44
+
45
+ return {
46
+ serverUfrag,
47
+ clientUfrag
48
+ }
49
+ }
50
+
51
+ /**
52
+ * The v2 server ufrag carries the client's ICE password so the server can
53
+ * infer the client's offer from the STUN USERNAME alone.
54
+ */
55
+ export function serverUfragV2 (clientIcePwd: string): string {
56
+ return `${UFRAG_PREFIX_V2}${clientIcePwd}`
57
+ }
58
+
59
+ export function decodeV2ClientPwd (serverUfrag: string): string | undefined {
60
+ if (!serverUfrag.startsWith(UFRAG_PREFIX_V2)) {
61
+ return undefined
62
+ }
63
+
64
+ const clientPwd = serverUfrag.substring(UFRAG_PREFIX_V2.length)
65
+
66
+ // The recovered value becomes the inferred offer's ice-pwd, so it must be a
67
+ // valid ICE password (ice-char, length 22..256) per RFC 8839 section 5.4.
68
+ if (!isIcePwd(clientPwd)) {
69
+ return undefined
70
+ }
71
+
72
+ return clientPwd
73
+ }
74
+
75
+ export interface StunRequestCallback {
76
+ (serverUfrag: string, clientUfrag: string, clientPwd: string | undefined, remoteHost: string, remotePort: number): void
77
+ }
78
+
79
+ /**
80
+ * Validate a STUN request received by the UDP mux listener and forward the ICE
81
+ * credentials it carries. Every value comes from the attacker-controlled STUN
82
+ * USERNAME and is reused as an ICE credential during native connection setup,
83
+ * where an invalid value aborts the process, so anything malformed is dropped
84
+ * here.
85
+ */
86
+ export function handleStunRequest (request: IceUdpMuxRequest, log: Logger, cb: StunRequestCallback): void {
87
+ if (request.ufrag == null) {
88
+ return
89
+ }
90
+
91
+ // The STUN USERNAME is "server_ufrag:client_ufrag" (RFC 8445 section 7.2.2),
92
+ // libjuice drops usernames without a colon so both halves are always present
93
+ const serverUfrag = request.localUfrag
94
+ const clientUfrag = request.ufrag
95
+
96
+ const parsed = parseStunUsernameUfrags(serverUfrag, clientUfrag)
97
+ if (parsed == null) {
98
+ log.trace('incoming STUN packet from %s:%d had invalid ufrags %s %s', request.host, request.port, serverUfrag, clientUfrag)
99
+ return
100
+ }
101
+
102
+ // Select the version explicitly from the server ufrag prefix. An unrecognized
103
+ // version is rejected, never assumed to be v1. See https://github.com/libp2p/specs/blob/master/webrtc/webrtc-direct.md.
104
+ if (parsed.serverUfrag.startsWith(UFRAG_PREFIX_V2)) {
105
+ const clientPwd = decodeV2ClientPwd(parsed.serverUfrag)
106
+ if (clientPwd == null) {
107
+ log.trace('incoming v2 STUN packet from %s:%d had an invalid client password %s', request.host, request.port, parsed.serverUfrag)
108
+ return
109
+ }
110
+ log.trace('incoming v2 STUN packet from %s:%d %s:%s', request.host, request.port, parsed.serverUfrag, parsed.clientUfrag)
111
+ cb(parsed.serverUfrag, parsed.clientUfrag, clientPwd, request.host, request.port)
112
+ return
113
+ }
114
+
115
+ if (parsed.serverUfrag.startsWith(UFRAG_PREFIX_V1)) {
116
+ // v1 reuses both ufrags as ICE passwords, so they must also satisfy the
117
+ // password length bound or native setup aborts the process
118
+ if (!isIcePwd(parsed.serverUfrag) || !isIcePwd(parsed.clientUfrag)) {
119
+ log.trace('incoming v1 STUN packet from %s:%d had ufrags that are not valid ICE passwords %s %s', request.host, request.port, parsed.serverUfrag, parsed.clientUfrag)
120
+ return
121
+ }
122
+ log.trace('incoming v1 STUN packet from %s:%d %s', request.host, request.port, parsed.serverUfrag)
123
+ cb(parsed.serverUfrag, parsed.clientUfrag, undefined, request.host, request.port)
124
+ return
125
+ }
126
+
127
+ log.trace('incoming STUN packet from %s:%d has an unsupported version prefix %s', request.host, request.port, parsed.serverUfrag)
128
+ }
package/src/util.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import pDefer from 'p-defer'
2
2
  import pTimeout from 'p-timeout'
3
- import { DATA_CHANNEL_DRAIN_TIMEOUT, DEFAULT_ICE_SERVERS, UFRAG_ALPHABET, UFRAG_PREFIX } from './constants.ts'
4
- import type { Logger, LoggerOptions } from '@libp2p/interface'
3
+ import { DATA_CHANNEL_DRAIN_TIMEOUT, DEFAULT_ICE_SERVERS, UFRAG_ALPHABET, UFRAG_PREFIX_V1 } from './constants.ts'
4
+ import type { LoggerOptions } from '@libp2p/interface'
5
5
  import type { Duplex, Source } from 'it-stream-types'
6
- import type { IceUdpMuxRequest, PeerConnection } from 'node-datachannel'
6
+ import type { PeerConnection } from 'node-datachannel'
7
7
 
8
8
  export const nopSource = async function * nop (): AsyncGenerator<Uint8Array, any, unknown> {}
9
9
 
@@ -102,33 +102,6 @@ export async function getRtcConfiguration (config?: RTCConfiguration | (() => RT
102
102
  return config
103
103
  }
104
104
 
105
- export const genUfrag = (len: number = 32): string => {
106
- return UFRAG_PREFIX + [...Array(len)].map(() => UFRAG_ALPHABET.at(Math.floor(Math.random() * UFRAG_ALPHABET.length))).join('')
107
- }
108
-
109
- // WebRTC-direct reuses the ufrag as the ICE password, which RFC 8445 requires to
110
- // be 22-256 chars from the ice-char set [a-zA-Z0-9+/]; other values crash the
111
- // process during native ICE setup, so reject them beforehand.
112
- const ICE_CREDENTIAL_REGEX = /^[a-zA-Z0-9+/]{22,256}$/
113
-
114
- export const isValidUfrag = (ufrag: string): boolean => {
115
- return ICE_CREDENTIAL_REGEX.test(ufrag)
116
- }
117
-
118
- // Validate and forward a STUN request received by the UDP mux listener. The ufrag
119
- // is attacker-controlled and reused as the ICE password in native setup, where an
120
- // invalid value aborts the process, so drop anything that is not a valid credential.
121
- export function handleStunRequest (request: IceUdpMuxRequest, log: Logger, cb: (ufrag: string, host: string, port: number) => void): void {
122
- if (request.ufrag == null) {
123
- return
124
- }
125
-
126
- if (!isValidUfrag(request.ufrag)) {
127
- log.trace('dropping incoming STUN packet from %s:%d with invalid ufrag', request.host, request.port)
128
- return
129
- }
130
-
131
- log.trace('incoming STUN packet from %s:%d %s', request.host, request.port, request.ufrag)
132
-
133
- cb(request.ufrag, request.host, request.port)
105
+ export const genUfrag = (len: number = 32, prefix: string = UFRAG_PREFIX_V1): string => {
106
+ return prefix + [...Array(len)].map(() => UFRAG_ALPHABET.at(Math.floor(Math.random() * UFRAG_ALPHABET.length))).join('')
134
107
  }
@@ -0,0 +1,17 @@
1
+ class NodeDataChannelUnavailableError extends Error {
2
+ constructor () {
3
+ super('node-datachannel is not available in browsers')
4
+ }
5
+ }
6
+
7
+ export class PeerConnection {
8
+ constructor () {
9
+ throw new NodeDataChannelUnavailableError()
10
+ }
11
+ }
12
+
13
+ export class IceUdpMuxListener {
14
+ constructor () {
15
+ throw new NodeDataChannelUnavailableError()
16
+ }
17
+ }
@@ -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
- }