@push.rocks/smartproxy 3.7.1 → 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.1',
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 DomainConfig {
2
+ export interface IDomainConfig {
3
3
  domain: string;
4
4
  allowedIPs: string[];
5
5
  targetIP?: string;
6
6
  }
7
- export interface ProxySettings extends plugins.tls.TlsOptions {
7
+ export interface IProxySettings extends plugins.tls.TlsOptions {
8
8
  fromPort: number;
9
9
  toPort: number;
10
10
  toHost?: string;
11
- domains: DomainConfig[];
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 | plugins.tls.Server;
18
- settings: ProxySettings;
19
- constructor(settings: ProxySettings);
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 (ip.match(/^\d{1,3}(\.\d{1,3}){3}$/)) {
111
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
28
112
  return [ip, `::ffff:${ip}`];
29
113
  }
30
114
  return [ip];
@@ -38,103 +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
- const server = this.settings.sniEnabled
42
- ? plugins.tls.createServer({
43
- ...this.settings,
44
- SNICallback: (serverName, cb) => {
45
- console.log(`SNI request for domain: ${serverName}`);
46
- // For SNI passthrough, we don't need to create a context
47
- // Just acknowledge the SNI request and continue
48
- cb(null);
49
- }
50
- })
51
- : plugins.net.createServer();
52
- const handleConnection = (from) => {
53
- const remoteIP = from.remoteAddress || '';
54
- let serverName = '';
55
- // First check if this IP is in the default allowed list
56
- const isDefaultAllowed = this.settings.defaultAllowedIPs && isAllowed(remoteIP, this.settings.defaultAllowedIPs);
57
- if (this.settings.sniEnabled && from instanceof plugins.tls.TLSSocket) {
58
- serverName = from.servername || '';
59
- console.log(`TLS Connection from ${remoteIP} for domain: ${serverName}`);
60
- }
61
- // If IP is in defaultAllowedIPs, allow the connection regardless of SNI
62
- if (isDefaultAllowed) {
63
- console.log(`Connection allowed: IP ${remoteIP} is in default allowed list`);
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
+ });
64
187
  }
65
- else if (this.settings.sniEnabled && serverName) {
66
- // For SNI connections that aren't in default list, check domain-specific rules
67
- const domainConfig = findMatchingDomain(serverName);
68
- if (!domainConfig) {
69
- console.log(`Connection rejected: No matching domain config for ${serverName} from IP ${remoteIP}`);
70
- 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();
71
193
  return;
72
194
  }
73
- if (!isAllowed(remoteIP, domainConfig.allowedIPs)) {
74
- console.log(`Connection rejected: IP ${remoteIP} not allowed for domain ${serverName}`);
75
- from.end();
76
- return;
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:', '');
77
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);
78
219
  }
79
- else {
80
- // Non-SNI connection and not in default list
81
- console.log(`Connection rejected: IP ${remoteIP} not allowed for non-SNI connection`);
82
- from.end();
83
- return;
84
- }
85
- // Determine target host - use domain-specific targetIP if available
86
- const domainConfig = serverName ? findMatchingDomain(serverName) : undefined;
87
- const targetHost = domainConfig?.targetIP || this.settings.toHost;
88
- // Create connection, optionally preserving the client's source IP
89
- const connectionOptions = {
90
- host: targetHost,
91
- port: this.settings.toPort,
92
- };
93
- // Only set localAddress if preserveSourceIP is enabled
94
- if (this.settings.preserveSourceIP) {
95
- connectionOptions.localAddress = remoteIP.replace('::ffff:', ''); // Remove IPv6 mapping if present
96
- }
97
- const to = plugins.net.createConnection(connectionOptions);
98
- console.log(`Connection established: ${remoteIP} -> ${targetHost}:${this.settings.toPort}${serverName ? ` (SNI: ${serverName})` : ''}`);
99
- from.setTimeout(120000);
100
- from.pipe(to);
101
- to.pipe(from);
102
- from.on('error', () => {
103
- cleanUpSockets(from, to);
104
- });
105
- to.on('error', () => {
106
- cleanUpSockets(from, to);
107
- });
108
- from.on('close', () => {
109
- cleanUpSockets(from, to);
110
- });
111
- to.on('close', () => {
112
- cleanUpSockets(from, to);
113
- });
114
- from.on('timeout', () => {
115
- cleanUpSockets(from, to);
116
- });
117
- to.on('timeout', () => {
118
- cleanUpSockets(from, to);
119
- });
120
- from.on('end', () => {
121
- cleanUpSockets(from, to);
122
- });
123
- to.on('end', () => {
124
- cleanUpSockets(from, to);
125
- });
126
- };
127
- this.netServer = server
128
- .on('connection', handleConnection)
129
- .on('secureConnection', handleConnection)
130
- .on('tlsClientError', (err, tlsSocket) => {
131
- console.log(`TLS Client Error: ${err.message}`);
132
220
  })
133
221
  .on('error', (err) => {
134
222
  console.log(`Server Error: ${err.message}`);
135
223
  })
136
- .listen(this.settings.fromPort);
137
- console.log(`PortProxy -> OK: Now listening on port ${this.settings.fromPort}${this.settings.sniEnabled ? ' (SNI enabled)' : ''}`);
224
+ .listen(this.settings.fromPort, () => {
225
+ console.log(`PortProxy -> OK: Now listening on port ${this.settings.fromPort}${this.settings.sniEnabled ? ' (SNI passthrough enabled)' : ''}`);
226
+ });
138
227
  }
139
228
  async stop() {
140
229
  const done = plugins.smartpromise.defer();
@@ -144,4 +233,4 @@ export class PortProxy {
144
233
  await done.promise;
145
234
  }
146
235
  }
147
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRwcm94eS5wb3J0cHJveHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydHByb3h5LnBvcnRwcm94eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHlCQUF5QixDQUFDO0FBc0JuRCxNQUFNLE9BQU8sU0FBUztJQUlwQixZQUFZLFFBQXVCO1FBQ2pDLElBQUksQ0FBQyxRQUFRLEdBQUc7WUFDZCxHQUFHLFFBQVE7WUFDWCxNQUFNLEVBQUUsUUFBUSxDQUFDLE1BQU0sSUFBSSxXQUFXO1NBQ3ZDLENBQUM7SUFDSixDQUFDO0lBRU0sS0FBSyxDQUFDLEtBQUs7UUFDaEIsTUFBTSxjQUFjLEdBQUcsQ0FBQyxJQUF3QixFQUFFLEVBQXNCLEVBQUUsRUFBRTtZQUMxRSxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDWCxFQUFFLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDVCxJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztZQUMxQixFQUFFLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztZQUN4QixJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDZCxFQUFFLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDWixJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDZixFQUFFLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDZixDQUFDLENBQUM7UUFDRixNQUFNLFdBQVcsR0FBRyxDQUFDLEVBQVUsRUFBWSxFQUFFO1lBQzNDLG9DQUFvQztZQUNwQyxJQUFJLEVBQUUsQ0FBQyxVQUFVLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQztnQkFDN0IsTUFBTSxJQUFJLEdBQUcsRUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLDBCQUEwQjtnQkFDcEQsT0FBTyxDQUFDLEVBQUUsRUFBRSxJQUFJLENBQUMsQ0FBQztZQUNwQixDQUFDO1lBQ0QsMkRBQTJEO1lBQzNELElBQUksRUFBRSxDQUFD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MkJBQTJCLFFBQVEsT0FBTyxVQUFVLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxNQUFNLEdBQUcsVUFBVSxDQUFDLENBQUMsQ0FBQyxVQUFVLFVBQVUsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1lBQ3hJLElBQUksQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDeEIsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUNkLEVBQUUsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDZCxJQUFJLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxHQUFHLEVBQUU7Z0JBQ3BCLGNBQWMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDM0IsQ0FBQyxDQUFDLENBQUM7WUFDSCxFQUFFLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxHQUFHLEVBQUU7Z0JBQ2xCLGNBQWMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDM0IsQ0FBQyxDQUFDLENBQUM7WUFDSCxJQUFJLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxHQUFHLEVBQUU7Z0JBQ3BCLGNBQWMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDM0IsQ0FBQyxDQUFDLENBQUM7WUFDSCxFQUFFLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxHQUFHLEVBQUU7Z0JBQ2xCLGNBQWMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDM0IsQ0FBQyxDQUFDLENBQUM7WUFDSCxJQUFJLENBQUMsRUFBRSxDQUFDLFNBQVMsRUFBRSxHQUFHLEVBQUU7Z0JBQ3RCLGNBQWMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDM0IsQ0FBQyxDQUFDLENBQUM7WUFDSCxFQUFFLENBQUMsRUFBRSxDQUFDLFNBQVMsRUFBRSxHQUFHLEVBQUU7Z0JBQ3BCLGNBQWMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDM0IsQ0FBQyxDQUFDLENBQUM7WUFDSCxJQUFJLENBQUMsRUFBRSxDQUFDLEtBQUssRUFBRSxHQUFHLEVBQUU7Z0JBQ2xCLGNBQWMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDM0IsQ0FBQyxDQUFDLENBQUM7WUFDSCxFQUFFLENBQUMsRUFBRSxDQUFDLEtBQUssRUFBRSxHQUFHLEVBQUU7Z0JBQ2hCLGNBQWMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDM0IsQ0FBQyxDQUFDLENBQUM7UUFDTCxDQUFDLENBQUM7UUFFRixJQUFJLENBQUMsU0FBUyxHQUFHLE1BQU07YUFDcEIsRUFBRSxDQUFDLFlBQVksRUFBRSxnQkFBZ0IsQ0FBQzthQUNsQyxFQUFFLENBQUMsa0JBQWtCLEVBQUUsZ0JBQWdCLENBQUM7YUFDeEMsRUFBRSxDQUFDLGdCQUFnQixFQUFFLENBQUMsR0FBRyxFQUFFLFNBQVMsRUFBRSxFQUFFO1lBQ3ZDLE9BQU8sQ0FBQyxHQUFHLENBQUMscUJBQXFCLEdBQUcsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDO1FBQ2xELENBQUMsQ0FBQzthQUNELEVBQUUsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxHQUFHLEVBQUUsRUFBRTtZQUNuQixPQUFPLENBQUMsR0FBRyxDQUFDLGlCQUFpQixHQUFHLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztRQUM5QyxDQUFDLENBQUM7YUFDRCxNQUFNLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUNsQyxPQUFPLENBQUMsR0FBRyxDQUFDLDBDQUEwQyxJQUFJLENBQUMsUUFBUSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDckksQ0FBQztJQUVNLEtBQUssQ0FBQyxJQUFJO1FBQ2YsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUMxQyxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUU7WUFDeEIsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ2pCLENBQUMsQ0FBQyxDQUFDO1FBQ0gsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDO0lBQ3JCLENBQUM7Q0FDRiJ9
236
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRwcm94eS5wb3J0cHJveHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydHByb3h5LnBvcnRwcm94eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHlCQUF5QixDQUFDO0FBcUJuRDs7O0dBR0c7QUFDSCxTQUFTLFVBQVUsQ0FBQyxNQUFjO0lBQ2hDLElBQUksTUFBTSxHQUFHLENBQUMsQ0FBQztJQUNmLGtEQUFrRDtJQUNsRCxJQUFJLE1BQU0sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7UUFDdEIsT0FBTyxTQUFTLENBQUM7SUFDbkIsQ0FBQztJQUVELG9CQUFvQjtJQUNwQixNQUFNLFVBQVUsR0FBRyxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3ZDLElBQUksVUFBVSxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUMsaUJBQWlCO1FBQ3hDLE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7SUFDRCxxQkFBcUI7SUFDckIsTUFBTSxZQUFZLEdBQUcsTUFBTSxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM1QyxJQUFJLE1BQU0sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxHQUFHLFlBQVksRUFBRSxDQUFDO1FBQ3JDLGtGQUFrRjtRQUNsRixPQUFPLFNBQVMsQ0FBQztJQUNuQixDQUFDO0lBRUQsTUFBTSxHQUFHLENBQUMsQ0FBQztJQUNYLHNEQUFzRDtJQUN0RCxNQUFNLGFBQWEsR0FBRyxNQUFNLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQy9DLElBQUksYUFBYSxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ3hCLE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7SUFDRCx1REFBdUQ7SUFDdkQsTUFBTSxJQUFJLENBQUMsQ0FBQztJQUVaLHNEQUFzRDtJQUN0RCxNQUFNLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztJQUVqQixhQUFh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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartproxy",
3
- "version": "3.7.1",
3
+ "version": "3.7.3",
4
4
  "private": false,
5
5
  "description": "a proxy for handling high workloads of proxying",
6
6
  "main": "dist_ts/index.js",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartproxy',
6
- version: '3.7.1',
6
+ version: '3.7.3',
7
7
  description: 'a proxy for handling high workloads of proxying'
8
8
  }
@@ -1,30 +1,122 @@
1
1
  import * as plugins from './smartproxy.plugins.js';
2
2
 
3
-
4
- export interface DomainConfig {
5
- domain: string; // glob pattern for domain
6
- allowedIPs: string[]; // glob patterns for IPs allowed to access this domain
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 ProxySettings extends plugins.tls.TlsOptions {
9
+ export interface IProxySettings extends plugins.tls.TlsOptions {
11
10
  // Port configuration
12
11
  fromPort: number;
13
12
  toPort: number;
14
- toHost?: string; // Target host to proxy to, defaults to 'localhost'
13
+ toHost?: string; // Target host to proxy to, defaults to 'localhost'
15
14
 
16
15
  // Domain and security settings
17
- domains: DomainConfig[];
16
+ domains: IDomainConfig[];
18
17
  sniEnabled?: boolean;
19
- defaultAllowedIPs?: string[]; // Optional default IP patterns if no matching domain found
20
- preserveSourceIP?: boolean; // Whether to preserve the client's source IP when proxying
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 | plugins.tls.Server;
25
- settings: ProxySettings;
116
+ netServer: plugins.net.Server;
117
+ settings: IProxySettings;
26
118
 
27
- constructor(settings: ProxySettings) {
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 (ip.match(/^\d{1,3}(\.\d{1,3}){3}$/)) {
145
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
53
146
  return [ip, `::ffff:${ip}`];
54
147
  }
55
148
  return [ip];
@@ -59,119 +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): DomainConfig | undefined => {
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
- const server = this.settings.sniEnabled
72
- ? plugins.tls.createServer({
73
- ...this.settings,
74
- SNICallback: (serverName: string, cb: (err: Error | null, ctx?: plugins.tls.SecureContext) => void) => {
75
- console.log(`SNI request for domain: ${serverName}`);
76
- // For SNI passthrough, we don't need to create a context
77
- // Just acknowledge the SNI request and continue
78
- cb(null);
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`);
79
195
  }
80
- })
81
- : plugins.net.createServer();
82
-
83
- const handleConnection = (from: plugins.net.Socket | plugins.tls.TLSSocket) => {
84
- const remoteIP = from.remoteAddress || '';
85
- let serverName = '';
86
-
87
- // First check if this IP is in the default allowed list
88
- const isDefaultAllowed = this.settings.defaultAllowedIPs && isAllowed(remoteIP, this.settings.defaultAllowedIPs);
89
-
90
- if (this.settings.sniEnabled && from instanceof plugins.tls.TLSSocket) {
91
- serverName = (from as any).servername || '';
92
- console.log(`TLS Connection from ${remoteIP} for domain: ${serverName}`);
93
- }
94
196
 
95
- // If IP is in defaultAllowedIPs, allow the connection regardless of SNI
96
- if (isDefaultAllowed) {
97
- console.log(`Connection allowed: IP ${remoteIP} is in default allowed list`);
98
- } else if (this.settings.sniEnabled && serverName) {
99
- // For SNI connections that aren't in default list, check domain-specific rules
100
- const domainConfig = findMatchingDomain(serverName);
101
- if (!domainConfig) {
102
- console.log(`Connection rejected: No matching domain config for ${serverName} from IP ${remoteIP}`);
103
- from.end();
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();
104
236
  return;
105
237
  }
106
- if (!isAllowed(remoteIP, domainConfig.allowedIPs)) {
107
- console.log(`Connection rejected: IP ${remoteIP} not allowed for domain ${serverName}`);
108
- from.end();
109
- return;
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:', '');
110
245
  }
111
- } else {
112
- // Non-SNI connection and not in default list
113
- console.log(`Connection rejected: IP ${remoteIP} not allowed for non-SNI connection`);
114
- from.end();
115
- return;
116
- }
117
-
118
- // Determine target host - use domain-specific targetIP if available
119
- const domainConfig = serverName ? findMatchingDomain(serverName) : undefined;
120
- const targetHost = domainConfig?.targetIP || this.settings.toHost!;
121
-
122
- // Create connection, optionally preserving the client's source IP
123
- const connectionOptions: plugins.net.NetConnectOpts = {
124
- host: targetHost,
125
- port: this.settings.toPort,
126
- };
127
-
128
- // Only set localAddress if preserveSourceIP is enabled
129
- if (this.settings.preserveSourceIP) {
130
- 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);
131
262
  }
132
-
133
- const to = plugins.net.createConnection(connectionOptions);
134
- console.log(`Connection established: ${remoteIP} -> ${targetHost}:${this.settings.toPort}${serverName ? ` (SNI: ${serverName})` : ''}`);
135
- from.setTimeout(120000);
136
- from.pipe(to);
137
- to.pipe(from);
138
- from.on('error', () => {
139
- cleanUpSockets(from, to);
140
- });
141
- to.on('error', () => {
142
- cleanUpSockets(from, to);
143
- });
144
- from.on('close', () => {
145
- cleanUpSockets(from, to);
146
- });
147
- to.on('close', () => {
148
- cleanUpSockets(from, to);
149
- });
150
- from.on('timeout', () => {
151
- cleanUpSockets(from, to);
152
- });
153
- to.on('timeout', () => {
154
- cleanUpSockets(from, to);
155
- });
156
- from.on('end', () => {
157
- cleanUpSockets(from, to);
158
- });
159
- to.on('end', () => {
160
- cleanUpSockets(from, to);
161
- });
162
- };
163
-
164
- this.netServer = server
165
- .on('connection', handleConnection)
166
- .on('secureConnection', handleConnection)
167
- .on('tlsClientError', (err, tlsSocket) => {
168
- console.log(`TLS Client Error: ${err.message}`);
169
- })
170
- .on('error', (err) => {
171
- console.log(`Server Error: ${err.message}`);
172
- })
173
- .listen(this.settings.fromPort);
174
- 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
+ });
175
270
  }
176
271
 
177
272
  public async stop() {
@@ -181,4 +276,4 @@ export class PortProxy {
181
276
  });
182
277
  await done.promise;
183
278
  }
184
- }
279
+ }