@push.rocks/smartproxy 3.10.3 → 3.11.0
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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.networkproxy.d.ts +31 -0
- package/dist_ts/classes.networkproxy.js +305 -0
- package/dist_ts/classes.port80handler.d.ts +37 -0
- package/dist_ts/classes.port80handler.js +186 -0
- package/dist_ts/classes.portproxy.d.ts +26 -0
- package/dist_ts/classes.portproxy.js +295 -0
- package/dist_ts/classes.router.d.ts +13 -0
- package/dist_ts/classes.router.js +33 -0
- package/dist_ts/classes.sslredirect.d.ts +8 -0
- package/dist_ts/classes.sslredirect.js +28 -0
- package/dist_ts/helpers.certificates.d.ts +5 -0
- package/dist_ts/helpers.certificates.js +23 -0
- package/dist_ts/index.d.ts +4 -3
- package/dist_ts/index.js +5 -4
- package/dist_ts/smartproxy.portproxy.d.ts +1 -3
- package/dist_ts/smartproxy.portproxy.js +27 -33
- package/package.json +2 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/{smartproxy.classes.networkproxy.ts → classes.networkproxy.ts} +1 -1
- package/ts/classes.port80handler.ts +214 -0
- package/ts/{smartproxy.portproxy.ts → classes.portproxy.ts} +35 -32
- package/ts/index.ts +4 -3
- /package/ts/{smartproxy.classes.router.ts → classes.router.ts} +0 -0
- /package/ts/{smartproxy.classes.sslredirect.ts → classes.sslredirect.ts} +0 -0
- /package/ts/{smartproxy.helpers.certificates.ts → helpers.certificates.ts} +0 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extracts the SNI (Server Name Indication) from a TLS ClientHello packet.
|
|
4
|
+
* @param buffer - Buffer containing the TLS ClientHello.
|
|
5
|
+
* @returns The server name if found, otherwise undefined.
|
|
6
|
+
*/
|
|
7
|
+
function extractSNI(buffer) {
|
|
8
|
+
let offset = 0;
|
|
9
|
+
if (buffer.length < 5)
|
|
10
|
+
return undefined;
|
|
11
|
+
const recordType = buffer.readUInt8(0);
|
|
12
|
+
if (recordType !== 22)
|
|
13
|
+
return undefined; // 22 = handshake
|
|
14
|
+
const recordLength = buffer.readUInt16BE(3);
|
|
15
|
+
if (buffer.length < 5 + recordLength)
|
|
16
|
+
return undefined;
|
|
17
|
+
offset = 5;
|
|
18
|
+
const handshakeType = buffer.readUInt8(offset);
|
|
19
|
+
if (handshakeType !== 1)
|
|
20
|
+
return undefined; // 1 = ClientHello
|
|
21
|
+
offset += 4; // Skip handshake header (type + length)
|
|
22
|
+
offset += 2 + 32; // Skip client version and random
|
|
23
|
+
const sessionIDLength = buffer.readUInt8(offset);
|
|
24
|
+
offset += 1 + sessionIDLength; // Skip session ID
|
|
25
|
+
const cipherSuitesLength = buffer.readUInt16BE(offset);
|
|
26
|
+
offset += 2 + cipherSuitesLength; // Skip cipher suites
|
|
27
|
+
const compressionMethodsLength = buffer.readUInt8(offset);
|
|
28
|
+
offset += 1 + compressionMethodsLength; // Skip compression methods
|
|
29
|
+
if (offset + 2 > buffer.length)
|
|
30
|
+
return undefined;
|
|
31
|
+
const extensionsLength = buffer.readUInt16BE(offset);
|
|
32
|
+
offset += 2;
|
|
33
|
+
const extensionsEnd = offset + extensionsLength;
|
|
34
|
+
while (offset + 4 <= extensionsEnd) {
|
|
35
|
+
const extensionType = buffer.readUInt16BE(offset);
|
|
36
|
+
const extensionLength = buffer.readUInt16BE(offset + 2);
|
|
37
|
+
offset += 4;
|
|
38
|
+
if (extensionType === 0x0000) { // SNI extension
|
|
39
|
+
if (offset + 2 > buffer.length)
|
|
40
|
+
return undefined;
|
|
41
|
+
const sniListLength = buffer.readUInt16BE(offset);
|
|
42
|
+
offset += 2;
|
|
43
|
+
const sniListEnd = offset + sniListLength;
|
|
44
|
+
while (offset + 3 < sniListEnd) {
|
|
45
|
+
const nameType = buffer.readUInt8(offset++);
|
|
46
|
+
const nameLen = buffer.readUInt16BE(offset);
|
|
47
|
+
offset += 2;
|
|
48
|
+
if (nameType === 0) { // host_name
|
|
49
|
+
if (offset + nameLen > buffer.length)
|
|
50
|
+
return undefined;
|
|
51
|
+
return buffer.toString('utf8', offset, offset + nameLen);
|
|
52
|
+
}
|
|
53
|
+
offset += nameLen;
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
offset += extensionLength;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
export class PortProxy {
|
|
64
|
+
constructor(settings) {
|
|
65
|
+
// Unified record tracking each connection pair.
|
|
66
|
+
this.connectionRecords = new Set();
|
|
67
|
+
this.connectionLogger = null;
|
|
68
|
+
this.terminationStats = {
|
|
69
|
+
incoming: {},
|
|
70
|
+
outgoing: {},
|
|
71
|
+
};
|
|
72
|
+
this.settings = {
|
|
73
|
+
...settings,
|
|
74
|
+
toHost: settings.toHost || 'localhost',
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
incrementTerminationStat(side, reason) {
|
|
78
|
+
this.terminationStats[side][reason] = (this.terminationStats[side][reason] || 0) + 1;
|
|
79
|
+
}
|
|
80
|
+
async start() {
|
|
81
|
+
// Helper to forcefully destroy sockets.
|
|
82
|
+
const cleanUpSockets = (socketA, socketB) => {
|
|
83
|
+
if (!socketA.destroyed)
|
|
84
|
+
socketA.destroy();
|
|
85
|
+
if (socketB && !socketB.destroyed)
|
|
86
|
+
socketB.destroy();
|
|
87
|
+
};
|
|
88
|
+
// Normalize an IP to include both IPv4 and IPv6 representations.
|
|
89
|
+
const normalizeIP = (ip) => {
|
|
90
|
+
if (ip.startsWith('::ffff:')) {
|
|
91
|
+
const ipv4 = ip.slice(7);
|
|
92
|
+
return [ip, ipv4];
|
|
93
|
+
}
|
|
94
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
|
|
95
|
+
return [ip, `::ffff:${ip}`];
|
|
96
|
+
}
|
|
97
|
+
return [ip];
|
|
98
|
+
};
|
|
99
|
+
// Check if a given IP matches any of the glob patterns.
|
|
100
|
+
const isAllowed = (ip, patterns) => {
|
|
101
|
+
const normalizedIPVariants = normalizeIP(ip);
|
|
102
|
+
const expandedPatterns = patterns.flatMap(normalizeIP);
|
|
103
|
+
return normalizedIPVariants.some(ipVariant => expandedPatterns.some(pattern => plugins.minimatch(ipVariant, pattern)));
|
|
104
|
+
};
|
|
105
|
+
// Find a matching domain config based on the SNI.
|
|
106
|
+
const findMatchingDomain = (serverName) => this.settings.domains.find(config => plugins.minimatch(serverName, config.domain));
|
|
107
|
+
this.netServer = plugins.net.createServer((socket) => {
|
|
108
|
+
const remoteIP = socket.remoteAddress || '';
|
|
109
|
+
const connectionRecord = {
|
|
110
|
+
incoming: socket,
|
|
111
|
+
outgoing: null,
|
|
112
|
+
incomingStartTime: Date.now(),
|
|
113
|
+
connectionClosed: false,
|
|
114
|
+
};
|
|
115
|
+
this.connectionRecords.add(connectionRecord);
|
|
116
|
+
console.log(`New connection from ${remoteIP}. Active connections: ${this.connectionRecords.size}`);
|
|
117
|
+
let initialDataReceived = false;
|
|
118
|
+
let incomingTerminationReason = null;
|
|
119
|
+
let outgoingTerminationReason = null;
|
|
120
|
+
// Ensure cleanup happens only once for the entire connection record.
|
|
121
|
+
const cleanupOnce = () => {
|
|
122
|
+
if (!connectionRecord.connectionClosed) {
|
|
123
|
+
connectionRecord.connectionClosed = true;
|
|
124
|
+
cleanUpSockets(connectionRecord.incoming, connectionRecord.outgoing || undefined);
|
|
125
|
+
this.connectionRecords.delete(connectionRecord);
|
|
126
|
+
console.log(`Connection from ${remoteIP} terminated. Active connections: ${this.connectionRecords.size}`);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
// Helper to reject an incoming connection.
|
|
130
|
+
const rejectIncomingConnection = (reason, logMessage) => {
|
|
131
|
+
console.log(logMessage);
|
|
132
|
+
socket.end();
|
|
133
|
+
if (incomingTerminationReason === null) {
|
|
134
|
+
incomingTerminationReason = reason;
|
|
135
|
+
this.incrementTerminationStat('incoming', reason);
|
|
136
|
+
}
|
|
137
|
+
cleanupOnce();
|
|
138
|
+
};
|
|
139
|
+
socket.on('error', (err) => {
|
|
140
|
+
const errorMessage = initialDataReceived
|
|
141
|
+
? `(Immediate) Incoming socket error from ${remoteIP}: ${err.message}`
|
|
142
|
+
: `(Premature) Incoming socket error from ${remoteIP} before data received: ${err.message}`;
|
|
143
|
+
console.log(errorMessage);
|
|
144
|
+
});
|
|
145
|
+
const handleError = (side) => (err) => {
|
|
146
|
+
const code = err.code;
|
|
147
|
+
let reason = 'error';
|
|
148
|
+
if (code === 'ECONNRESET') {
|
|
149
|
+
reason = 'econnreset';
|
|
150
|
+
console.log(`ECONNRESET on ${side} side from ${remoteIP}: ${err.message}`);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.log(`Error on ${side} side from ${remoteIP}: ${err.message}`);
|
|
154
|
+
}
|
|
155
|
+
if (side === 'incoming' && incomingTerminationReason === null) {
|
|
156
|
+
incomingTerminationReason = reason;
|
|
157
|
+
this.incrementTerminationStat('incoming', reason);
|
|
158
|
+
}
|
|
159
|
+
else if (side === 'outgoing' && outgoingTerminationReason === null) {
|
|
160
|
+
outgoingTerminationReason = reason;
|
|
161
|
+
this.incrementTerminationStat('outgoing', reason);
|
|
162
|
+
}
|
|
163
|
+
cleanupOnce();
|
|
164
|
+
};
|
|
165
|
+
const handleClose = (side) => () => {
|
|
166
|
+
console.log(`Connection closed on ${side} side from ${remoteIP}`);
|
|
167
|
+
if (side === 'incoming' && incomingTerminationReason === null) {
|
|
168
|
+
incomingTerminationReason = 'normal';
|
|
169
|
+
this.incrementTerminationStat('incoming', 'normal');
|
|
170
|
+
}
|
|
171
|
+
else if (side === 'outgoing' && outgoingTerminationReason === null) {
|
|
172
|
+
outgoingTerminationReason = 'normal';
|
|
173
|
+
this.incrementTerminationStat('outgoing', 'normal');
|
|
174
|
+
}
|
|
175
|
+
cleanupOnce();
|
|
176
|
+
};
|
|
177
|
+
const setupConnection = (serverName, initialChunk) => {
|
|
178
|
+
const defaultAllowed = this.settings.defaultAllowedIPs && isAllowed(remoteIP, this.settings.defaultAllowedIPs);
|
|
179
|
+
if (!defaultAllowed && serverName) {
|
|
180
|
+
const domainConfig = findMatchingDomain(serverName);
|
|
181
|
+
if (!domainConfig) {
|
|
182
|
+
return rejectIncomingConnection('rejected', `Connection rejected: No matching domain config for ${serverName} from ${remoteIP}`);
|
|
183
|
+
}
|
|
184
|
+
if (!isAllowed(remoteIP, domainConfig.allowedIPs)) {
|
|
185
|
+
return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed for domain ${serverName}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else if (!defaultAllowed && !serverName) {
|
|
189
|
+
return rejectIncomingConnection('rejected', `Connection rejected: No SNI and IP ${remoteIP} not in default allowed list`);
|
|
190
|
+
}
|
|
191
|
+
else if (defaultAllowed && !serverName) {
|
|
192
|
+
console.log(`Connection allowed: IP ${remoteIP} is in default allowed list`);
|
|
193
|
+
}
|
|
194
|
+
const domainConfig = serverName ? findMatchingDomain(serverName) : undefined;
|
|
195
|
+
const targetHost = domainConfig?.targetIP || this.settings.toHost;
|
|
196
|
+
const connectionOptions = {
|
|
197
|
+
host: targetHost,
|
|
198
|
+
port: this.settings.toPort,
|
|
199
|
+
};
|
|
200
|
+
if (this.settings.preserveSourceIP) {
|
|
201
|
+
connectionOptions.localAddress = remoteIP.replace('::ffff:', '');
|
|
202
|
+
}
|
|
203
|
+
const targetSocket = plugins.net.connect(connectionOptions);
|
|
204
|
+
connectionRecord.outgoing = targetSocket;
|
|
205
|
+
connectionRecord.outgoingStartTime = Date.now();
|
|
206
|
+
console.log(`Connection established: ${remoteIP} -> ${targetHost}:${this.settings.toPort}` +
|
|
207
|
+
`${serverName ? ` (SNI: ${serverName})` : ''}`);
|
|
208
|
+
if (initialChunk) {
|
|
209
|
+
socket.unshift(initialChunk);
|
|
210
|
+
}
|
|
211
|
+
socket.setTimeout(120000);
|
|
212
|
+
socket.pipe(targetSocket);
|
|
213
|
+
targetSocket.pipe(socket);
|
|
214
|
+
socket.on('error', handleError('incoming'));
|
|
215
|
+
targetSocket.on('error', handleError('outgoing'));
|
|
216
|
+
socket.on('close', handleClose('incoming'));
|
|
217
|
+
targetSocket.on('close', handleClose('outgoing'));
|
|
218
|
+
socket.on('timeout', () => {
|
|
219
|
+
console.log(`Timeout on incoming side from ${remoteIP}`);
|
|
220
|
+
if (incomingTerminationReason === null) {
|
|
221
|
+
incomingTerminationReason = 'timeout';
|
|
222
|
+
this.incrementTerminationStat('incoming', 'timeout');
|
|
223
|
+
}
|
|
224
|
+
cleanupOnce();
|
|
225
|
+
});
|
|
226
|
+
targetSocket.on('timeout', () => {
|
|
227
|
+
console.log(`Timeout on outgoing side from ${remoteIP}`);
|
|
228
|
+
if (outgoingTerminationReason === null) {
|
|
229
|
+
outgoingTerminationReason = 'timeout';
|
|
230
|
+
this.incrementTerminationStat('outgoing', 'timeout');
|
|
231
|
+
}
|
|
232
|
+
cleanupOnce();
|
|
233
|
+
});
|
|
234
|
+
socket.on('end', handleClose('incoming'));
|
|
235
|
+
targetSocket.on('end', handleClose('outgoing'));
|
|
236
|
+
};
|
|
237
|
+
if (this.settings.sniEnabled) {
|
|
238
|
+
socket.setTimeout(5000, () => {
|
|
239
|
+
console.log(`Initial data timeout for ${remoteIP}`);
|
|
240
|
+
socket.end();
|
|
241
|
+
cleanupOnce();
|
|
242
|
+
});
|
|
243
|
+
socket.once('data', (chunk) => {
|
|
244
|
+
socket.setTimeout(0);
|
|
245
|
+
initialDataReceived = true;
|
|
246
|
+
const serverName = extractSNI(chunk) || '';
|
|
247
|
+
console.log(`Received connection from ${remoteIP} with SNI: ${serverName}`);
|
|
248
|
+
setupConnection(serverName, chunk);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
initialDataReceived = true;
|
|
253
|
+
if (!this.settings.defaultAllowedIPs || !isAllowed(remoteIP, this.settings.defaultAllowedIPs)) {
|
|
254
|
+
return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed for non-SNI connection`);
|
|
255
|
+
}
|
|
256
|
+
setupConnection('');
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
.on('error', (err) => {
|
|
260
|
+
console.log(`Server Error: ${err.message}`);
|
|
261
|
+
})
|
|
262
|
+
.listen(this.settings.fromPort, () => {
|
|
263
|
+
console.log(`PortProxy -> OK: Now listening on port ${this.settings.fromPort}` +
|
|
264
|
+
`${this.settings.sniEnabled ? ' (SNI passthrough enabled)' : ''}`);
|
|
265
|
+
});
|
|
266
|
+
// Every 10 seconds log active connection count and longest running durations.
|
|
267
|
+
this.connectionLogger = setInterval(() => {
|
|
268
|
+
const now = Date.now();
|
|
269
|
+
let maxIncoming = 0;
|
|
270
|
+
let maxOutgoing = 0;
|
|
271
|
+
for (const record of this.connectionRecords) {
|
|
272
|
+
maxIncoming = Math.max(maxIncoming, now - record.incomingStartTime);
|
|
273
|
+
if (record.outgoingStartTime) {
|
|
274
|
+
maxOutgoing = Math.max(maxOutgoing, now - record.outgoingStartTime);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
console.log(`(Interval Log) Active connections: ${this.connectionRecords.size}. ` +
|
|
278
|
+
`Longest running incoming: ${plugins.prettyMs(maxIncoming)}, outgoing: ${plugins.prettyMs(maxOutgoing)}. ` +
|
|
279
|
+
`Termination stats (incoming): ${JSON.stringify(this.terminationStats.incoming)}, ` +
|
|
280
|
+
`(outgoing): ${JSON.stringify(this.terminationStats.outgoing)}`);
|
|
281
|
+
}, 10000);
|
|
282
|
+
}
|
|
283
|
+
async stop() {
|
|
284
|
+
const done = plugins.smartpromise.defer();
|
|
285
|
+
this.netServer.close(() => {
|
|
286
|
+
done.resolve();
|
|
287
|
+
});
|
|
288
|
+
if (this.connectionLogger) {
|
|
289
|
+
clearInterval(this.connectionLogger);
|
|
290
|
+
this.connectionLogger = null;
|
|
291
|
+
}
|
|
292
|
+
await done.promise;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5wb3J0cHJveHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLnBvcnRwcm94eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQWtCeEM7Ozs7R0FJRztBQUNILFNBQVMsVUFBVSxDQUFDLE1BQWM7SUFDaEMsSUFBSSxNQUFNLEdBQUcsQ0FBQyxDQUFDO0lBQ2YsSUFBSSxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUM7UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUV4QyxNQUFNLFVBQVUsR0FBRyxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3ZDLElBQUksVUFBVSxLQUFLLEVBQUU7UUFBRSxPQUFPLFNBQVMsQ0FBQyxDQUFDLGlCQUFpQjtJQUUxRCxNQUFNLFlBQVksR0FBRyxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQzVDLElBQUksTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDLEdBQUcsWUFBWTtRQUFFLE9BQU8sU0FBUyxDQUFDO0lBRXZELE1BQU0sR0FBRyxDQUFDLENBQUM7SUFDWCxNQUFNLGFBQWEsR0FBRyxNQUFNLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQy9DLElBQUksYUFBYSxLQUFLLENBQUM7UUFBRSxPQUFPLFNBQVMsQ0FBQyxDQUFDLGtCQUFrQjtJQUU3RCxNQUFNLElBQUksQ0FBQyxDQUFDLENBQUMsd0NBQXdDO0lBQ3JELE1BQU0sSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUMsaUNBQWlDO0lBRW5ELE1BQU0sZUFBZSxHQUFHLE1BQU0sQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDakQsTUFBTSxJQUFJLENBQUMsR0FBRyxlQUFlLENBQUMsQ0FBQyxrQkFBa0I7SUFFakQsTUFBTSxrQkFBa0IsR0FBRyxNQUFNLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FB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
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
export declare class ProxyRouter {
|
|
3
|
+
reverseProxyConfigs: plugins.tsclass.network.IReverseProxyConfig[];
|
|
4
|
+
/**
|
|
5
|
+
* sets a new set of reverse configs to be routed to
|
|
6
|
+
* @param reverseCandidatesArg
|
|
7
|
+
*/
|
|
8
|
+
setNewProxyConfigs(reverseCandidatesArg: plugins.tsclass.network.IReverseProxyConfig[]): void;
|
|
9
|
+
/**
|
|
10
|
+
* routes a request
|
|
11
|
+
*/
|
|
12
|
+
routeReq(req: plugins.http.IncomingMessage): plugins.tsclass.network.IReverseProxyConfig;
|
|
13
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
export class ProxyRouter {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.reverseProxyConfigs = [];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* sets a new set of reverse configs to be routed to
|
|
8
|
+
* @param reverseCandidatesArg
|
|
9
|
+
*/
|
|
10
|
+
setNewProxyConfigs(reverseCandidatesArg) {
|
|
11
|
+
this.reverseProxyConfigs = reverseCandidatesArg;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* routes a request
|
|
15
|
+
*/
|
|
16
|
+
routeReq(req) {
|
|
17
|
+
const originalHost = req.headers.host;
|
|
18
|
+
if (!originalHost) {
|
|
19
|
+
console.error('No host header found in request');
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
// Strip port from host if present
|
|
23
|
+
const hostWithoutPort = originalHost.split(':')[0];
|
|
24
|
+
const correspodingReverseProxyConfig = this.reverseProxyConfigs.find((reverseConfig) => {
|
|
25
|
+
return reverseConfig.hostName === hostWithoutPort;
|
|
26
|
+
});
|
|
27
|
+
if (!correspodingReverseProxyConfig) {
|
|
28
|
+
console.error(`No config found for host: ${hostWithoutPort}`);
|
|
29
|
+
}
|
|
30
|
+
return correspodingReverseProxyConfig;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yb3V0ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLnJvdXRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUV4QyxNQUFNLE9BQU8sV0FBVztJQUF4QjtRQUNTLHdCQUFtQixHQUFrRCxFQUFFLENBQUM7SUE2QmpGLENBQUM7SUEzQkM7OztPQUdHO0lBQ0ksa0JBQWtCLENBQUMsb0JBQW1FO1FBQzNGLElBQUksQ0FBQyxtQkFBbUIsR0FBRyxvQkFBb0IsQ0FBQztJQUNsRCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxRQUFRLENBQUMsR0FBaUM7UUFDL0MsTUFBTSxZQUFZLEdBQUcsR0FBRyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUM7UUFDdEMsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO1lBQ2xCLE9BQU8sQ0FBQyxLQUFLLENBQUMsaUNBQWlDLENBQUMsQ0FBQztZQUNqRCxPQUFPLFNBQVMsQ0FBQztRQUNuQixDQUFDO1FBQ0Qsa0NBQWtDO1FBQ2xDLE1BQU0sZUFBZSxHQUFHLFlBQVksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDbkQsTUFBTSw4QkFBOEIsR0FBRyxJQUFJLENBQUMsbUJBQW1CLENBQUMsSUFBSSxDQUFDLENBQUMsYUFBYSxFQUFFLEVBQUU7WUFDckYsT0FBTyxhQUFhLENBQUMsUUFBUSxLQUFLLGVBQWUsQ0FBQztRQUNwRCxDQUFDLENBQUMsQ0FBQztRQUNILElBQUksQ0FBQyw4QkFBOEIsRUFBRSxDQUFDO1lBQ3BDLE9BQU8sQ0FBQyxLQUFLLENBQUMsNkJBQTZCLGVBQWUsRUFBRSxDQUFDLENBQUM7UUFDaEUsQ0FBQztRQUNELE9BQU8sOEJBQThCLENBQUM7SUFDeEMsQ0FBQztDQUNGIn0=
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
export class SslRedirect {
|
|
3
|
+
constructor(portArg) {
|
|
4
|
+
this.port = portArg;
|
|
5
|
+
}
|
|
6
|
+
async start() {
|
|
7
|
+
this.httpServer = plugins.http.createServer((request, response) => {
|
|
8
|
+
const requestUrl = new URL(request.url, `http://${request.headers.host}`);
|
|
9
|
+
const completeUrlWithoutProtocol = `${requestUrl.host}${requestUrl.pathname}${requestUrl.search}`;
|
|
10
|
+
const redirectUrl = `https://${completeUrlWithoutProtocol}`;
|
|
11
|
+
console.log(`Got http request for http://${completeUrlWithoutProtocol}`);
|
|
12
|
+
console.log(`Redirecting to ${redirectUrl}`);
|
|
13
|
+
response.writeHead(302, {
|
|
14
|
+
Location: redirectUrl,
|
|
15
|
+
});
|
|
16
|
+
response.end();
|
|
17
|
+
});
|
|
18
|
+
this.httpServer.listen(this.port);
|
|
19
|
+
}
|
|
20
|
+
async stop() {
|
|
21
|
+
const done = plugins.smartpromise.defer();
|
|
22
|
+
this.httpServer.close(() => {
|
|
23
|
+
done.resolve();
|
|
24
|
+
});
|
|
25
|
+
await done.promise;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zc2xyZWRpcmVjdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMuc3NscmVkaXJlY3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFFeEMsTUFBTSxPQUFPLFdBQVc7SUFHdEIsWUFBWSxPQUFlO1FBQ3pCLElBQUksQ0FBQyxJQUFJLEdBQUcsT0FBTyxDQUFDO0lBQ3RCLENBQUM7SUFFTSxLQUFLLENBQUMsS0FBSztRQUNoQixJQUFJLENBQUMsVUFBVSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUMsT0FBTyxFQUFFLFFBQVEsRUFBRSxFQUFFO1lBQ2hFLE1BQU0sVUFBVSxHQUFHLElBQUksR0FBRyxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsVUFBVSxPQUFPLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUM7WUFDMUUsTUFBTSwwQkFBMEIsR0FBRyxHQUFHLFVBQVUsQ0FBQyxJQUFJLEdBQUcsVUFBVSxDQUFDLFFBQVEsR0FBRyxVQUFVLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDbEcsTUFBTSxXQUFXLEdBQUcsV0FBVywwQkFBMEIsRUFBRSxDQUFDO1lBQzVELE9BQU8sQ0FBQyxHQUFHLENBQUMsK0JBQStCLDBCQUEwQixFQUFFLENBQUMsQ0FBQztZQUN6RSxPQUFPLENBQUMsR0FBRyxDQUFDLGtCQUFrQixXQUFXLEVBQUUsQ0FBQyxDQUFDO1lBQzdDLFFBQVEsQ0FBQyxTQUFTLENBQUMsR0FBRyxFQUFFO2dCQUN0QixRQUFRLEVBQUUsV0FBVzthQUN0QixDQUFDLENBQUM7WUFDSCxRQUFRLENBQUMsR0FBRyxFQUFFLENBQUM7UUFDakIsQ0FBQyxDQUFDLENBQUM7UUFDSCxJQUFJLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDcEMsQ0FBQztJQUVNLEtBQUssQ0FBQyxJQUFJO1FBQ2YsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUMxQyxJQUFJLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUU7WUFDekIsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ2pCLENBQUMsQ0FBQyxDQUFDO1FBQ0gsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDO0lBQ3JCLENBQUM7Q0FDRiJ9
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
export function loadDefaultCertificates() {
|
|
6
|
+
try {
|
|
7
|
+
const certPath = path.join(__dirname, '..', 'assets', 'certs');
|
|
8
|
+
const privateKey = fs.readFileSync(path.join(certPath, 'key.pem'), 'utf8');
|
|
9
|
+
const publicKey = fs.readFileSync(path.join(certPath, 'cert.pem'), 'utf8');
|
|
10
|
+
if (!privateKey || !publicKey) {
|
|
11
|
+
throw new Error('Failed to load default certificates');
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
privateKey,
|
|
15
|
+
publicKey
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
console.error('Error loading default certificates:', error);
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVscGVycy5jZXJ0aWZpY2F0ZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9oZWxwZXJzLmNlcnRpZmljYXRlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxNQUFNLElBQUksQ0FBQztBQUN6QixPQUFPLEtBQUssSUFBSSxNQUFNLE1BQU0sQ0FBQztBQUM3QixPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sS0FBSyxDQUFDO0FBRXBDLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsYUFBYSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztBQU8vRCxNQUFNLFVBQVUsdUJBQXVCO0lBQ3JDLElBQUksQ0FBQztRQUNILE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDL0QsTUFBTSxVQUFVLEdBQUcsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxTQUFTLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQztRQUMzRSxNQUFNLFNBQVMsR0FBRyxFQUFFLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLFVBQVUsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBRTNFLElBQUksQ0FBQyxVQUFVLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUM5QixNQUFNLElBQUksS0FBSyxDQUFDLHFDQUFxQyxDQUFDLENBQUM7UUFDekQsQ0FBQztRQUVELE9BQU87WUFDTCxVQUFVO1lBQ1YsU0FBUztTQUNWLENBQUM7SUFDSixDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNmLE9BQU8sQ0FBQyxLQUFLLENBQUMscUNBQXFDLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDNUQsTUFBTSxLQUFLLENBQUM7SUFDZCxDQUFDO0FBQ0gsQ0FBQyJ9
|
package/dist_ts/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export * from './
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './
|
|
1
|
+
export * from './classes.networkproxy.js';
|
|
2
|
+
export * from './classes.portproxy.js';
|
|
3
|
+
export * from './classes.port80handler.js';
|
|
4
|
+
export * from './classes.sslredirect.js';
|
package/dist_ts/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export * from './
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './
|
|
4
|
-
|
|
1
|
+
export * from './classes.networkproxy.js';
|
|
2
|
+
export * from './classes.portproxy.js';
|
|
3
|
+
export * from './classes.port80handler.js';
|
|
4
|
+
export * from './classes.sslredirect.js';
|
|
5
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLDJCQUEyQixDQUFDO0FBQzFDLGNBQWMsd0JBQXdCLENBQUM7QUFDdkMsY0FBYyw0QkFBNEIsQ0FBQztBQUMzQyxjQUFjLDBCQUEwQixDQUFDIn0=
|
|
@@ -16,9 +16,7 @@ export interface IProxySettings extends plugins.tls.TlsOptions {
|
|
|
16
16
|
export declare class PortProxy {
|
|
17
17
|
netServer: plugins.net.Server;
|
|
18
18
|
settings: IProxySettings;
|
|
19
|
-
private
|
|
20
|
-
private incomingConnectionTimes;
|
|
21
|
-
private outgoingConnectionTimes;
|
|
19
|
+
private connectionRecords;
|
|
22
20
|
private connectionLogger;
|
|
23
21
|
private terminationStats;
|
|
24
22
|
constructor(settings: IProxySettings);
|