@push.rocks/smartproxy 3.7.2 → 3.7.3
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.7.
|
|
6
|
+
version: '3.7.3',
|
|
7
7
|
description: 'a proxy for handling high workloads of proxying'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLGlEQUFpRDtDQUMvRCxDQUFBIn0=
|
|
@@ -1,22 +1,22 @@
|
|
|
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
|
-
constructor(settings:
|
|
17
|
+
netServer: plugins.net.Server;
|
|
18
|
+
settings: IProxySettings;
|
|
19
|
+
constructor(settings: IProxySettings);
|
|
20
20
|
start(): Promise<void>;
|
|
21
21
|
stop(): Promise<void>;
|
|
22
22
|
}
|
|
@@ -1,4 +1,88 @@
|
|
|
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) {
|
|
4
88
|
this.settings = {
|
|
@@ -24,7 +108,7 @@ export class PortProxy {
|
|
|
24
108
|
return [ip, ipv4];
|
|
25
109
|
}
|
|
26
110
|
// Handle IPv4 addresses by adding IPv4-mapped IPv6 variant
|
|
27
|
-
if (
|
|
111
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
|
|
28
112
|
return [ip, `::ffff:${ip}`];
|
|
29
113
|
}
|
|
30
114
|
return [ip];
|
|
@@ -38,106 +122,108 @@ export class PortProxy {
|
|
|
38
122
|
const findMatchingDomain = (serverName) => {
|
|
39
123
|
return this.settings.domains.find(config => plugins.minimatch(serverName, config.domain));
|
|
40
124
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
125
|
+
// Always create a plain net server for TLS passthrough.
|
|
126
|
+
this.netServer = plugins.net.createServer((socket) => {
|
|
127
|
+
const remoteIP = socket.remoteAddress || '';
|
|
128
|
+
// If SNI is enabled, we peek at the first chunk to extract the SNI.
|
|
129
|
+
if (this.settings.sniEnabled) {
|
|
130
|
+
socket.once('data', (chunk) => {
|
|
131
|
+
// Try to extract the server name from the ClientHello.
|
|
132
|
+
const serverName = extractSNI(chunk) || '';
|
|
133
|
+
console.log(`Received connection from ${remoteIP} with SNI: ${serverName}`);
|
|
134
|
+
// Check if the IP is allowed by default.
|
|
135
|
+
const isDefaultAllowed = this.settings.defaultAllowedIPs && isAllowed(remoteIP, this.settings.defaultAllowedIPs);
|
|
136
|
+
if (!isDefaultAllowed && serverName) {
|
|
137
|
+
const domainConfig = findMatchingDomain(serverName);
|
|
138
|
+
if (!domainConfig) {
|
|
139
|
+
console.log(`Connection rejected: No matching domain config for ${serverName} from IP ${remoteIP}`);
|
|
140
|
+
socket.end();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (!isAllowed(remoteIP, domainConfig.allowedIPs)) {
|
|
144
|
+
console.log(`Connection rejected: IP ${remoteIP} not allowed for domain ${serverName}`);
|
|
145
|
+
socket.end();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else if (!isDefaultAllowed && !serverName) {
|
|
150
|
+
console.log(`Connection rejected: No SNI and IP ${remoteIP} not in default allowed list`);
|
|
151
|
+
socket.end();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
console.log(`Connection allowed: IP ${remoteIP} is in default allowed list`);
|
|
156
|
+
}
|
|
157
|
+
// Determine target host.
|
|
158
|
+
const domainConfig = serverName ? findMatchingDomain(serverName) : undefined;
|
|
159
|
+
const targetHost = domainConfig?.targetIP || this.settings.toHost;
|
|
160
|
+
// Create connection options.
|
|
161
|
+
const connectionOptions = {
|
|
162
|
+
host: targetHost,
|
|
163
|
+
port: this.settings.toPort,
|
|
164
|
+
};
|
|
165
|
+
if (this.settings.preserveSourceIP) {
|
|
166
|
+
connectionOptions.localAddress = remoteIP.replace('::ffff:', '');
|
|
167
|
+
}
|
|
168
|
+
const to = plugins.net.connect(connectionOptions);
|
|
169
|
+
console.log(`Connection established: ${remoteIP} -> ${targetHost}:${this.settings.toPort}${serverName ? ` (SNI: ${serverName})` : ''}`);
|
|
170
|
+
// Unshift the data chunk back so that the TLS handshake can complete at the backend.
|
|
171
|
+
socket.unshift(chunk);
|
|
172
|
+
socket.setTimeout(120000);
|
|
173
|
+
socket.pipe(to);
|
|
174
|
+
to.pipe(socket);
|
|
175
|
+
const errorHandler = () => {
|
|
176
|
+
cleanUpSockets(socket, to);
|
|
177
|
+
};
|
|
178
|
+
socket.on('error', errorHandler);
|
|
179
|
+
to.on('error', errorHandler);
|
|
180
|
+
socket.on('close', errorHandler);
|
|
181
|
+
to.on('close', errorHandler);
|
|
182
|
+
socket.on('timeout', errorHandler);
|
|
183
|
+
to.on('timeout', errorHandler);
|
|
184
|
+
socket.on('end', errorHandler);
|
|
185
|
+
to.on('end', errorHandler);
|
|
186
|
+
});
|
|
66
187
|
}
|
|
67
|
-
else
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
from.end();
|
|
188
|
+
else {
|
|
189
|
+
// If SNI is not enabled, use defaultAllowedIPs check.
|
|
190
|
+
if (!this.settings.defaultAllowedIPs || !isAllowed(remoteIP, this.settings.defaultAllowedIPs)) {
|
|
191
|
+
console.log(`Connection rejected: IP ${remoteIP} not allowed for non-SNI connection`);
|
|
192
|
+
socket.end();
|
|
73
193
|
return;
|
|
74
194
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
195
|
+
const targetHost = 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:', '');
|
|
79
202
|
}
|
|
203
|
+
const to = plugins.net.connect(connectionOptions);
|
|
204
|
+
console.log(`Connection established: ${remoteIP} -> ${targetHost}:${this.settings.toPort}`);
|
|
205
|
+
socket.setTimeout(120000);
|
|
206
|
+
socket.pipe(to);
|
|
207
|
+
to.pipe(socket);
|
|
208
|
+
const errorHandler = () => {
|
|
209
|
+
cleanUpSockets(socket, to);
|
|
210
|
+
};
|
|
211
|
+
socket.on('error', errorHandler);
|
|
212
|
+
to.on('error', errorHandler);
|
|
213
|
+
socket.on('close', errorHandler);
|
|
214
|
+
to.on('close', errorHandler);
|
|
215
|
+
socket.on('timeout', errorHandler);
|
|
216
|
+
to.on('timeout', errorHandler);
|
|
217
|
+
socket.on('end', errorHandler);
|
|
218
|
+
to.on('end', errorHandler);
|
|
80
219
|
}
|
|
81
|
-
else {
|
|
82
|
-
// Non-SNI connection and not in default list
|
|
83
|
-
console.log(`Connection rejected: IP ${remoteIP} not allowed for non-SNI connection`);
|
|
84
|
-
from.end();
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
// Determine target host - use domain-specific targetIP if available
|
|
88
|
-
const domainConfig = serverName ? findMatchingDomain(serverName) : undefined;
|
|
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
|
|
98
|
-
}
|
|
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
220
|
})
|
|
136
221
|
.on('error', (err) => {
|
|
137
222
|
console.log(`Server Error: ${err.message}`);
|
|
138
223
|
})
|
|
139
|
-
.listen(this.settings.fromPort)
|
|
140
|
-
|
|
224
|
+
.listen(this.settings.fromPort, () => {
|
|
225
|
+
console.log(`PortProxy -> OK: Now listening on port ${this.settings.fromPort}${this.settings.sniEnabled ? ' (SNI passthrough enabled)' : ''}`);
|
|
226
|
+
});
|
|
141
227
|
}
|
|
142
228
|
async stop() {
|
|
143
229
|
const done = plugins.smartpromise.defer();
|
|
@@ -147,4 +233,4 @@ export class PortProxy {
|
|
|
147
233
|
await done.promise;
|
|
148
234
|
}
|
|
149
235
|
}
|
|
150
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRwcm94eS5wb3J0cHJveHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydHByb3h5LnBvcnRwcm94eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHlCQUF5QixDQUFDO0FBc0JuRCxNQUFNLE9BQU8sU0FBUztJQUlwQixZQUFZLFFBQXVCO1FBQ2pDLElBQUksQ0FBQyxRQUFRLEdBQUc7WUFDZCxHQUFHLFFBQVE7WUFDWCxNQUFNLEVBQUUsUUFBUSxDQUFDLE1BQU0sSUFBSSxXQUFXO1NBQ3ZDLENBQUM7SUFDSixDQUFDO0lBRU0sS0FBSyxDQUFDLEtBQUs7UUFDaEIsTUFBTSxjQUFjLEdBQUcsQ0FBQyxJQUF3QixFQUFFLEVBQXNCLEVBQUUsRUFBRTtZQUMxRSxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDWCxFQUFFLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDVCxJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztZQUMxQixFQUFFLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztZQUN4QixJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDZCxFQUFFLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDWixJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDZixFQUFFLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDZixDQUFDLENBQUM7UUFDRixNQUFNLFdBQVcsR0FBRyxDQUFDLEVBQVUsRUFBWSxFQUFFO1lBQzNDLG9DQUFvQztZQUNwQyxJQUFJLEVBQUUsQ0FBQyxVQUFVLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQztnQkFDN0IsTUFBTSxJQUFJLEdBQUcsRUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLDBCQUEwQjtnQkFDcEQsT0FBTyxDQUFDLEVBQUUsRUFBRSxJQUFJLENBQUMsQ0FBQztZQUNwQixDQUFDO1lBQ0QsMkRBQTJEO1lBQzNELElBQUksRUFBRSxDQUFD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
|
|
236
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRwcm94eS5wb3J0cHJveHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydHByb3h5LnBvcnRwcm94eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHlCQUF5QixDQUFDO0FBcUJuRDs7O0dBR0c7QUFDSCxTQUFTLFVBQVUsQ0FBQyxNQUFjO0lBQ2hDLElBQUksTUFBTSxHQUFHLENBQUMsQ0FBQztJQUNmLGtEQUFrRDtJQUNsRCxJQUFJLE1BQU0sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7UUFDdEIsT0FBTyxTQUFTLENBQUM7SUFDbkIsQ0FBQztJQUVELG9CQUFvQjtJQUNwQixNQUFNLFVBQVUsR0FBRyxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3ZDLElBQUksVUFBVSxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUMsaUJBQWlCO1FBQ3hDLE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7SUFDRCxxQkFBcUI7SUFDckIsTUFBTSxZQUFZLEdBQUcsTUFBTSxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM1QyxJQUFJLE1BQU0sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxHQUFHLFlBQVksRUFBRSxDQUFDO1FBQ3JDLGtGQUFrRjtRQUNsRixPQUFPLFNBQVMsQ0FBQztJQUNuQixDQUFDO0lBRUQsTUFBTSxHQUFHLENBQUMsQ0FBQztJQUNYLHNEQUFzRDtJQUN0RCxNQUFNLGFBQWEsR0FBRyxNQUFNLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQy9DLElBQUksYUFBYSxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ3hCLE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7SUFDRCx1REFBdUQ7SUFDdkQsTUFBTSxJQUFJLENBQUMsQ0FBQztJQUVaLHNEQUFzRDtJQUN0RCxNQUFNLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztJQUVqQixhQUFh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
|
package/package.json
CHANGED
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -1,30 +1,122 @@
|
|
|
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;
|
|
26
118
|
|
|
27
|
-
constructor(settings:
|
|
119
|
+
constructor(settings: IProxySettings) {
|
|
28
120
|
this.settings = {
|
|
29
121
|
...settings,
|
|
30
122
|
toHost: settings.toHost || 'localhost'
|
|
@@ -42,6 +134,7 @@ export class PortProxy {
|
|
|
42
134
|
from.destroy();
|
|
43
135
|
to.destroy();
|
|
44
136
|
};
|
|
137
|
+
|
|
45
138
|
const normalizeIP = (ip: string): string[] => {
|
|
46
139
|
// Handle IPv4-mapped IPv6 addresses
|
|
47
140
|
if (ip.startsWith('::ffff:')) {
|
|
@@ -49,7 +142,7 @@ export class PortProxy {
|
|
|
49
142
|
return [ip, ipv4];
|
|
50
143
|
}
|
|
51
144
|
// Handle IPv4 addresses by adding IPv4-mapped IPv6 variant
|
|
52
|
-
if (
|
|
145
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
|
|
53
146
|
return [ip, `::ffff:${ip}`];
|
|
54
147
|
}
|
|
55
148
|
return [ip];
|
|
@@ -59,122 +152,121 @@ export class PortProxy {
|
|
|
59
152
|
// Expand patterns to include both IPv4 and IPv6 variants
|
|
60
153
|
const expandedPatterns = patterns.flatMap(normalizeIP);
|
|
61
154
|
// Check if any variant of the IP matches any expanded pattern
|
|
62
|
-
return normalizeIP(value).some(ip =>
|
|
155
|
+
return normalizeIP(value).some(ip =>
|
|
63
156
|
expandedPatterns.some(pattern => plugins.minimatch(ip, pattern))
|
|
64
157
|
);
|
|
65
158
|
};
|
|
66
159
|
|
|
67
|
-
const findMatchingDomain = (serverName: string):
|
|
160
|
+
const findMatchingDomain = (serverName: string): IDomainConfig | undefined => {
|
|
68
161
|
return this.settings.domains.find(config => plugins.minimatch(serverName, config.domain));
|
|
69
162
|
};
|
|
70
163
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
164
|
+
// Always create a plain net server for TLS passthrough.
|
|
165
|
+
this.netServer = plugins.net.createServer((socket: plugins.net.Socket) => {
|
|
166
|
+
const remoteIP = socket.remoteAddress || '';
|
|
167
|
+
|
|
168
|
+
// If SNI is enabled, we peek at the first chunk to extract the SNI.
|
|
169
|
+
if (this.settings.sniEnabled) {
|
|
170
|
+
socket.once('data', (chunk: Buffer) => {
|
|
171
|
+
// Try to extract the server name from the ClientHello.
|
|
172
|
+
const serverName = extractSNI(chunk) || '';
|
|
173
|
+
console.log(`Received connection from ${remoteIP} with SNI: ${serverName}`);
|
|
174
|
+
|
|
175
|
+
// Check if the IP is allowed by default.
|
|
176
|
+
const isDefaultAllowed = this.settings.defaultAllowedIPs && isAllowed(remoteIP, this.settings.defaultAllowedIPs);
|
|
177
|
+
if (!isDefaultAllowed && serverName) {
|
|
178
|
+
const domainConfig = findMatchingDomain(serverName);
|
|
179
|
+
if (!domainConfig) {
|
|
180
|
+
console.log(`Connection rejected: No matching domain config for ${serverName} from IP ${remoteIP}`);
|
|
181
|
+
socket.end();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (!isAllowed(remoteIP, domainConfig.allowedIPs)) {
|
|
185
|
+
console.log(`Connection rejected: IP ${remoteIP} not allowed for domain ${serverName}`);
|
|
186
|
+
socket.end();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
} else if (!isDefaultAllowed && !serverName) {
|
|
190
|
+
console.log(`Connection rejected: No SNI and IP ${remoteIP} not in default allowed list`);
|
|
191
|
+
socket.end();
|
|
192
|
+
return;
|
|
193
|
+
} else {
|
|
194
|
+
console.log(`Connection allowed: IP ${remoteIP} is in default allowed list`);
|
|
81
195
|
}
|
|
82
|
-
})
|
|
83
|
-
: plugins.net.createServer();
|
|
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
|
-
}
|
|
96
196
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
197
|
+
// Determine target host.
|
|
198
|
+
const domainConfig = serverName ? findMatchingDomain(serverName) : undefined;
|
|
199
|
+
const targetHost = domainConfig?.targetIP || this.settings.toHost!;
|
|
200
|
+
|
|
201
|
+
// Create connection options.
|
|
202
|
+
const connectionOptions: plugins.net.NetConnectOpts = {
|
|
203
|
+
host: targetHost,
|
|
204
|
+
port: this.settings.toPort,
|
|
205
|
+
};
|
|
206
|
+
if (this.settings.preserveSourceIP) {
|
|
207
|
+
connectionOptions.localAddress = remoteIP.replace('::ffff:', '');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const to = plugins.net.connect(connectionOptions);
|
|
211
|
+
console.log(`Connection established: ${remoteIP} -> ${targetHost}:${this.settings.toPort}${serverName ? ` (SNI: ${serverName})` : ''}`);
|
|
212
|
+
|
|
213
|
+
// Unshift the data chunk back so that the TLS handshake can complete at the backend.
|
|
214
|
+
socket.unshift(chunk);
|
|
215
|
+
socket.setTimeout(120000);
|
|
216
|
+
socket.pipe(to);
|
|
217
|
+
to.pipe(socket);
|
|
218
|
+
|
|
219
|
+
const errorHandler = () => {
|
|
220
|
+
cleanUpSockets(socket, to);
|
|
221
|
+
};
|
|
222
|
+
socket.on('error', errorHandler);
|
|
223
|
+
to.on('error', errorHandler);
|
|
224
|
+
socket.on('close', errorHandler);
|
|
225
|
+
to.on('close', errorHandler);
|
|
226
|
+
socket.on('timeout', errorHandler);
|
|
227
|
+
to.on('timeout', errorHandler);
|
|
228
|
+
socket.on('end', errorHandler);
|
|
229
|
+
to.on('end', errorHandler);
|
|
230
|
+
});
|
|
231
|
+
} else {
|
|
232
|
+
// If SNI is not enabled, use defaultAllowedIPs check.
|
|
233
|
+
if (!this.settings.defaultAllowedIPs || !isAllowed(remoteIP, this.settings.defaultAllowedIPs)) {
|
|
234
|
+
console.log(`Connection rejected: IP ${remoteIP} not allowed for non-SNI connection`);
|
|
235
|
+
socket.end();
|
|
106
236
|
return;
|
|
107
237
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
238
|
+
const targetHost = this.settings.toHost!;
|
|
239
|
+
const connectionOptions: plugins.net.NetConnectOpts = {
|
|
240
|
+
host: targetHost,
|
|
241
|
+
port: this.settings.toPort,
|
|
242
|
+
};
|
|
243
|
+
if (this.settings.preserveSourceIP) {
|
|
244
|
+
connectionOptions.localAddress = remoteIP.replace('::ffff:', '');
|
|
112
245
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// Only set localAddress if preserveSourceIP is enabled
|
|
131
|
-
if (this.settings.preserveSourceIP) {
|
|
132
|
-
connectionOptions.localAddress = remoteIP.replace('::ffff:', ''); // Remove IPv6 mapping if present
|
|
246
|
+
const to = plugins.net.connect(connectionOptions);
|
|
247
|
+
console.log(`Connection established: ${remoteIP} -> ${targetHost}:${this.settings.toPort}`);
|
|
248
|
+
socket.setTimeout(120000);
|
|
249
|
+
socket.pipe(to);
|
|
250
|
+
to.pipe(socket);
|
|
251
|
+
const errorHandler = () => {
|
|
252
|
+
cleanUpSockets(socket, to);
|
|
253
|
+
};
|
|
254
|
+
socket.on('error', errorHandler);
|
|
255
|
+
to.on('error', errorHandler);
|
|
256
|
+
socket.on('close', errorHandler);
|
|
257
|
+
to.on('close', errorHandler);
|
|
258
|
+
socket.on('timeout', errorHandler);
|
|
259
|
+
to.on('timeout', errorHandler);
|
|
260
|
+
socket.on('end', errorHandler);
|
|
261
|
+
to.on('end', errorHandler);
|
|
133
262
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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)' : ''}`);
|
|
263
|
+
})
|
|
264
|
+
.on('error', (err: Error) => {
|
|
265
|
+
console.log(`Server Error: ${err.message}`);
|
|
266
|
+
})
|
|
267
|
+
.listen(this.settings.fromPort, () => {
|
|
268
|
+
console.log(`PortProxy -> OK: Now listening on port ${this.settings.fromPort}${this.settings.sniEnabled ? ' (SNI passthrough enabled)' : ''}`);
|
|
269
|
+
});
|
|
178
270
|
}
|
|
179
271
|
|
|
180
272
|
public async stop() {
|
|
@@ -184,4 +276,4 @@ export class PortProxy {
|
|
|
184
276
|
});
|
|
185
277
|
await done.promise;
|
|
186
278
|
}
|
|
187
|
-
}
|
|
279
|
+
}
|