@torrent-tv/proxy 2.9.17 → 2.9.18
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/CHANGELOG.md +4 -0
- package/bin/cli.js +26 -1
- package/package.json +1 -1
- package/services/webrtc-manager.js +17 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## 2.9.18
|
|
2
|
+
|
|
3
|
+
- **New**: WebRTC is now reachable behind NAT via a static UDP port mapping. All sessions are pinned to a single UDP port (same number as the HTTP port, default 9090) and multiplexed over it (`enableIceUdpMux` + `portRangeBegin`/`portRangeEnd` in `webrtc-manager.js`), and that UDP port is UPnP/NAT-PMP-mapped at startup (a second `port-mapper.js` instance, protocol UDP, removed on shutdown). Because the socket is bound to a fixed, statically-mapped port, the proxy's `srflx` ICE candidate now carries `publicIP:9090` — reachable from the browser even behind symmetric NAT for that port (previously WebRTC used an ephemeral UDP port that UPnP could not map). Verified: two PeerConnections share the one UDP port with no bind conflict; host + srflx (v4 and global v6) candidates all carry the fixed port. The UDP endpoint is not reported to the server (the browser learns it via ICE, not the TCP dial-back probe).
|
|
4
|
+
|
|
1
5
|
## 2.9.17
|
|
2
6
|
|
|
3
7
|
- **New**: The proxy reports its UPnP-mapped external endpoint to the server over the tunnel (new `proxy-endpoint` message: `{ externalIp, externalPort, protocol }` from `port-mapper.getMappedEndpoint()`). Sent when the mapping completes and re-sent on every tunnel (re)connect, so the server can dial back and verify the proxy is reachable from the internet (server 0.8.22). No effect if port mapping is disabled or failed.
|
package/bin/cli.js
CHANGED
|
@@ -122,6 +122,9 @@ let tunnelClient = null;
|
|
|
122
122
|
/** @type {ReturnType<typeof createPortMapper> | null} */
|
|
123
123
|
let portMapper = null;
|
|
124
124
|
|
|
125
|
+
/** @type {ReturnType<typeof createPortMapper> | null} UDP mapping for the WebRTC port. */
|
|
126
|
+
let udpPortMapper = null;
|
|
127
|
+
|
|
125
128
|
|
|
126
129
|
/**
|
|
127
130
|
* Register this proxy with the registry server.
|
|
@@ -190,12 +193,16 @@ async function shutdown(signal) {
|
|
|
190
193
|
}
|
|
191
194
|
logger.warn(`Received ${signal}, shutting down...`);
|
|
192
195
|
try {
|
|
193
|
-
// Remove the router port
|
|
196
|
+
// Remove the router port mappings before exiting (lease expiry is the
|
|
194
197
|
// backstop if this is skipped on a hard kill).
|
|
195
198
|
if (portMapper) {
|
|
196
199
|
await portMapper.stop();
|
|
197
200
|
portMapper = null;
|
|
198
201
|
}
|
|
202
|
+
if (udpPortMapper) {
|
|
203
|
+
await udpPortMapper.stop();
|
|
204
|
+
udpPortMapper = null;
|
|
205
|
+
}
|
|
199
206
|
if (app) {
|
|
200
207
|
await app.close();
|
|
201
208
|
}
|
|
@@ -248,6 +255,21 @@ try {
|
|
|
248
255
|
const message = error instanceof Error ? error.message : String(error);
|
|
249
256
|
logger.warn(`Port mapping failed to start: ${message}`);
|
|
250
257
|
});
|
|
258
|
+
|
|
259
|
+
// Also map the WebRTC UDP port (same number, different protocol). All
|
|
260
|
+
// WebRTC sessions are multiplexed onto this single UDP port (ICE UDP mux),
|
|
261
|
+
// so a static mapping makes the proxy's WebRTC path reachable even behind
|
|
262
|
+
// symmetric NAT. This endpoint is NOT reported to the server: the browser
|
|
263
|
+
// discovers it via ICE (srflx) candidates, not the TCP dial-back probe.
|
|
264
|
+
udpPortMapper = createPortMapper({
|
|
265
|
+
port: actualPort,
|
|
266
|
+
protocol: "UDP",
|
|
267
|
+
description: "torrent-tv proxy (WebRTC)"
|
|
268
|
+
});
|
|
269
|
+
void udpPortMapper.start().catch((error) => {
|
|
270
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
271
|
+
logger.warn(`UDP port mapping failed to start: ${message}`);
|
|
272
|
+
});
|
|
251
273
|
} else {
|
|
252
274
|
logger.info("Automatic port mapping is disabled (--no-port-mapping).");
|
|
253
275
|
}
|
|
@@ -297,6 +319,9 @@ try {
|
|
|
297
319
|
});
|
|
298
320
|
|
|
299
321
|
webRtcManager = createWebRtcManager({
|
|
322
|
+
// Pin all WebRTC sessions to this single UDP port (multiplexed via ICE UDP
|
|
323
|
+
// mux) so the UPnP UDP mapping above makes the WebRTC path reachable.
|
|
324
|
+
udpPort: actualPort,
|
|
300
325
|
sendSignal(sessionId, signal) {
|
|
301
326
|
tunnelClient?.sendSignal(sessionId, signal);
|
|
302
327
|
},
|
package/package.json
CHANGED
|
@@ -70,6 +70,11 @@ function isPrivateHostCandidate(candidate) {
|
|
|
70
70
|
* `node-datachannel` `DataChannel` object; hand it to `createDataChannelHandler`.
|
|
71
71
|
* @property {(message: string) => void} [onLog]
|
|
72
72
|
* Optional log sink.
|
|
73
|
+
* @property {number} [udpPort]
|
|
74
|
+
* When set, every PeerConnection is pinned to this single UDP port and ICE
|
|
75
|
+
* UDP multiplexing is enabled, so all sessions share one port that can be
|
|
76
|
+
* statically UPnP-mapped (makes the WebRTC path reachable behind NAT). When
|
|
77
|
+
* omitted, node-datachannel uses an ephemeral UDP port (previous behaviour).
|
|
73
78
|
*/
|
|
74
79
|
|
|
75
80
|
/**
|
|
@@ -89,10 +94,20 @@ function isPrivateHostCandidate(candidate) {
|
|
|
89
94
|
* @param {WebRtcManagerOptions} options
|
|
90
95
|
* @returns {WebRtcManager}
|
|
91
96
|
*/
|
|
92
|
-
export function createWebRtcManager({ sendSignal, onDataChannel, onLog }) {
|
|
97
|
+
export function createWebRtcManager({ sendSignal, onDataChannel, onLog, udpPort }) {
|
|
93
98
|
/** @type {Map<string, import("node-datachannel").PeerConnection>} */
|
|
94
99
|
const peers = new Map();
|
|
95
100
|
|
|
101
|
+
// Base PeerConnection config shared by every session. When a UDP port is
|
|
102
|
+
// configured, pin all sessions to it and enable ICE UDP mux so they share the
|
|
103
|
+
// single (UPnP-mapped) port; otherwise fall back to an ephemeral UDP port.
|
|
104
|
+
const pcConfig = { iceServers: ICE_SERVERS };
|
|
105
|
+
if (Number.isInteger(udpPort) && udpPort > 0 && udpPort <= 65535) {
|
|
106
|
+
pcConfig.enableIceUdpMux = true;
|
|
107
|
+
pcConfig.portRangeBegin = udpPort;
|
|
108
|
+
pcConfig.portRangeEnd = udpPort;
|
|
109
|
+
}
|
|
110
|
+
|
|
96
111
|
/**
|
|
97
112
|
* @param {string} message
|
|
98
113
|
* @returns {void}
|
|
@@ -118,9 +133,7 @@ export function createWebRtcManager({ sendSignal, onDataChannel, onLog }) {
|
|
|
118
133
|
return existing;
|
|
119
134
|
}
|
|
120
135
|
|
|
121
|
-
const pc = new nodeDataChannel.PeerConnection(`proxy-${sessionId.slice(0, 8)}`,
|
|
122
|
-
iceServers: ICE_SERVERS
|
|
123
|
-
});
|
|
136
|
+
const pc = new nodeDataChannel.PeerConnection(`proxy-${sessionId.slice(0, 8)}`, pcConfig);
|
|
124
137
|
|
|
125
138
|
// Forward all ICE candidates to the browser through the tunnel.
|
|
126
139
|
//
|