@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.
- package/README.md +35 -1
- package/dist/index.min.js +9 -9
- package/dist/index.min.js.map +4 -4
- package/dist/src/constants.d.ts +2 -1
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +2 -1
- package/dist/src/constants.js.map +1 -1
- package/dist/src/index.d.ts +35 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +35 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/private-to-public/listener.d.ts.map +1 -1
- package/dist/src/private-to-public/listener.js +9 -7
- package/dist/src/private-to-public/listener.js.map +1 -1
- package/dist/src/private-to-public/transport.browser.d.ts +9 -0
- package/dist/src/private-to-public/transport.browser.d.ts.map +1 -1
- package/dist/src/private-to-public/transport.browser.js +33 -5
- package/dist/src/private-to-public/transport.browser.js.map +1 -1
- package/dist/src/private-to-public/utils/connect.d.ts +16 -0
- package/dist/src/private-to-public/utils/connect.d.ts.map +1 -1
- package/dist/src/private-to-public/utils/connect.js +77 -16
- package/dist/src/private-to-public/utils/connect.js.map +1 -1
- package/dist/src/private-to-public/utils/get-rtcpeerconnection.d.ts +4 -1
- package/dist/src/private-to-public/utils/get-rtcpeerconnection.d.ts.map +1 -1
- package/dist/src/private-to-public/utils/get-rtcpeerconnection.js +18 -11
- package/dist/src/private-to-public/utils/get-rtcpeerconnection.js.map +1 -1
- package/dist/src/private-to-public/utils/sdp.d.ts +3 -1
- package/dist/src/private-to-public/utils/sdp.d.ts.map +1 -1
- package/dist/src/private-to-public/utils/sdp.js +16 -2
- package/dist/src/private-to-public/utils/sdp.js.map +1 -1
- package/dist/src/private-to-public/utils/stun-listener.browser.d.ts +9 -0
- package/dist/src/private-to-public/utils/stun-listener.browser.d.ts.map +1 -0
- package/dist/src/private-to-public/utils/stun-listener.browser.js +6 -0
- package/dist/src/private-to-public/utils/stun-listener.browser.js.map +1 -0
- package/dist/src/private-to-public/utils/stun-listener.d.ts +2 -4
- package/dist/src/private-to-public/utils/stun-listener.d.ts.map +1 -1
- package/dist/src/private-to-public/utils/stun-listener.js +8 -2
- package/dist/src/private-to-public/utils/stun-listener.js.map +1 -1
- package/dist/src/private-to-public/utils/stun.d.ts +34 -0
- package/dist/src/private-to-public/utils/stun.d.ts.map +1 -0
- package/dist/src/private-to-public/utils/stun.js +107 -0
- package/dist/src/private-to-public/utils/stun.js.map +1 -0
- package/dist/src/util.d.ts +3 -5
- package/dist/src/util.d.ts.map +1 -1
- package/dist/src/util.js +3 -24
- package/dist/src/util.js.map +1 -1
- package/dist/src/webrtc/node-datachannel.browser.d.ts +7 -0
- package/dist/src/webrtc/node-datachannel.browser.d.ts.map +1 -0
- package/dist/src/webrtc/node-datachannel.browser.js +16 -0
- package/dist/src/webrtc/node-datachannel.browser.js.map +1 -0
- package/package.json +22 -11
- package/src/constants.ts +2 -1
- package/src/index.ts +35 -1
- package/src/private-to-public/listener.ts +9 -7
- package/src/private-to-public/transport.browser.ts +48 -5
- package/src/private-to-public/utils/connect.ts +112 -17
- package/src/private-to-public/utils/get-rtcpeerconnection.ts +20 -11
- package/src/private-to-public/utils/sdp.ts +21 -2
- package/src/private-to-public/utils/stun-listener.browser.ts +14 -0
- package/src/private-to-public/utils/stun-listener.ts +9 -7
- package/src/private-to-public/utils/stun.ts +128 -0
- package/src/util.ts +5 -32
- package/src/webrtc/node-datachannel.browser.ts +17 -0
- 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 '
|
|
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
|
|
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
|
-
|
|
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,
|
|
4
|
-
import type {
|
|
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 {
|
|
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
|
|
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
|
+
}
|
package/dist/typedoc-urls.json
DELETED
|
@@ -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
|
-
}
|