socket-function 1.2.21 → 1.2.23
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/index.d.ts +3 -0
- package/package.json +1 -1
- package/src/forwardPort.d.ts +3 -0
- package/src/forwardPort.ts +44 -22
- package/src/protocolNegotiation.ts +13 -1
- package/src/webSocketServer.ts +5 -8
package/index.d.ts
CHANGED
|
@@ -984,6 +984,9 @@ declare module "socket-function/src/forwardPort" {
|
|
|
984
984
|
externalPort: number;
|
|
985
985
|
internalPort: number;
|
|
986
986
|
duration?: number;
|
|
987
|
+
/** Keep the lease alive by re-creating the mapping at 80% of `duration`, forever. On by
|
|
988
|
+
* default, which is what you want — an un-maintained forward silently expires. */
|
|
989
|
+
autoRenew?: boolean;
|
|
987
990
|
}): Promise<void>;
|
|
988
991
|
/** Our machine's LAN IP, as the router sees it — used to tell whether an existing port
|
|
989
992
|
* mapping points at us or at a different machine on the network. */
|
package/package.json
CHANGED
package/src/forwardPort.d.ts
CHANGED
|
@@ -19,6 +19,9 @@ export declare function forwardPort(config: {
|
|
|
19
19
|
externalPort: number;
|
|
20
20
|
internalPort: number;
|
|
21
21
|
duration?: number;
|
|
22
|
+
/** Keep the lease alive by re-creating the mapping at 80% of `duration`, forever. On by
|
|
23
|
+
* default, which is what you want — an un-maintained forward silently expires. */
|
|
24
|
+
autoRenew?: boolean;
|
|
22
25
|
}): Promise<void>;
|
|
23
26
|
/** Our machine's LAN IP, as the router sees it — used to tell whether an existing port
|
|
24
27
|
* mapping points at us or at a different machine on the network. */
|
package/src/forwardPort.ts
CHANGED
|
@@ -74,36 +74,58 @@ export async function listPortMappings(): Promise<PortMapping[]> {
|
|
|
74
74
|
throw new Error(`Failed to list port mappings, could not find a working controlURL. Last error: ${(lastError as Error)?.stack ?? lastError}`);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// A UPnP lease expires on its own, so a forward left un-renewed silently dies partway through.
|
|
78
|
+
// Renew at 80% of the duration: enough margin to cover a failed attempt (we retry on the next
|
|
79
|
+
// tick) while the previous lease is still live, so the mapping never actually lapses.
|
|
80
|
+
const LEASE_RENEW_FRACTION = 0.8;
|
|
81
|
+
|
|
77
82
|
export async function forwardPort(config: {
|
|
78
83
|
externalPort: number;
|
|
79
84
|
internalPort: number;
|
|
80
85
|
duration?: number;
|
|
86
|
+
/** Keep the lease alive by re-creating the mapping at 80% of `duration`, forever. On by
|
|
87
|
+
* default, which is what you want — an un-maintained forward silently expires. */
|
|
88
|
+
autoRenew?: boolean;
|
|
81
89
|
}) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
let duration = config.duration ?? timeInHour;
|
|
85
|
-
|
|
86
|
-
const { internalIP, gatewayIP, controlPort, controlURLs } = await resolveGateway();
|
|
90
|
+
let duration = config.duration ?? timeInHour;
|
|
91
|
+
let autoRenew = config.autoRenew ?? true;
|
|
87
92
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
93
|
+
async function createMapping() {
|
|
94
|
+
try {
|
|
95
|
+
const { externalPort, internalPort } = config;
|
|
96
|
+
const { internalIP, gatewayIP, controlPort, controlURLs } = await resolveGateway();
|
|
97
|
+
|
|
98
|
+
for (let controlURL of controlURLs) {
|
|
99
|
+
try {
|
|
100
|
+
await createPortMapping({
|
|
101
|
+
externalPort, internalPort,
|
|
102
|
+
gatewayIP,
|
|
103
|
+
controlPort,
|
|
104
|
+
controlPath: controlURL,
|
|
105
|
+
internalIP,
|
|
106
|
+
duration,
|
|
107
|
+
});
|
|
108
|
+
console.log(`Port mapping created on ${gatewayIP}:${externalPort} -> ${internalIP}:${internalPort}`);
|
|
109
|
+
return;
|
|
110
|
+
} catch (e) {
|
|
111
|
+
console.error(`Failed to create port mapping using controlURL ${controlURL}`, e);
|
|
112
|
+
}
|
|
102
113
|
}
|
|
114
|
+
console.error("Failed to create port mapping, could not find controlURL");
|
|
115
|
+
} catch (e) {
|
|
116
|
+
console.error("Error in forwardPort", e);
|
|
103
117
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await createMapping();
|
|
121
|
+
|
|
122
|
+
if (autoRenew) {
|
|
123
|
+
// unref so a script that only wants a one-off forward (and then exits) isn't held open by
|
|
124
|
+
// the renewal timer; a running server stays alive on its own and the renewals keep firing.
|
|
125
|
+
let timer = setInterval(() => {
|
|
126
|
+
void createMapping();
|
|
127
|
+
}, Math.max(1, Math.floor(duration * LEASE_RENEW_FRACTION)));
|
|
128
|
+
timer.unref?.();
|
|
107
129
|
}
|
|
108
130
|
}
|
|
109
131
|
|
|
@@ -35,6 +35,17 @@ function encodeFlagBit(b: boolean): string { return b ? "1" : "0"; }
|
|
|
35
35
|
// connecting through a Let's Encrypt cert on a public domain.
|
|
36
36
|
const WILDCARD_TARGET = "";
|
|
37
37
|
|
|
38
|
+
// The target match is on the host portion only, not the exact listening port. A
|
|
39
|
+
// server can restart (or rebind) on a new port while keeping the same host identity,
|
|
40
|
+
// and a client still holding the old nodeId (host:oldPort) should be accepted rather
|
|
41
|
+
// than rejected as a stale thread. Strip only the trailing :port (last colon segment),
|
|
42
|
+
// so hosts that themselves contain colons survive.
|
|
43
|
+
function stripPort(nodeId: string): string {
|
|
44
|
+
let idx = nodeId.lastIndexOf(":");
|
|
45
|
+
if (idx < 0) return nodeId;
|
|
46
|
+
return nodeId.slice(0, idx);
|
|
47
|
+
}
|
|
48
|
+
|
|
38
49
|
function encodeOne(target: string, flags: ConnectionFlags): string {
|
|
39
50
|
let plain = `${PROTOCOL_VERSION}|${target}|clz4=${encodeFlagBit(flags.clientLZ4)}|slz4=${encodeFlagBit(flags.serverLZ4)}`;
|
|
40
51
|
return hexEncode(plain);
|
|
@@ -105,7 +116,8 @@ export function chooseProtocol(
|
|
|
105
116
|
for (let hex of proposed) {
|
|
106
117
|
let decoded = decodeProtocol(hex);
|
|
107
118
|
if (!decoded) continue;
|
|
108
|
-
|
|
119
|
+
// NOTE: We allow the pork to be different. This doesn't degrade security as we're still using TLS, so proxies don't reduce our security, However, it does make it a lot easier for a server to listen on multiple ports and redirect them all to the single-mounted port (As socket function doesn't support mounting on multiple ports because it makes it complicated for code that wants to say where we mounted to if we could be mounted to multiple ports at once).
|
|
120
|
+
if (decoded.target !== WILDCARD_TARGET && stripPort(decoded.target) !== stripPort(serverNodeId)) continue;
|
|
109
121
|
// Server capability check: if the proposal asks the server to receive
|
|
110
122
|
// LZ4 (slz4=1) but the server doesn't support it, skip.
|
|
111
123
|
if (decoded.flags.serverLZ4 && !serverCapabilities.lz4) continue;
|
package/src/webSocketServer.ts
CHANGED
|
@@ -11,8 +11,8 @@ import { parseSNIExtension, parseTLSHello, SNIType } from "./tlsParsing";
|
|
|
11
11
|
import debugbreak from "debugbreak";
|
|
12
12
|
import { getNodeId } from "./nodeCache";
|
|
13
13
|
import crypto from "crypto";
|
|
14
|
-
import { Watchable, getRootDomain
|
|
15
|
-
import { delay
|
|
14
|
+
import { Watchable, getRootDomain } from "./misc";
|
|
15
|
+
import { delay } from "./batching";
|
|
16
16
|
import { magenta, red } from "./formatting/logColors";
|
|
17
17
|
import { yellow } from "./formatting/logColors";
|
|
18
18
|
import { green } from "./formatting/logColors";
|
|
@@ -439,12 +439,9 @@ export async function startSocketServer(
|
|
|
439
439
|
port = (realServer.address() as net.AddressInfo).port;
|
|
440
440
|
|
|
441
441
|
if (doForward) {
|
|
442
|
-
// The mapping is claimed above;
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
console.log(magenta(`Refreshed port forward ${port} to our machine`));
|
|
446
|
-
}
|
|
447
|
-
runInfinitePoll(timeInMinute * 30, refreshForward);
|
|
442
|
+
// The mapping is claimed above; this establishes the maintained forward, which
|
|
443
|
+
// auto-renews its own lease (at 80% of the duration) so it doesn't expire.
|
|
444
|
+
await forwardPort({ externalPort: port, internalPort: port });
|
|
448
445
|
}
|
|
449
446
|
|
|
450
447
|
let nodeId = getNodeId(getCommonName(config.cert), port);
|