Compare commits

...

20 Commits

Author SHA1 Message Date
12de96a7d5 3.22.1 2025-03-03 01:57:52 +00:00
296e1fcdc7 fix(PortProxy): Fix connection timeout and IP validation handling for PortProxy 2025-03-03 01:57:52 +00:00
8459e4013c 3.22.0 2025-03-03 01:50:30 +00:00
191c8ac0e6 feat(classes.portproxy): Enhanced PortProxy to support initial data timeout and improved IP handling 2025-03-03 01:50:30 +00:00
3ab483d164 3.21.0 2025-03-03 01:42:16 +00:00
fcd80dc56b feat(PortProxy): Enhancements to connection management in PortProxy 2025-03-03 01:42:16 +00:00
8ddffcd6e5 3.20.2 2025-03-01 20:31:50 +00:00
a5a7781c17 fix(PortProxy): Enhance connection cleanup handling in PortProxy 2025-03-01 20:31:50 +00:00
d647e77cdf 3.20.1 2025-03-01 17:32:31 +00:00
9161336197 fix(PortProxy): Improve IP allowance check for forced domains 2025-03-01 17:32:31 +00:00
2e63d13dd4 3.20.0 2025-03-01 17:19:27 +00:00
af6ed735d5 feat(PortProxy): Enhance PortProxy with advanced connection cleanup and logging 2025-03-01 17:19:27 +00:00
7d38f29ef3 3.19.0 2025-03-01 13:17:05 +00:00
0df26d4367 feat(PortProxy): Enhance PortProxy with default blocked IPs 2025-03-01 13:17:05 +00:00
f9a6e2d748 3.18.2 2025-02-27 21:25:03 +00:00
1cb6302750 fix(portproxy): Fixed typographical errors in comments within PortProxy class. 2025-02-27 21:25:03 +00:00
f336f25535 3.18.1 2025-02-27 21:19:34 +00:00
5d6b707440 fix(PortProxy): Refactor and enhance PortProxy test cases and handling 2025-02-27 21:19:34 +00:00
622ad2ff20 3.18.0 2025-02-27 20:59:29 +00:00
dd23efd28d feat(PortProxy): Add SNI-based renegotiation handling in PortProxy 2025-02-27 20:59:29 +00:00
5 changed files with 892 additions and 437 deletions

View File

@ -1,5 +1,75 @@
# Changelog # Changelog
## 2025-03-03 - 3.22.1 - fix(PortProxy)
Fix connection timeout and IP validation handling for PortProxy
- Adjusted initial data timeout setting for SNI-enabled connections in PortProxy.
- Restored IP validation logic to original behavior, ensuring compatibility with domain configurations.
## 2025-03-03 - 3.22.0 - feat(classes.portproxy)
Enhanced PortProxy to support initial data timeout and improved IP handling
- Added `initialDataTimeout` to PortProxy settings for handling data flow in chained proxies.
- Improved IP validation by allowing relaxed checks in chained proxy setups.
- Introduced dynamic logging for connection lifecycle and proxy configurations.
- Enhanced timeout handling for better proxy resilience.
## 2025-03-03 - 3.21.0 - feat(PortProxy)
Enhancements to connection management in PortProxy
- Introduced a unique ID for each connection record for improved tracking.
- Enhanced cleanup mechanism for connections with dual states: initiated and executed.
- Implemented shutdown process handling to ensure graceful connection closure.
- Added logging for better tracing of connection activities and states.
- Improved connection setup with explicit timeouts and data flow management.
- Integrated inactivity and parity checks to monitor connection health.
## 2025-03-01 - 3.20.2 - fix(PortProxy)
Enhance connection cleanup handling in PortProxy
- Add checks to ensure timers are reset only if outgoing socket is active
- Prevent setting outgoingActive if the connection is already closed
## 2025-03-01 - 3.20.1 - fix(PortProxy)
Improve IP allowance check for forced domains
- Enhanced IP allowance check logic by incorporating blocked IPs and default allowed IPs for forced domains within port proxy configurations.
## 2025-03-01 - 3.20.0 - feat(PortProxy)
Enhance PortProxy with advanced connection cleanup and logging
- Introduced `cleanupConnection` method for improved connection management.
- Added logging for connection cleanup including special conditions.
- Implemented parity check to clean up connections when outgoing side closes but incoming remains active.
- Improved logging during interval checks for active connections and their durations.
## 2025-03-01 - 3.19.0 - feat(PortProxy)
Enhance PortProxy with default blocked IPs
- Introduced defaultBlockedIPs in IPortProxySettings to handle globally blocked IPs.
- Added logic for merging domain-specific and default allowed and blocked IPs for effective IP filtering.
- Refactored helper functions for IP and port range checks to improve modularity in PortProxy.
## 2025-02-27 - 3.18.2 - fix(portproxy)
Fixed typographical errors in comments within PortProxy class.
- Corrected typographical errors in comments within the PortProxy class.
## 2025-02-27 - 3.18.1 - fix(PortProxy)
Refactor and enhance PortProxy test cases and handling
- Refactored test cases in test/test.portproxy.ts for clarity and added coverage.
- Improved TCP server helper functions for better flexibility.
- Fixed issues with domain handling in PortProxy configuration.
- Introduced round-robin logic for multi-IP domains in PortProxy.
- Ensured proper cleanup and stopping of test servers in the test suite.
## 2025-02-27 - 3.18.0 - feat(PortProxy)
Add SNI-based renegotiation handling in PortProxy
- Introduced a new field 'lockedDomain' in IConnectionRecord to store initial SNI.
- Enhanced connection management by enforcing termination if rehandshake is detected with different SNI.
## 2025-02-27 - 3.17.1 - fix(PortProxy) ## 2025-02-27 - 3.17.1 - fix(PortProxy)
Fix handling of SNI re-negotiation in PortProxy Fix handling of SNI re-negotiation in PortProxy

View File

@ -1,6 +1,6 @@
{ {
"name": "@push.rocks/smartproxy", "name": "@push.rocks/smartproxy",
"version": "3.17.1", "version": "3.22.1",
"private": false, "private": false,
"description": "A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, and dynamic routing with authentication options.", "description": "A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, and dynamic routing with authentication options.",
"main": "dist_ts/index.js", "main": "dist_ts/index.js",

View File

@ -8,31 +8,30 @@ const TEST_SERVER_PORT = 4000;
const PROXY_PORT = 4001; const PROXY_PORT = 4001;
const TEST_DATA = 'Hello through port proxy!'; const TEST_DATA = 'Hello through port proxy!';
// Helper function to create a test TCP server // Helper: Creates a test TCP server that listens on a given port and host.
function createTestServer(port: number): Promise<net.Server> { function createTestServer(port: number, host: string = 'localhost'): Promise<net.Server> {
return new Promise((resolve) => { return new Promise((resolve) => {
const server = net.createServer((socket) => { const server = net.createServer((socket) => {
socket.on('data', (data) => { socket.on('data', (data) => {
// Echo the received data back // Echo the received data back with a prefix.
socket.write(`Echo: ${data.toString()}`); socket.write(`Echo: ${data.toString()}`);
}); });
socket.on('error', (error) => { socket.on('error', (error) => {
console.error('[Test Server] Socket error:', error); console.error(`[Test Server] Socket error on ${host}:${port}:`, error);
}); });
}); });
server.listen(port, () => { server.listen(port, host, () => {
console.log(`[Test Server] Listening on port ${port}`); console.log(`[Test Server] Listening on ${host}:${port}`);
resolve(server); resolve(server);
}); });
}); });
} }
// Helper function to create a test client connection // Helper: Creates a test client connection.
function createTestClient(port: number, data: string): Promise<string> { function createTestClient(port: number, data: string): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const client = new net.Socket(); const client = new net.Socket();
let response = ''; let response = '';
client.connect(port, 'localhost', () => { client.connect(port, 'localhost', () => {
console.log('[Test Client] Connected to server'); console.log('[Test Client] Connected to server');
client.write(data); client.write(data);
@ -41,47 +40,44 @@ function createTestClient(port: number, data: string): Promise<string> {
response += chunk.toString(); response += chunk.toString();
client.end(); client.end();
}); });
client.on('end', () => { client.on('end', () => resolve(response));
resolve(response); client.on('error', (error) => reject(error));
});
client.on('error', (error) => {
reject(error);
});
}); });
} }
// Setup test environment // SETUP: Create a test server and a PortProxy instance.
tap.test('setup port proxy test environment', async () => { tap.test('setup port proxy test environment', async () => {
testServer = await createTestServer(TEST_SERVER_PORT); testServer = await createTestServer(TEST_SERVER_PORT);
portProxy = new PortProxy({ portProxy = new PortProxy({
fromPort: PROXY_PORT, fromPort: PROXY_PORT,
toPort: TEST_SERVER_PORT, toPort: TEST_SERVER_PORT,
targetIP: 'localhost', targetIP: 'localhost',
domains: [], domainConfigs: [],
sniEnabled: false, sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1'], defaultAllowedIPs: ['127.0.0.1'],
globalPortRanges: [] globalPortRanges: []
}); });
}); });
// Test that the proxy starts and its servers are listening.
tap.test('should start port proxy', async () => { tap.test('should start port proxy', async () => {
await portProxy.start(); await portProxy.start();
// Since netServers is private, we cast to any to verify that all created servers are listening.
expect((portProxy as any).netServers.every((server: net.Server) => server.listening)).toBeTrue(); expect((portProxy as any).netServers.every((server: net.Server) => server.listening)).toBeTrue();
}); });
// Test basic TCP forwarding.
tap.test('should forward TCP connections and data to localhost', async () => { tap.test('should forward TCP connections and data to localhost', async () => {
const response = await createTestClient(PROXY_PORT, TEST_DATA); const response = await createTestClient(PROXY_PORT, TEST_DATA);
expect(response).toEqual(`Echo: ${TEST_DATA}`); expect(response).toEqual(`Echo: ${TEST_DATA}`);
}); });
// Test proxy with a custom target host.
tap.test('should forward TCP connections to custom host', async () => { tap.test('should forward TCP connections to custom host', async () => {
// Create a new proxy instance with a custom host (targetIP)
const customHostProxy = new PortProxy({ const customHostProxy = new PortProxy({
fromPort: PROXY_PORT + 1, fromPort: PROXY_PORT + 1,
toPort: TEST_SERVER_PORT, toPort: TEST_SERVER_PORT,
targetIP: '127.0.0.1', targetIP: '127.0.0.1',
domains: [], domainConfigs: [],
sniEnabled: false, sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1'], defaultAllowedIPs: ['127.0.0.1'],
globalPortRanges: [] globalPortRanges: []
@ -93,153 +89,151 @@ tap.test('should forward TCP connections to custom host', async () => {
await customHostProxy.stop(); await customHostProxy.stop();
}); });
tap.test('should forward connections based on domain-specific target IP', async () => { // Test forced domain routing via port-range configuration.
// Create a second test server on a different port // In this test, we want to forward to a different IP (using '127.0.0.2')
const TEST_SERVER_PORT_2 = TEST_SERVER_PORT + 100; // while keeping the same port. We create a test server on '127.0.0.2'.
const testServer2 = await createTestServer(TEST_SERVER_PORT_2); tap.test('should forward connections based on domain-specific target IP (forced domain via port-range)', async () => {
const forcedProxyPort = PROXY_PORT + 2;
// Create a test server listening on '127.0.0.2' at forcedProxyPort.
const testServer2 = await createTestServer(forcedProxyPort, '127.0.0.2');
// Create a proxy with domain-specific target IPs
const domainProxy = new PortProxy({ const domainProxy = new PortProxy({
fromPort: PROXY_PORT + 2, fromPort: forcedProxyPort,
toPort: TEST_SERVER_PORT, // default port (for non-port-range handling) toPort: TEST_SERVER_PORT, // default target port (unused for forced domain)
targetIP: 'localhost', // default target IP targetIP: 'localhost',
domains: [{ domainConfigs: [{
domain: 'domain1.test', domains: ['forced.test'],
allowedIPs: ['127.0.0.1'], allowedIPs: ['127.0.0.1'],
targetIP: '127.0.0.1' targetIPs: ['127.0.0.2'], // Use a different IP than the default.
}, { portRanges: [{ from: forcedProxyPort, to: forcedProxyPort }]
domain: 'domain2.test',
allowedIPs: ['127.0.0.1'],
targetIP: 'localhost'
}], }],
sniEnabled: false, sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1'], defaultAllowedIPs: ['127.0.0.1'],
globalPortRanges: [] globalPortRanges: [{ from: forcedProxyPort, to: forcedProxyPort }]
}); });
await domainProxy.start(); await domainProxy.start();
// Test default connection (should use default targetIP) // When connecting to forcedProxyPort, forced domain handling triggers,
const response1 = await createTestClient(PROXY_PORT + 2, TEST_DATA); // so the proxy will connect to '127.0.0.2' on the same port.
expect(response1).toEqual(`Echo: ${TEST_DATA}`); const response = await createTestClient(forcedProxyPort, TEST_DATA);
expect(response).toEqual(`Echo: ${TEST_DATA}`);
// Create another proxy with a different default targetIP
const domainProxy2 = new PortProxy({
fromPort: PROXY_PORT + 3,
toPort: TEST_SERVER_PORT,
targetIP: '127.0.0.1',
domains: [],
sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1'],
globalPortRanges: []
});
await domainProxy2.start();
const response2 = await createTestClient(PROXY_PORT + 3, TEST_DATA);
expect(response2).toEqual(`Echo: ${TEST_DATA}`);
await domainProxy.stop(); await domainProxy.stop();
await domainProxy2.stop();
await new Promise<void>((resolve) => testServer2.close(() => resolve())); await new Promise<void>((resolve) => testServer2.close(() => resolve()));
}); });
// Test handling of multiple concurrent connections.
tap.test('should handle multiple concurrent connections', async () => { tap.test('should handle multiple concurrent connections', async () => {
const concurrentRequests = 5; const concurrentRequests = 5;
const requests = Array(concurrentRequests).fill(null).map((_, i) => const requests = Array(concurrentRequests).fill(null).map((_, i) =>
createTestClient(PROXY_PORT, `${TEST_DATA} ${i + 1}`) createTestClient(PROXY_PORT, `${TEST_DATA} ${i + 1}`)
); );
const responses = await Promise.all(requests); const responses = await Promise.all(requests);
responses.forEach((response, i) => { responses.forEach((response, i) => {
expect(response).toEqual(`Echo: ${TEST_DATA} ${i + 1}`); expect(response).toEqual(`Echo: ${TEST_DATA} ${i + 1}`);
}); });
}); });
// Test connection timeout handling.
tap.test('should handle connection timeouts', async () => { tap.test('should handle connection timeouts', async () => {
const client = new net.Socket(); const client = new net.Socket();
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
client.connect(PROXY_PORT, 'localhost', () => { client.connect(PROXY_PORT, 'localhost', () => {
// Don't send any data, just wait for timeout // Do not send any data to trigger a timeout.
client.on('close', () => { client.on('close', () => resolve());
resolve();
});
}); });
}); });
}); });
// Test stopping the port proxy.
tap.test('should stop port proxy', async () => { tap.test('should stop port proxy', async () => {
await portProxy.stop(); await portProxy.stop();
expect((portProxy as any).netServers.every((server: net.Server) => !server.listening)).toBeTrue(); expect((portProxy as any).netServers.every((server: net.Server) => !server.listening)).toBeTrue();
}); });
// Cleanup chained proxies tests // Test chained proxies with and without source IP preservation.
tap.test('should support optional source IP preservation in chained proxies', async () => { tap.test('should support optional source IP preservation in chained proxies', async () => {
// Test 1: Without IP preservation (default behavior) // Chained proxies without IP preservation.
const firstProxyDefault = new PortProxy({ const firstProxyDefault = new PortProxy({
fromPort: PROXY_PORT + 4, fromPort: PROXY_PORT + 4,
toPort: PROXY_PORT + 5, toPort: PROXY_PORT + 5,
targetIP: 'localhost', targetIP: 'localhost',
domains: [], domainConfigs: [],
sniEnabled: false, sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1', '::ffff:127.0.0.1'], defaultAllowedIPs: ['127.0.0.1', '::ffff:127.0.0.1'],
globalPortRanges: [] globalPortRanges: []
}); });
const secondProxyDefault = new PortProxy({ const secondProxyDefault = new PortProxy({
fromPort: PROXY_PORT + 5, fromPort: PROXY_PORT + 5,
toPort: TEST_SERVER_PORT, toPort: TEST_SERVER_PORT,
targetIP: 'localhost', targetIP: 'localhost',
domains: [], domainConfigs: [],
sniEnabled: false, sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1', '::ffff:127.0.0.1'], defaultAllowedIPs: ['127.0.0.1', '::ffff:127.0.0.1'],
globalPortRanges: [] globalPortRanges: []
}); });
await secondProxyDefault.start(); await secondProxyDefault.start();
await firstProxyDefault.start(); await firstProxyDefault.start();
// This should work because we explicitly allow both IPv4 and IPv6 formats
const response1 = await createTestClient(PROXY_PORT + 4, TEST_DATA); const response1 = await createTestClient(PROXY_PORT + 4, TEST_DATA);
expect(response1).toEqual(`Echo: ${TEST_DATA}`); expect(response1).toEqual(`Echo: ${TEST_DATA}`);
await firstProxyDefault.stop(); await firstProxyDefault.stop();
await secondProxyDefault.stop(); await secondProxyDefault.stop();
// Test 2: With IP preservation // Chained proxies with IP preservation.
const firstProxyPreserved = new PortProxy({ const firstProxyPreserved = new PortProxy({
fromPort: PROXY_PORT + 6, fromPort: PROXY_PORT + 6,
toPort: PROXY_PORT + 7, toPort: PROXY_PORT + 7,
targetIP: 'localhost', targetIP: 'localhost',
domains: [], domainConfigs: [],
sniEnabled: false, sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1'], defaultAllowedIPs: ['127.0.0.1'],
preserveSourceIP: true, preserveSourceIP: true,
globalPortRanges: [] globalPortRanges: []
}); });
const secondProxyPreserved = new PortProxy({ const secondProxyPreserved = new PortProxy({
fromPort: PROXY_PORT + 7, fromPort: PROXY_PORT + 7,
toPort: TEST_SERVER_PORT, toPort: TEST_SERVER_PORT,
targetIP: 'localhost', targetIP: 'localhost',
domains: [], domainConfigs: [],
sniEnabled: false, sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1'], defaultAllowedIPs: ['127.0.0.1'],
preserveSourceIP: true, preserveSourceIP: true,
globalPortRanges: [] globalPortRanges: []
}); });
await secondProxyPreserved.start(); await secondProxyPreserved.start();
await firstProxyPreserved.start(); await firstProxyPreserved.start();
// This should work with just IPv4 because source IP is preserved
const response2 = await createTestClient(PROXY_PORT + 6, TEST_DATA); const response2 = await createTestClient(PROXY_PORT + 6, TEST_DATA);
expect(response2).toEqual(`Echo: ${TEST_DATA}`); expect(response2).toEqual(`Echo: ${TEST_DATA}`);
await firstProxyPreserved.stop(); await firstProxyPreserved.stop();
await secondProxyPreserved.stop(); await secondProxyPreserved.stop();
}); });
// Test round-robin behavior for multiple target IPs in a domain config.
tap.test('should use round robin for multiple target IPs in domain config', async () => {
const domainConfig = {
domains: ['rr.test'],
allowedIPs: ['127.0.0.1'],
targetIPs: ['hostA', 'hostB']
} as any;
const proxyInstance = new PortProxy({
fromPort: 0,
toPort: 0,
targetIP: 'localhost',
domainConfigs: [domainConfig],
sniEnabled: false,
defaultAllowedIPs: [],
globalPortRanges: []
});
const firstTarget = (proxyInstance as any).getTargetIP(domainConfig);
const secondTarget = (proxyInstance as any).getTargetIP(domainConfig);
expect(firstTarget).toEqual('hostA');
expect(secondTarget).toEqual('hostB');
});
// CLEANUP: Tear down the test server.
tap.test('cleanup port proxy test environment', async () => { tap.test('cleanup port proxy test environment', async () => {
await new Promise<void>((resolve) => testServer.close(() => resolve())); await new Promise<void>((resolve) => testServer.close(() => resolve()));
}); });
@ -248,7 +242,6 @@ process.on('exit', () => {
if (testServer) { if (testServer) {
testServer.close(); testServer.close();
} }
// Use a cast to access the private property for cleanup.
if (portProxy && (portProxy as any).netServers) { if (portProxy && (portProxy as any).netServers) {
portProxy.stop(); portProxy.stop();
} }

View File

@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/smartproxy', name: '@push.rocks/smartproxy',
version: '3.17.1', version: '3.22.1',
description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, and dynamic routing with authentication options.' description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, and dynamic routing with authentication options.'
} }

File diff suppressed because it is too large Load Diff