@push.rocks/smartproxy 19.5.10 → 19.5.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/core/utils/socket-utils.d.ts +13 -0
- package/dist_ts/core/utils/socket-utils.js +29 -1
- package/dist_ts/forwarding/handlers/https-passthrough-handler.js +91 -62
- package/dist_ts/forwarding/handlers/https-terminate-to-http-handler.js +32 -12
- package/dist_ts/forwarding/handlers/https-terminate-to-https-handler.js +2 -2
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +226 -196
- package/package.json +1 -1
- package/readme.hints.md +52 -1
- package/readme.plan.md +124 -296
- package/ts/core/utils/socket-utils.ts +43 -0
- package/ts/forwarding/handlers/https-passthrough-handler.ts +104 -73
- package/ts/forwarding/handlers/https-terminate-to-http-handler.ts +35 -13
- package/ts/forwarding/handlers/https-terminate-to-https-handler.ts +1 -1
- package/ts/proxies/smart-proxy/route-connection-handler.ts +47 -7
|
@@ -2,7 +2,7 @@ import * as plugins from '../../plugins.js';
|
|
|
2
2
|
import { ForwardingHandler } from './base-handler.js';
|
|
3
3
|
import type { IForwardConfig } from '../config/forwarding-types.js';
|
|
4
4
|
import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
|
|
5
|
-
import { createIndependentSocketHandlers, setupSocketHandlers } from '../../core/utils/socket-utils.js';
|
|
5
|
+
import { createIndependentSocketHandlers, setupSocketHandlers, createSocketWithErrorHandler } from '../../core/utils/socket-utils.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Handler for HTTPS passthrough (SNI forwarding without termination)
|
|
@@ -48,91 +48,122 @@ export class HttpsPassthroughHandler extends ForwardingHandler {
|
|
|
48
48
|
target: `${target.host}:${target.port}`
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
// Create a connection to the target server
|
|
52
|
-
const serverSocket = plugins.net.connect(target.port, target.host);
|
|
53
|
-
|
|
54
51
|
// Track data transfer for logging
|
|
55
52
|
let bytesSent = 0;
|
|
56
53
|
let bytesReceived = 0;
|
|
54
|
+
let serverSocket: plugins.net.Socket | null = null;
|
|
55
|
+
let cleanupClient: ((reason: string) => Promise<void>) | null = null;
|
|
56
|
+
let cleanupServer: ((reason: string) => Promise<void>) | null = null;
|
|
57
57
|
|
|
58
|
-
// Create
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
(
|
|
58
|
+
// Create a connection to the target server with immediate error handling
|
|
59
|
+
serverSocket = createSocketWithErrorHandler({
|
|
60
|
+
port: target.port,
|
|
61
|
+
host: target.host,
|
|
62
|
+
onError: async (error) => {
|
|
63
|
+
// Server connection failed - clean up client socket immediately
|
|
64
|
+
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
65
|
+
error: error.message,
|
|
66
|
+
code: (error as any).code || 'UNKNOWN',
|
|
67
|
+
remoteAddress,
|
|
68
|
+
target: `${target.host}:${target.port}`
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Clean up the client socket since we can't forward
|
|
72
|
+
if (!clientSocket.destroyed) {
|
|
73
|
+
clientSocket.destroy();
|
|
74
|
+
}
|
|
75
|
+
|
|
63
76
|
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
|
64
77
|
remoteAddress,
|
|
65
|
-
bytesSent,
|
|
66
|
-
bytesReceived,
|
|
67
|
-
reason
|
|
78
|
+
bytesSent: 0,
|
|
79
|
+
bytesReceived: 0,
|
|
80
|
+
reason: `server_connection_failed: ${error.message}`
|
|
68
81
|
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
bytesSent += data.length;
|
|
88
|
-
|
|
89
|
-
// Check if server socket is writable
|
|
90
|
-
if (serverSocket.writable) {
|
|
91
|
-
const flushed = serverSocket.write(data);
|
|
82
|
+
},
|
|
83
|
+
onConnect: () => {
|
|
84
|
+
// Connection successful - set up forwarding handlers
|
|
85
|
+
const handlers = createIndependentSocketHandlers(
|
|
86
|
+
clientSocket,
|
|
87
|
+
serverSocket!,
|
|
88
|
+
(reason) => {
|
|
89
|
+
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
|
90
|
+
remoteAddress,
|
|
91
|
+
bytesSent,
|
|
92
|
+
bytesReceived,
|
|
93
|
+
reason
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
cleanupClient = handlers.cleanupClient;
|
|
99
|
+
cleanupServer = handlers.cleanupServer;
|
|
92
100
|
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
101
|
+
// Setup handlers with custom timeout handling that doesn't close connections
|
|
102
|
+
const timeout = this.getTimeout();
|
|
103
|
+
|
|
104
|
+
setupSocketHandlers(clientSocket, cleanupClient, (socket) => {
|
|
105
|
+
// Just reset timeout, don't close
|
|
106
|
+
socket.setTimeout(timeout);
|
|
107
|
+
}, 'client');
|
|
108
|
+
|
|
109
|
+
setupSocketHandlers(serverSocket!, cleanupServer, (socket) => {
|
|
110
|
+
// Just reset timeout, don't close
|
|
111
|
+
socket.setTimeout(timeout);
|
|
112
|
+
}, 'server');
|
|
113
|
+
|
|
114
|
+
// Forward data from client to server
|
|
115
|
+
clientSocket.on('data', (data) => {
|
|
116
|
+
bytesSent += data.length;
|
|
117
|
+
|
|
118
|
+
// Check if server socket is writable
|
|
119
|
+
if (serverSocket && serverSocket.writable) {
|
|
120
|
+
const flushed = serverSocket.write(data);
|
|
121
|
+
|
|
122
|
+
// Handle backpressure
|
|
123
|
+
if (!flushed) {
|
|
124
|
+
clientSocket.pause();
|
|
125
|
+
serverSocket.once('drain', () => {
|
|
126
|
+
clientSocket.resume();
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.emit(ForwardingHandlerEvents.DATA_FORWARDED, {
|
|
132
|
+
direction: 'outbound',
|
|
133
|
+
bytes: data.length,
|
|
134
|
+
total: bytesSent
|
|
98
135
|
});
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
this.emit(ForwardingHandlerEvents.DATA_FORWARDED, {
|
|
103
|
-
direction: 'outbound',
|
|
104
|
-
bytes: data.length,
|
|
105
|
-
total: bytesSent
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// Forward data from server to client
|
|
110
|
-
serverSocket.on('data', (data) => {
|
|
111
|
-
bytesReceived += data.length;
|
|
112
|
-
|
|
113
|
-
// Check if client socket is writable
|
|
114
|
-
if (clientSocket.writable) {
|
|
115
|
-
const flushed = clientSocket.write(data);
|
|
136
|
+
});
|
|
116
137
|
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
138
|
+
// Forward data from server to client
|
|
139
|
+
serverSocket!.on('data', (data) => {
|
|
140
|
+
bytesReceived += data.length;
|
|
141
|
+
|
|
142
|
+
// Check if client socket is writable
|
|
143
|
+
if (clientSocket.writable) {
|
|
144
|
+
const flushed = clientSocket.write(data);
|
|
145
|
+
|
|
146
|
+
// Handle backpressure
|
|
147
|
+
if (!flushed) {
|
|
148
|
+
serverSocket!.pause();
|
|
149
|
+
clientSocket.once('drain', () => {
|
|
150
|
+
serverSocket!.resume();
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
this.emit(ForwardingHandlerEvents.DATA_FORWARDED, {
|
|
156
|
+
direction: 'inbound',
|
|
157
|
+
bytes: data.length,
|
|
158
|
+
total: bytesReceived
|
|
122
159
|
});
|
|
123
|
-
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Set initial timeouts - they will be reset on each timeout event
|
|
163
|
+
clientSocket.setTimeout(timeout);
|
|
164
|
+
serverSocket!.setTimeout(timeout);
|
|
124
165
|
}
|
|
125
|
-
|
|
126
|
-
this.emit(ForwardingHandlerEvents.DATA_FORWARDED, {
|
|
127
|
-
direction: 'inbound',
|
|
128
|
-
bytes: data.length,
|
|
129
|
-
total: bytesReceived
|
|
130
|
-
});
|
|
131
166
|
});
|
|
132
|
-
|
|
133
|
-
// Set initial timeouts - they will be reset on each timeout event
|
|
134
|
-
clientSocket.setTimeout(timeout);
|
|
135
|
-
serverSocket.setTimeout(timeout);
|
|
136
167
|
}
|
|
137
168
|
|
|
138
169
|
/**
|
|
@@ -2,7 +2,7 @@ import * as plugins from '../../plugins.js';
|
|
|
2
2
|
import { ForwardingHandler } from './base-handler.js';
|
|
3
3
|
import type { IForwardConfig } from '../config/forwarding-types.js';
|
|
4
4
|
import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
|
|
5
|
-
import { createSocketCleanupHandler, setupSocketHandlers } from '../../core/utils/socket-utils.js';
|
|
5
|
+
import { createSocketCleanupHandler, setupSocketHandlers, createSocketWithErrorHandler } from '../../core/utils/socket-utils.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Handler for HTTPS termination with HTTP backend
|
|
@@ -141,19 +141,41 @@ export class HttpsTerminateToHttpHandler extends ForwardingHandler {
|
|
|
141
141
|
if (dataBuffer.includes(Buffer.from('\r\n\r\n')) && !connectionEstablished) {
|
|
142
142
|
const target = this.getTargetFromConfig();
|
|
143
143
|
|
|
144
|
-
// Create backend connection
|
|
145
|
-
backendSocket =
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
144
|
+
// Create backend connection with immediate error handling
|
|
145
|
+
backendSocket = createSocketWithErrorHandler({
|
|
146
|
+
port: target.port,
|
|
147
|
+
host: target.host,
|
|
148
|
+
onError: (error) => {
|
|
149
|
+
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
150
|
+
error: error.message,
|
|
151
|
+
code: (error as any).code || 'UNKNOWN',
|
|
152
|
+
remoteAddress,
|
|
153
|
+
target: `${target.host}:${target.port}`
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Clean up the TLS socket since we can't forward
|
|
157
|
+
if (!tlsSocket.destroyed) {
|
|
158
|
+
tlsSocket.destroy();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
|
162
|
+
remoteAddress,
|
|
163
|
+
reason: `backend_connection_failed: ${error.message}`
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
onConnect: () => {
|
|
167
|
+
connectionEstablished = true;
|
|
168
|
+
|
|
169
|
+
// Send buffered data
|
|
170
|
+
if (dataBuffer.length > 0) {
|
|
171
|
+
backendSocket!.write(dataBuffer);
|
|
172
|
+
dataBuffer = Buffer.alloc(0);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Set up bidirectional data flow
|
|
176
|
+
tlsSocket.pipe(backendSocket!);
|
|
177
|
+
backendSocket!.pipe(tlsSocket);
|
|
152
178
|
}
|
|
153
|
-
|
|
154
|
-
// Set up bidirectional data flow
|
|
155
|
-
tlsSocket.pipe(backendSocket!);
|
|
156
|
-
backendSocket!.pipe(tlsSocket);
|
|
157
179
|
});
|
|
158
180
|
|
|
159
181
|
// Update the cleanup handler with the backend socket
|
|
@@ -2,7 +2,7 @@ import * as plugins from '../../plugins.js';
|
|
|
2
2
|
import { ForwardingHandler } from './base-handler.js';
|
|
3
3
|
import type { IForwardConfig } from '../config/forwarding-types.js';
|
|
4
4
|
import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
|
|
5
|
-
import { createSocketCleanupHandler, setupSocketHandlers } from '../../core/utils/socket-utils.js';
|
|
5
|
+
import { createSocketCleanupHandler, setupSocketHandlers, createSocketWithErrorHandler } from '../../core/utils/socket-utils.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Handler for HTTPS termination with HTTPS backend
|
|
@@ -9,7 +9,7 @@ import { TlsManager } from './tls-manager.js';
|
|
|
9
9
|
import { HttpProxyBridge } from './http-proxy-bridge.js';
|
|
10
10
|
import { TimeoutManager } from './timeout-manager.js';
|
|
11
11
|
import { RouteManager } from './route-manager.js';
|
|
12
|
-
import { cleanupSocket, createIndependentSocketHandlers, setupSocketHandlers } from '../../core/utils/socket-utils.js';
|
|
12
|
+
import { cleanupSocket, createIndependentSocketHandlers, setupSocketHandlers, createSocketWithErrorHandler } from '../../core/utils/socket-utils.js';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Handles new connection processing and setup logic with support for route-based configuration
|
|
@@ -1073,13 +1073,52 @@ export class RouteConnectionHandler {
|
|
|
1073
1073
|
record.pendingDataSize = initialChunk.length;
|
|
1074
1074
|
}
|
|
1075
1075
|
|
|
1076
|
-
// Create the target socket
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1076
|
+
// Create the target socket with immediate error handling
|
|
1077
|
+
let targetSocket: plugins.net.Socket;
|
|
1078
|
+
|
|
1079
|
+
// Flag to track if initial connection failed
|
|
1080
|
+
let connectionFailed = false;
|
|
1081
|
+
|
|
1082
|
+
targetSocket = createSocketWithErrorHandler({
|
|
1083
|
+
port: finalTargetPort,
|
|
1084
|
+
host: finalTargetHost,
|
|
1085
|
+
onError: (error) => {
|
|
1086
|
+
// Mark connection as failed
|
|
1087
|
+
connectionFailed = true;
|
|
1088
|
+
|
|
1089
|
+
// Connection failed - clean up immediately
|
|
1090
|
+
logger.log('error',
|
|
1091
|
+
`Connection setup error for ${connectionId} to ${finalTargetHost}:${finalTargetPort}: ${error.message} (${(error as any).code})`,
|
|
1092
|
+
{
|
|
1093
|
+
connectionId,
|
|
1094
|
+
targetHost: finalTargetHost,
|
|
1095
|
+
targetPort: finalTargetPort,
|
|
1096
|
+
errorMessage: error.message,
|
|
1097
|
+
errorCode: (error as any).code,
|
|
1098
|
+
component: 'route-handler'
|
|
1099
|
+
}
|
|
1100
|
+
);
|
|
1101
|
+
|
|
1102
|
+
// Resume the incoming socket to prevent it from hanging
|
|
1103
|
+
socket.resume();
|
|
1104
|
+
|
|
1105
|
+
// Clean up the incoming socket
|
|
1106
|
+
if (!socket.destroyed) {
|
|
1107
|
+
socket.destroy();
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// Clean up the connection record
|
|
1111
|
+
this.connectionManager.initiateCleanupOnce(record, `connection_failed_${(error as any).code || 'unknown'}`);
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
// Only proceed with setup if connection didn't fail immediately
|
|
1116
|
+
if (!connectionFailed) {
|
|
1117
|
+
record.outgoing = targetSocket;
|
|
1118
|
+
record.outgoingStartTime = Date.now();
|
|
1080
1119
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1120
|
+
// Apply socket optimizations
|
|
1121
|
+
targetSocket.setNoDelay(this.settings.noDelay);
|
|
1083
1122
|
|
|
1084
1123
|
// Apply keep-alive settings if enabled
|
|
1085
1124
|
if (this.settings.keepAlive) {
|
|
@@ -1346,5 +1385,6 @@ export class RouteConnectionHandler {
|
|
|
1346
1385
|
record.tlsHandshakeComplete = true;
|
|
1347
1386
|
}
|
|
1348
1387
|
});
|
|
1388
|
+
} // End of if (!connectionFailed)
|
|
1349
1389
|
}
|
|
1350
1390
|
}
|