@push.rocks/smartproxy 3.7.2 → 3.8.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.
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartproxy',
|
|
6
|
-
version: '3.
|
|
6
|
+
version: '3.8.0',
|
|
7
7
|
description: 'a proxy for handling high workloads of proxying'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLGlEQUFpRDtDQUMvRCxDQUFBIn0=
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import * as plugins from './smartproxy.plugins.js';
|
|
2
|
-
export interface
|
|
2
|
+
export interface IDomainConfig {
|
|
3
3
|
domain: string;
|
|
4
4
|
allowedIPs: string[];
|
|
5
5
|
targetIP?: string;
|
|
6
6
|
}
|
|
7
|
-
export interface
|
|
7
|
+
export interface IProxySettings extends plugins.tls.TlsOptions {
|
|
8
8
|
fromPort: number;
|
|
9
9
|
toPort: number;
|
|
10
10
|
toHost?: string;
|
|
11
|
-
domains:
|
|
11
|
+
domains: IDomainConfig[];
|
|
12
12
|
sniEnabled?: boolean;
|
|
13
13
|
defaultAllowedIPs?: string[];
|
|
14
14
|
preserveSourceIP?: boolean;
|
|
15
15
|
}
|
|
16
16
|
export declare class PortProxy {
|
|
17
|
-
netServer: plugins.net.Server
|
|
18
|
-
settings:
|
|
19
|
-
|
|
17
|
+
netServer: plugins.net.Server;
|
|
18
|
+
settings: IProxySettings;
|
|
19
|
+
private activeConnections;
|
|
20
|
+
private connectionLogger;
|
|
21
|
+
constructor(settings: IProxySettings);
|
|
20
22
|
start(): Promise<void>;
|
|
21
23
|
stop(): Promise<void>;
|
|
22
24
|
}
|
|
@@ -1,6 +1,93 @@
|
|
|
1
1
|
import * as plugins from './smartproxy.plugins.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extract SNI (Server Name Indication) from a TLS ClientHello packet.
|
|
4
|
+
* Returns the server name if found, or undefined.
|
|
5
|
+
*/
|
|
6
|
+
function extractSNI(buffer) {
|
|
7
|
+
let offset = 0;
|
|
8
|
+
// We need at least 5 bytes for the record header.
|
|
9
|
+
if (buffer.length < 5) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
// TLS record header
|
|
13
|
+
const recordType = buffer.readUInt8(0);
|
|
14
|
+
if (recordType !== 22) { // 22 = handshake
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
// Read record length
|
|
18
|
+
const recordLength = buffer.readUInt16BE(3);
|
|
19
|
+
if (buffer.length < 5 + recordLength) {
|
|
20
|
+
// Not all data arrived yet; in production you might need to accumulate more data.
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
offset = 5;
|
|
24
|
+
// Handshake message type should be 1 for ClientHello.
|
|
25
|
+
const handshakeType = buffer.readUInt8(offset);
|
|
26
|
+
if (handshakeType !== 1) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
// Skip handshake header (1 byte type + 3 bytes length)
|
|
30
|
+
offset += 4;
|
|
31
|
+
// Skip client version (2 bytes) and random (32 bytes)
|
|
32
|
+
offset += 2 + 32;
|
|
33
|
+
// Session ID
|
|
34
|
+
const sessionIDLength = buffer.readUInt8(offset);
|
|
35
|
+
offset += 1 + sessionIDLength;
|
|
36
|
+
// Cipher suites
|
|
37
|
+
const cipherSuitesLength = buffer.readUInt16BE(offset);
|
|
38
|
+
offset += 2 + cipherSuitesLength;
|
|
39
|
+
// Compression methods
|
|
40
|
+
const compressionMethodsLength = buffer.readUInt8(offset);
|
|
41
|
+
offset += 1 + compressionMethodsLength;
|
|
42
|
+
// Extensions length
|
|
43
|
+
if (offset + 2 > buffer.length) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
const extensionsLength = buffer.readUInt16BE(offset);
|
|
47
|
+
offset += 2;
|
|
48
|
+
const extensionsEnd = offset + extensionsLength;
|
|
49
|
+
// Iterate over extensions
|
|
50
|
+
while (offset + 4 <= extensionsEnd) {
|
|
51
|
+
const extensionType = buffer.readUInt16BE(offset);
|
|
52
|
+
const extensionLength = buffer.readUInt16BE(offset + 2);
|
|
53
|
+
offset += 4;
|
|
54
|
+
// Check for SNI extension (type 0)
|
|
55
|
+
if (extensionType === 0x0000) {
|
|
56
|
+
// SNI extension: first 2 bytes are the SNI list length.
|
|
57
|
+
if (offset + 2 > buffer.length) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const sniListLength = buffer.readUInt16BE(offset);
|
|
61
|
+
offset += 2;
|
|
62
|
+
const sniListEnd = offset + sniListLength;
|
|
63
|
+
// Loop through the list; typically there is one entry.
|
|
64
|
+
while (offset + 3 < sniListEnd) {
|
|
65
|
+
const nameType = buffer.readUInt8(offset);
|
|
66
|
+
offset++;
|
|
67
|
+
const nameLen = buffer.readUInt16BE(offset);
|
|
68
|
+
offset += 2;
|
|
69
|
+
if (nameType === 0) { // host_name
|
|
70
|
+
if (offset + nameLen > buffer.length) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const serverName = buffer.toString('utf8', offset, offset + nameLen);
|
|
74
|
+
return serverName;
|
|
75
|
+
}
|
|
76
|
+
offset += nameLen;
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
offset += extensionLength;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
2
86
|
export class PortProxy {
|
|
3
87
|
constructor(settings) {
|
|
88
|
+
// Track active incoming connections
|
|
89
|
+
this.activeConnections = new Set();
|
|
90
|
+
this.connectionLogger = null;
|
|
4
91
|
this.settings = {
|
|
5
92
|
...settings,
|
|
6
93
|
toHost: settings.toHost || 'localhost'
|
|
@@ -24,7 +111,7 @@ export class PortProxy {
|
|
|
24
111
|
return [ip, ipv4];
|
|
25
112
|
}
|
|
26
113
|
// Handle IPv4 addresses by adding IPv4-mapped IPv6 variant
|
|
27
|
-
if (
|
|
114
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
|
|
28
115
|
return [ip, `::ffff:${ip}`];
|
|
29
116
|
}
|
|
30
117
|
return [ip];
|
|
@@ -38,113 +125,135 @@ export class PortProxy {
|
|
|
38
125
|
const findMatchingDomain = (serverName) => {
|
|
39
126
|
return this.settings.domains.find(config => plugins.minimatch(serverName, config.domain));
|
|
40
127
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
128
|
+
// Create a plain net server for TLS passthrough.
|
|
129
|
+
this.netServer = plugins.net.createServer((socket) => {
|
|
130
|
+
const remoteIP = socket.remoteAddress || '';
|
|
131
|
+
// Track the new incoming connection.
|
|
132
|
+
this.activeConnections.add(socket);
|
|
133
|
+
console.log(`New connection from ${remoteIP}. Active connections: ${this.activeConnections.size}`);
|
|
134
|
+
// Flag to ensure cleanup happens only once.
|
|
135
|
+
let connectionClosed = false;
|
|
136
|
+
const cleanupOnce = () => {
|
|
137
|
+
if (!connectionClosed) {
|
|
138
|
+
connectionClosed = true;
|
|
139
|
+
cleanUpSockets(socket, to);
|
|
140
|
+
if (this.activeConnections.has(socket)) {
|
|
141
|
+
this.activeConnections.delete(socket);
|
|
142
|
+
console.log(`Connection from ${remoteIP} terminated. Active connections: ${this.activeConnections.size}`);
|
|
143
|
+
}
|
|
51
144
|
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const isDefaultAllowed = this.settings.defaultAllowedIPs && isAllowed(remoteIP, this.settings.defaultAllowedIPs);
|
|
59
|
-
if (this.settings.sniEnabled && from instanceof plugins.tls.TLSSocket) {
|
|
60
|
-
serverName = from.servername || '';
|
|
61
|
-
console.log(`TLS Connection from ${remoteIP} for domain: ${serverName}`);
|
|
62
|
-
}
|
|
63
|
-
// If IP is in defaultAllowedIPs, allow the connection regardless of SNI
|
|
64
|
-
if (isDefaultAllowed) {
|
|
65
|
-
console.log(`Connection allowed: IP ${remoteIP} is in default allowed list`);
|
|
66
|
-
}
|
|
67
|
-
else if (this.settings.sniEnabled && serverName) {
|
|
68
|
-
// For SNI connections that aren't in default list, check domain-specific rules
|
|
69
|
-
const domainConfig = findMatchingDomain(serverName);
|
|
70
|
-
if (!domainConfig) {
|
|
71
|
-
console.log(`Connection rejected: No matching domain config for ${serverName} from IP ${remoteIP}`);
|
|
72
|
-
from.end();
|
|
73
|
-
return;
|
|
145
|
+
};
|
|
146
|
+
let to;
|
|
147
|
+
const handleError = (side) => (err) => {
|
|
148
|
+
const code = err.code;
|
|
149
|
+
if (code === 'ECONNRESET') {
|
|
150
|
+
console.log(`ECONNRESET on ${side} side from ${remoteIP}: ${err.message}`);
|
|
74
151
|
}
|
|
75
|
-
|
|
76
|
-
console.log(`
|
|
77
|
-
|
|
152
|
+
else {
|
|
153
|
+
console.log(`Error on ${side} side from ${remoteIP}: ${err.message}`);
|
|
154
|
+
}
|
|
155
|
+
cleanupOnce();
|
|
156
|
+
};
|
|
157
|
+
const handleClose = (side) => () => {
|
|
158
|
+
console.log(`Connection closed on ${side} side from ${remoteIP}`);
|
|
159
|
+
cleanupOnce();
|
|
160
|
+
};
|
|
161
|
+
// Setup connection, optionally accepting the initial data chunk.
|
|
162
|
+
const setupConnection = (serverName, initialChunk) => {
|
|
163
|
+
// Check if the IP is allowed by default.
|
|
164
|
+
const isDefaultAllowed = this.settings.defaultAllowedIPs && isAllowed(remoteIP, this.settings.defaultAllowedIPs);
|
|
165
|
+
if (!isDefaultAllowed && serverName) {
|
|
166
|
+
const domainConfig = findMatchingDomain(serverName);
|
|
167
|
+
if (!domainConfig) {
|
|
168
|
+
console.log(`Connection rejected: No matching domain config for ${serverName} from ${remoteIP}`);
|
|
169
|
+
socket.end();
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (!isAllowed(remoteIP, domainConfig.allowedIPs)) {
|
|
173
|
+
console.log(`Connection rejected: IP ${remoteIP} not allowed for domain ${serverName}`);
|
|
174
|
+
socket.end();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else if (!isDefaultAllowed && !serverName) {
|
|
179
|
+
console.log(`Connection rejected: No SNI and IP ${remoteIP} not in default allowed list`);
|
|
180
|
+
socket.end();
|
|
78
181
|
return;
|
|
79
182
|
}
|
|
183
|
+
else {
|
|
184
|
+
console.log(`Connection allowed: IP ${remoteIP} is in default allowed list`);
|
|
185
|
+
}
|
|
186
|
+
// Determine target host.
|
|
187
|
+
const domainConfig = serverName ? findMatchingDomain(serverName) : undefined;
|
|
188
|
+
const targetHost = domainConfig?.targetIP || this.settings.toHost;
|
|
189
|
+
// Create connection options.
|
|
190
|
+
const connectionOptions = {
|
|
191
|
+
host: targetHost,
|
|
192
|
+
port: this.settings.toPort,
|
|
193
|
+
};
|
|
194
|
+
if (this.settings.preserveSourceIP) {
|
|
195
|
+
connectionOptions.localAddress = remoteIP.replace('::ffff:', '');
|
|
196
|
+
}
|
|
197
|
+
// Establish outgoing connection.
|
|
198
|
+
to = plugins.net.connect(connectionOptions);
|
|
199
|
+
console.log(`Connection established: ${remoteIP} -> ${targetHost}:${this.settings.toPort}${serverName ? ` (SNI: ${serverName})` : ''}`);
|
|
200
|
+
// Push back the initial chunk if provided.
|
|
201
|
+
if (initialChunk) {
|
|
202
|
+
socket.unshift(initialChunk);
|
|
203
|
+
}
|
|
204
|
+
socket.setTimeout(120000);
|
|
205
|
+
socket.pipe(to);
|
|
206
|
+
to.pipe(socket);
|
|
207
|
+
// Attach error and close handlers for both sockets.
|
|
208
|
+
socket.on('error', handleError('incoming'));
|
|
209
|
+
to.on('error', handleError('outgoing'));
|
|
210
|
+
socket.on('close', handleClose('incoming'));
|
|
211
|
+
to.on('close', handleClose('outgoing'));
|
|
212
|
+
socket.on('timeout', handleError('incoming'));
|
|
213
|
+
to.on('timeout', handleError('outgoing'));
|
|
214
|
+
socket.on('end', handleClose('incoming'));
|
|
215
|
+
to.on('end', handleClose('outgoing'));
|
|
216
|
+
};
|
|
217
|
+
// For SNI-enabled connections, peek at the first chunk.
|
|
218
|
+
if (this.settings.sniEnabled) {
|
|
219
|
+
socket.once('data', (chunk) => {
|
|
220
|
+
// Try to extract the server name from the ClientHello.
|
|
221
|
+
const serverName = extractSNI(chunk) || '';
|
|
222
|
+
console.log(`Received connection from ${remoteIP} with SNI: ${serverName}`);
|
|
223
|
+
setupConnection(serverName, chunk);
|
|
224
|
+
});
|
|
80
225
|
}
|
|
81
226
|
else {
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const targetHost = domainConfig?.targetIP || this.settings.toHost;
|
|
90
|
-
// Create connection, optionally preserving the client's source IP
|
|
91
|
-
const connectionOptions = {
|
|
92
|
-
host: targetHost,
|
|
93
|
-
port: this.settings.toPort,
|
|
94
|
-
};
|
|
95
|
-
// Only set localAddress if preserveSourceIP is enabled
|
|
96
|
-
if (this.settings.preserveSourceIP) {
|
|
97
|
-
connectionOptions.localAddress = remoteIP.replace('::ffff:', ''); // Remove IPv6 mapping if present
|
|
227
|
+
// For non-SNI connections, simply check defaultAllowedIPs.
|
|
228
|
+
if (!this.settings.defaultAllowedIPs || !isAllowed(remoteIP, this.settings.defaultAllowedIPs)) {
|
|
229
|
+
console.log(`Connection rejected: IP ${remoteIP} not allowed for non-SNI connection`);
|
|
230
|
+
socket.end();
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
setupConnection('');
|
|
98
234
|
}
|
|
99
|
-
// If this is a TLS connection, use net.connect to ensure raw passthrough
|
|
100
|
-
const to = plugins.net.connect(connectionOptions);
|
|
101
|
-
console.log(`Connection established: ${remoteIP} -> ${targetHost}:${this.settings.toPort}${serverName ? ` (SNI: ${serverName})` : ''}`);
|
|
102
|
-
from.setTimeout(120000);
|
|
103
|
-
from.pipe(to);
|
|
104
|
-
to.pipe(from);
|
|
105
|
-
from.on('error', () => {
|
|
106
|
-
cleanUpSockets(from, to);
|
|
107
|
-
});
|
|
108
|
-
to.on('error', () => {
|
|
109
|
-
cleanUpSockets(from, to);
|
|
110
|
-
});
|
|
111
|
-
from.on('close', () => {
|
|
112
|
-
cleanUpSockets(from, to);
|
|
113
|
-
});
|
|
114
|
-
to.on('close', () => {
|
|
115
|
-
cleanUpSockets(from, to);
|
|
116
|
-
});
|
|
117
|
-
from.on('timeout', () => {
|
|
118
|
-
cleanUpSockets(from, to);
|
|
119
|
-
});
|
|
120
|
-
to.on('timeout', () => {
|
|
121
|
-
cleanUpSockets(from, to);
|
|
122
|
-
});
|
|
123
|
-
from.on('end', () => {
|
|
124
|
-
cleanUpSockets(from, to);
|
|
125
|
-
});
|
|
126
|
-
to.on('end', () => {
|
|
127
|
-
cleanUpSockets(from, to);
|
|
128
|
-
});
|
|
129
|
-
};
|
|
130
|
-
this.netServer = server
|
|
131
|
-
.on('connection', handleConnection)
|
|
132
|
-
.on('secureConnection', handleConnection)
|
|
133
|
-
.on('tlsClientError', (err, tlsSocket) => {
|
|
134
|
-
console.log(`TLS Client Error: ${err.message}`);
|
|
135
235
|
})
|
|
136
236
|
.on('error', (err) => {
|
|
137
237
|
console.log(`Server Error: ${err.message}`);
|
|
138
238
|
})
|
|
139
|
-
.listen(this.settings.fromPort)
|
|
140
|
-
|
|
239
|
+
.listen(this.settings.fromPort, () => {
|
|
240
|
+
console.log(`PortProxy -> OK: Now listening on port ${this.settings.fromPort}${this.settings.sniEnabled ? ' (SNI passthrough enabled)' : ''}`);
|
|
241
|
+
});
|
|
242
|
+
// Log active connection count every 10 seconds.
|
|
243
|
+
this.connectionLogger = setInterval(() => {
|
|
244
|
+
console.log(`(Interval Log) Active connections: ${this.activeConnections.size}`);
|
|
245
|
+
}, 10000);
|
|
141
246
|
}
|
|
142
247
|
async stop() {
|
|
143
248
|
const done = plugins.smartpromise.defer();
|
|
144
249
|
this.netServer.close(() => {
|
|
145
250
|
done.resolve();
|
|
146
251
|
});
|
|
252
|
+
if (this.connectionLogger) {
|
|
253
|
+
clearInterval(this.connectionLogger);
|
|
254
|
+
this.connectionLogger = null;
|
|
255
|
+
}
|
|
147
256
|
await done.promise;
|
|
148
257
|
}
|
|
149
258
|
}
|
|
150
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRwcm94eS5wb3J0cHJveHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydHByb3h5LnBvcnRwcm94eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHlCQUF5QixDQUFDO0FBc0JuRCxNQUFNLE9BQU8sU0FBUztJQUlwQixZQUFZLFFBQXVCO1FBQ2pDLElBQUksQ0FBQyxRQUFRLEdBQUc7WUFDZCxHQUFHLFFBQVE7WUFDWCxNQUFNLEVBQUUsUUFBUSxDQUFDLE1BQU0sSUFBSSxXQUFXO1NBQ3ZDLENBQUM7SUFDSixDQUFDO0lBRU0sS0FBSyxDQUFDLEtBQUs7UUFDaEIsTUFBTSxjQUFjLEdBQUcsQ0FBQyxJQUF3QixFQUFFLEVBQXNCLEVBQUUsRUFBRTtZQUMxRSxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDWCxFQUFFLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDVCxJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztZQUMxQixFQUFFLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztZQUN4QixJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDZCxFQUFFLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDWixJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDZixFQUFFLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDZixDQUFDLENBQUM7UUFDRixNQUFNLFdBQVcsR0FBRyxDQUFDLEVBQVUsRUFBWSxFQUFFO1lBQzNDLG9DQUFvQztZQUNwQyxJQUFJLEVBQUUsQ0FBQyxVQUFVLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQztnQkFDN0IsTUFBTSxJQUFJLEdBQUcsRUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLDBCQUEwQjtnQkFDcEQsT0FBTyxDQUFDLEVBQUUsRUFBRSxJQUFJLENBQUMsQ0FBQztZQUNwQixDQUFDO1lBQ0QsMkRBQTJEO1lBQzNELElBQUksRUFBRSxDQUFD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
|
|
259
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRwcm94eS5wb3J0cHJveHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydHByb3h5LnBvcnRwcm94eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHlCQUF5QixDQUFDO0FBcUJuRDs7O0dBR0c7QUFDSCxTQUFTLFVBQVUsQ0FBQyxNQUFjO0lBQ2hDLElBQUksTUFBTSxHQUFHLENBQUMsQ0FBQztJQUNmLGtEQUFrRDtJQUNsRCxJQUFJLE1BQU0sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7UUFDdEIsT0FBTyxTQUFTLENBQUM7SUFDbkIsQ0FBQztJQUVELG9CQUFvQjtJQUNwQixNQUFNLFVBQVUsR0FBRyxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3ZDLElBQUksVUFBVSxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUMsaUJBQWlCO1FBQ3hDLE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7SUFDRCxxQkFBcUI7SUFDckIsTUFBTSxZQUFZLEdBQUcsTUFBTSxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM1QyxJQUFJLE1BQU0sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxHQUFHLFlBQVksRUFBRSxDQUFDO1FBQ3JDLGtGQUFrRjtRQUNsRixPQUFPLFNBQVMsQ0FBQztJQUNuQixDQUFDO0lBRUQsTUFBTSxHQUFHLENBQUMsQ0FBQztJQUNYLHNEQUFzRDtJQUN0RCxNQUFNLGFBQWEsR0FBRyxNQUFNLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQy9DLElBQUksYUFBYSxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ3hCLE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7SUFDRCx1REFBdUQ7SUFDdkQsTUFBTSxJQUFJLENBQUMsQ0FBQztJQUVaLHNEQUFzRDtJQUN0RCxNQUFNLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztJQUVqQixhQUFh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
|
package/package.json
CHANGED
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -1,30 +1,125 @@
|
|
|
1
1
|
import * as plugins from './smartproxy.plugins.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
targetIP?: string; // Optional target IP for this domain
|
|
3
|
+
export interface IDomainConfig {
|
|
4
|
+
domain: string; // glob pattern for domain
|
|
5
|
+
allowedIPs: string[]; // glob patterns for IPs allowed to access this domain
|
|
6
|
+
targetIP?: string; // Optional target IP for this domain
|
|
8
7
|
}
|
|
9
8
|
|
|
10
|
-
export interface
|
|
9
|
+
export interface IProxySettings extends plugins.tls.TlsOptions {
|
|
11
10
|
// Port configuration
|
|
12
11
|
fromPort: number;
|
|
13
12
|
toPort: number;
|
|
14
|
-
toHost?: string;
|
|
13
|
+
toHost?: string; // Target host to proxy to, defaults to 'localhost'
|
|
15
14
|
|
|
16
15
|
// Domain and security settings
|
|
17
|
-
domains:
|
|
16
|
+
domains: IDomainConfig[];
|
|
18
17
|
sniEnabled?: boolean;
|
|
19
|
-
defaultAllowedIPs?: string[];
|
|
20
|
-
preserveSourceIP?: boolean;
|
|
18
|
+
defaultAllowedIPs?: string[]; // Optional default IP patterns if no matching domain found
|
|
19
|
+
preserveSourceIP?: boolean; // Whether to preserve the client's source IP when proxying
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Extract SNI (Server Name Indication) from a TLS ClientHello packet.
|
|
24
|
+
* Returns the server name if found, or undefined.
|
|
25
|
+
*/
|
|
26
|
+
function extractSNI(buffer: Buffer): string | undefined {
|
|
27
|
+
let offset = 0;
|
|
28
|
+
// We need at least 5 bytes for the record header.
|
|
29
|
+
if (buffer.length < 5) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// TLS record header
|
|
34
|
+
const recordType = buffer.readUInt8(0);
|
|
35
|
+
if (recordType !== 22) { // 22 = handshake
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
// Read record length
|
|
39
|
+
const recordLength = buffer.readUInt16BE(3);
|
|
40
|
+
if (buffer.length < 5 + recordLength) {
|
|
41
|
+
// Not all data arrived yet; in production you might need to accumulate more data.
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
offset = 5;
|
|
46
|
+
// Handshake message type should be 1 for ClientHello.
|
|
47
|
+
const handshakeType = buffer.readUInt8(offset);
|
|
48
|
+
if (handshakeType !== 1) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
// Skip handshake header (1 byte type + 3 bytes length)
|
|
52
|
+
offset += 4;
|
|
53
|
+
|
|
54
|
+
// Skip client version (2 bytes) and random (32 bytes)
|
|
55
|
+
offset += 2 + 32;
|
|
56
|
+
|
|
57
|
+
// Session ID
|
|
58
|
+
const sessionIDLength = buffer.readUInt8(offset);
|
|
59
|
+
offset += 1 + sessionIDLength;
|
|
60
|
+
|
|
61
|
+
// Cipher suites
|
|
62
|
+
const cipherSuitesLength = buffer.readUInt16BE(offset);
|
|
63
|
+
offset += 2 + cipherSuitesLength;
|
|
64
|
+
|
|
65
|
+
// Compression methods
|
|
66
|
+
const compressionMethodsLength = buffer.readUInt8(offset);
|
|
67
|
+
offset += 1 + compressionMethodsLength;
|
|
68
|
+
|
|
69
|
+
// Extensions length
|
|
70
|
+
if (offset + 2 > buffer.length) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const extensionsLength = buffer.readUInt16BE(offset);
|
|
74
|
+
offset += 2;
|
|
75
|
+
const extensionsEnd = offset + extensionsLength;
|
|
76
|
+
|
|
77
|
+
// Iterate over extensions
|
|
78
|
+
while (offset + 4 <= extensionsEnd) {
|
|
79
|
+
const extensionType = buffer.readUInt16BE(offset);
|
|
80
|
+
const extensionLength = buffer.readUInt16BE(offset + 2);
|
|
81
|
+
offset += 4;
|
|
82
|
+
|
|
83
|
+
// Check for SNI extension (type 0)
|
|
84
|
+
if (extensionType === 0x0000) {
|
|
85
|
+
// SNI extension: first 2 bytes are the SNI list length.
|
|
86
|
+
if (offset + 2 > buffer.length) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
const sniListLength = buffer.readUInt16BE(offset);
|
|
90
|
+
offset += 2;
|
|
91
|
+
const sniListEnd = offset + sniListLength;
|
|
92
|
+
// Loop through the list; typically there is one entry.
|
|
93
|
+
while (offset + 3 < sniListEnd) {
|
|
94
|
+
const nameType = buffer.readUInt8(offset);
|
|
95
|
+
offset++;
|
|
96
|
+
const nameLen = buffer.readUInt16BE(offset);
|
|
97
|
+
offset += 2;
|
|
98
|
+
if (nameType === 0) { // host_name
|
|
99
|
+
if (offset + nameLen > buffer.length) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
const serverName = buffer.toString('utf8', offset, offset + nameLen);
|
|
103
|
+
return serverName;
|
|
104
|
+
}
|
|
105
|
+
offset += nameLen;
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
} else {
|
|
109
|
+
offset += extensionLength;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
21
113
|
}
|
|
22
114
|
|
|
23
115
|
export class PortProxy {
|
|
24
|
-
netServer: plugins.net.Server
|
|
25
|
-
settings:
|
|
116
|
+
netServer: plugins.net.Server;
|
|
117
|
+
settings: IProxySettings;
|
|
118
|
+
// Track active incoming connections
|
|
119
|
+
private activeConnections: Set<plugins.net.Socket> = new Set();
|
|
120
|
+
private connectionLogger: NodeJS.Timeout | null = null;
|
|
26
121
|
|
|
27
|
-
constructor(settings:
|
|
122
|
+
constructor(settings: IProxySettings) {
|
|
28
123
|
this.settings = {
|
|
29
124
|
...settings,
|
|
30
125
|
toHost: settings.toHost || 'localhost'
|
|
@@ -42,6 +137,7 @@ export class PortProxy {
|
|
|
42
137
|
from.destroy();
|
|
43
138
|
to.destroy();
|
|
44
139
|
};
|
|
140
|
+
|
|
45
141
|
const normalizeIP = (ip: string): string[] => {
|
|
46
142
|
// Handle IPv4-mapped IPv6 addresses
|
|
47
143
|
if (ip.startsWith('::ffff:')) {
|
|
@@ -49,7 +145,7 @@ export class PortProxy {
|
|
|
49
145
|
return [ip, ipv4];
|
|
50
146
|
}
|
|
51
147
|
// Handle IPv4 addresses by adding IPv4-mapped IPv6 variant
|
|
52
|
-
if (
|
|
148
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
|
|
53
149
|
return [ip, `::ffff:${ip}`];
|
|
54
150
|
}
|
|
55
151
|
return [ip];
|
|
@@ -59,122 +155,142 @@ export class PortProxy {
|
|
|
59
155
|
// Expand patterns to include both IPv4 and IPv6 variants
|
|
60
156
|
const expandedPatterns = patterns.flatMap(normalizeIP);
|
|
61
157
|
// Check if any variant of the IP matches any expanded pattern
|
|
62
|
-
return normalizeIP(value).some(ip =>
|
|
158
|
+
return normalizeIP(value).some(ip =>
|
|
63
159
|
expandedPatterns.some(pattern => plugins.minimatch(ip, pattern))
|
|
64
160
|
);
|
|
65
161
|
};
|
|
66
162
|
|
|
67
|
-
const findMatchingDomain = (serverName: string):
|
|
163
|
+
const findMatchingDomain = (serverName: string): IDomainConfig | undefined => {
|
|
68
164
|
return this.settings.domains.find(config => plugins.minimatch(serverName, config.domain));
|
|
69
165
|
};
|
|
70
166
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
167
|
+
// Create a plain net server for TLS passthrough.
|
|
168
|
+
this.netServer = plugins.net.createServer((socket: plugins.net.Socket) => {
|
|
169
|
+
const remoteIP = socket.remoteAddress || '';
|
|
170
|
+
|
|
171
|
+
// Track the new incoming connection.
|
|
172
|
+
this.activeConnections.add(socket);
|
|
173
|
+
console.log(`New connection from ${remoteIP}. Active connections: ${this.activeConnections.size}`);
|
|
174
|
+
|
|
175
|
+
// Flag to ensure cleanup happens only once.
|
|
176
|
+
let connectionClosed = false;
|
|
177
|
+
const cleanupOnce = () => {
|
|
178
|
+
if (!connectionClosed) {
|
|
179
|
+
connectionClosed = true;
|
|
180
|
+
cleanUpSockets(socket, to);
|
|
181
|
+
if (this.activeConnections.has(socket)) {
|
|
182
|
+
this.activeConnections.delete(socket);
|
|
183
|
+
console.log(`Connection from ${remoteIP} terminated. Active connections: ${this.activeConnections.size}`);
|
|
81
184
|
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const handleConnection = (from: plugins.net.Socket | plugins.tls.TLSSocket) => {
|
|
86
|
-
const remoteIP = from.remoteAddress || '';
|
|
87
|
-
let serverName = '';
|
|
88
|
-
|
|
89
|
-
// First check if this IP is in the default allowed list
|
|
90
|
-
const isDefaultAllowed = this.settings.defaultAllowedIPs && isAllowed(remoteIP, this.settings.defaultAllowedIPs);
|
|
91
|
-
|
|
92
|
-
if (this.settings.sniEnabled && from instanceof plugins.tls.TLSSocket) {
|
|
93
|
-
serverName = (from as any).servername || '';
|
|
94
|
-
console.log(`TLS Connection from ${remoteIP} for domain: ${serverName}`);
|
|
95
|
-
}
|
|
185
|
+
}
|
|
186
|
+
};
|
|
96
187
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
console.log(`
|
|
105
|
-
from.end();
|
|
106
|
-
return;
|
|
188
|
+
let to: plugins.net.Socket;
|
|
189
|
+
|
|
190
|
+
const handleError = (side: 'incoming' | 'outgoing') => (err: Error) => {
|
|
191
|
+
const code = (err as any).code;
|
|
192
|
+
if (code === 'ECONNRESET') {
|
|
193
|
+
console.log(`ECONNRESET on ${side} side from ${remoteIP}: ${err.message}`);
|
|
194
|
+
} else {
|
|
195
|
+
console.log(`Error on ${side} side from ${remoteIP}: ${err.message}`);
|
|
107
196
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
197
|
+
cleanupOnce();
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const handleClose = (side: 'incoming' | 'outgoing') => () => {
|
|
201
|
+
console.log(`Connection closed on ${side} side from ${remoteIP}`);
|
|
202
|
+
cleanupOnce();
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Setup connection, optionally accepting the initial data chunk.
|
|
206
|
+
const setupConnection = (serverName: string, initialChunk?: Buffer) => {
|
|
207
|
+
// Check if the IP is allowed by default.
|
|
208
|
+
const isDefaultAllowed = this.settings.defaultAllowedIPs && isAllowed(remoteIP, this.settings.defaultAllowedIPs);
|
|
209
|
+
if (!isDefaultAllowed && serverName) {
|
|
210
|
+
const domainConfig = findMatchingDomain(serverName);
|
|
211
|
+
if (!domainConfig) {
|
|
212
|
+
console.log(`Connection rejected: No matching domain config for ${serverName} from ${remoteIP}`);
|
|
213
|
+
socket.end();
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (!isAllowed(remoteIP, domainConfig.allowedIPs)) {
|
|
217
|
+
console.log(`Connection rejected: IP ${remoteIP} not allowed for domain ${serverName}`);
|
|
218
|
+
socket.end();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
} else if (!isDefaultAllowed && !serverName) {
|
|
222
|
+
console.log(`Connection rejected: No SNI and IP ${remoteIP} not in default allowed list`);
|
|
223
|
+
socket.end();
|
|
111
224
|
return;
|
|
225
|
+
} else {
|
|
226
|
+
console.log(`Connection allowed: IP ${remoteIP} is in default allowed list`);
|
|
112
227
|
}
|
|
113
|
-
} else {
|
|
114
|
-
// Non-SNI connection and not in default list
|
|
115
|
-
console.log(`Connection rejected: IP ${remoteIP} not allowed for non-SNI connection`);
|
|
116
|
-
from.end();
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
228
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
229
|
+
// Determine target host.
|
|
230
|
+
const domainConfig = serverName ? findMatchingDomain(serverName) : undefined;
|
|
231
|
+
const targetHost = domainConfig?.targetIP || this.settings.toHost!;
|
|
232
|
+
|
|
233
|
+
// Create connection options.
|
|
234
|
+
const connectionOptions: plugins.net.NetConnectOpts = {
|
|
235
|
+
host: targetHost,
|
|
236
|
+
port: this.settings.toPort,
|
|
237
|
+
};
|
|
238
|
+
if (this.settings.preserveSourceIP) {
|
|
239
|
+
connectionOptions.localAddress = remoteIP.replace('::ffff:', '');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Establish outgoing connection.
|
|
243
|
+
to = plugins.net.connect(connectionOptions);
|
|
244
|
+
console.log(`Connection established: ${remoteIP} -> ${targetHost}:${this.settings.toPort}${serverName ? ` (SNI: ${serverName})` : ''}`);
|
|
245
|
+
|
|
246
|
+
// Push back the initial chunk if provided.
|
|
247
|
+
if (initialChunk) {
|
|
248
|
+
socket.unshift(initialChunk);
|
|
249
|
+
}
|
|
250
|
+
socket.setTimeout(120000);
|
|
251
|
+
socket.pipe(to);
|
|
252
|
+
to.pipe(socket);
|
|
253
|
+
|
|
254
|
+
// Attach error and close handlers for both sockets.
|
|
255
|
+
socket.on('error', handleError('incoming'));
|
|
256
|
+
to.on('error', handleError('outgoing'));
|
|
257
|
+
socket.on('close', handleClose('incoming'));
|
|
258
|
+
to.on('close', handleClose('outgoing'));
|
|
259
|
+
socket.on('timeout', handleError('incoming'));
|
|
260
|
+
to.on('timeout', handleError('outgoing'));
|
|
261
|
+
socket.on('end', handleClose('incoming'));
|
|
262
|
+
to.on('end', handleClose('outgoing'));
|
|
128
263
|
};
|
|
129
264
|
|
|
130
|
-
//
|
|
131
|
-
if (this.settings.
|
|
132
|
-
|
|
265
|
+
// For SNI-enabled connections, peek at the first chunk.
|
|
266
|
+
if (this.settings.sniEnabled) {
|
|
267
|
+
socket.once('data', (chunk: Buffer) => {
|
|
268
|
+
// Try to extract the server name from the ClientHello.
|
|
269
|
+
const serverName = extractSNI(chunk) || '';
|
|
270
|
+
console.log(`Received connection from ${remoteIP} with SNI: ${serverName}`);
|
|
271
|
+
setupConnection(serverName, chunk);
|
|
272
|
+
});
|
|
273
|
+
} else {
|
|
274
|
+
// For non-SNI connections, simply check defaultAllowedIPs.
|
|
275
|
+
if (!this.settings.defaultAllowedIPs || !isAllowed(remoteIP, this.settings.defaultAllowedIPs)) {
|
|
276
|
+
console.log(`Connection rejected: IP ${remoteIP} not allowed for non-SNI connection`);
|
|
277
|
+
socket.end();
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
setupConnection('');
|
|
133
281
|
}
|
|
282
|
+
})
|
|
283
|
+
.on('error', (err: Error) => {
|
|
284
|
+
console.log(`Server Error: ${err.message}`);
|
|
285
|
+
})
|
|
286
|
+
.listen(this.settings.fromPort, () => {
|
|
287
|
+
console.log(`PortProxy -> OK: Now listening on port ${this.settings.fromPort}${this.settings.sniEnabled ? ' (SNI passthrough enabled)' : ''}`);
|
|
288
|
+
});
|
|
134
289
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
console.log(`
|
|
138
|
-
|
|
139
|
-
from.pipe(to);
|
|
140
|
-
to.pipe(from);
|
|
141
|
-
from.on('error', () => {
|
|
142
|
-
cleanUpSockets(from, to);
|
|
143
|
-
});
|
|
144
|
-
to.on('error', () => {
|
|
145
|
-
cleanUpSockets(from, to);
|
|
146
|
-
});
|
|
147
|
-
from.on('close', () => {
|
|
148
|
-
cleanUpSockets(from, to);
|
|
149
|
-
});
|
|
150
|
-
to.on('close', () => {
|
|
151
|
-
cleanUpSockets(from, to);
|
|
152
|
-
});
|
|
153
|
-
from.on('timeout', () => {
|
|
154
|
-
cleanUpSockets(from, to);
|
|
155
|
-
});
|
|
156
|
-
to.on('timeout', () => {
|
|
157
|
-
cleanUpSockets(from, to);
|
|
158
|
-
});
|
|
159
|
-
from.on('end', () => {
|
|
160
|
-
cleanUpSockets(from, to);
|
|
161
|
-
});
|
|
162
|
-
to.on('end', () => {
|
|
163
|
-
cleanUpSockets(from, to);
|
|
164
|
-
});
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
this.netServer = server
|
|
168
|
-
.on('connection', handleConnection)
|
|
169
|
-
.on('secureConnection', handleConnection)
|
|
170
|
-
.on('tlsClientError', (err, tlsSocket) => {
|
|
171
|
-
console.log(`TLS Client Error: ${err.message}`);
|
|
172
|
-
})
|
|
173
|
-
.on('error', (err) => {
|
|
174
|
-
console.log(`Server Error: ${err.message}`);
|
|
175
|
-
})
|
|
176
|
-
.listen(this.settings.fromPort);
|
|
177
|
-
console.log(`PortProxy -> OK: Now listening on port ${this.settings.fromPort}${this.settings.sniEnabled ? ' (SNI enabled)' : ''}`);
|
|
290
|
+
// Log active connection count every 10 seconds.
|
|
291
|
+
this.connectionLogger = setInterval(() => {
|
|
292
|
+
console.log(`(Interval Log) Active connections: ${this.activeConnections.size}`);
|
|
293
|
+
}, 10000);
|
|
178
294
|
}
|
|
179
295
|
|
|
180
296
|
public async stop() {
|
|
@@ -182,6 +298,10 @@ export class PortProxy {
|
|
|
182
298
|
this.netServer.close(() => {
|
|
183
299
|
done.resolve();
|
|
184
300
|
});
|
|
301
|
+
if (this.connectionLogger) {
|
|
302
|
+
clearInterval(this.connectionLogger);
|
|
303
|
+
this.connectionLogger = null;
|
|
304
|
+
}
|
|
185
305
|
await done.promise;
|
|
186
306
|
}
|
|
187
|
-
}
|
|
307
|
+
}
|