Compare commits

...

44 Commits

Author SHA1 Message Date
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
0ddf68a919 3.17.1 2025-02-27 20:10:26 +00:00
ec08ca51f5 fix(PortProxy): Fix handling of SNI re-negotiation in PortProxy 2025-02-27 20:10:26 +00:00
29688d1379 3.17.0 2025-02-27 19:57:28 +00:00
c83f6fa278 feat(smartproxy): Enhance description clarity and improve SNI handling with domain locking. 2025-02-27 19:57:27 +00:00
60333b0a59 3.16.9 2025-02-27 15:46:14 +00:00
1aa409907b fix(portproxy): Extend domain input validation to support string arrays in port proxy configurations. 2025-02-27 15:46:14 +00:00
adee6afc76 3.16.8 2025-02-27 15:41:03 +00:00
4a0792142f fix(PortProxy): Fix IP filtering for domain and global default allowed lists and improve port-based routing logic. 2025-02-27 15:41:03 +00:00
f1b810a4fa 3.16.7 2025-02-27 15:32:06 +00:00
96b5877c5f fix(PortProxy): Improved IP validation logic in PortProxy to ensure correct domain matching and fallback 2025-02-27 15:32:06 +00:00
6d627f67f7 3.16.6 2025-02-27 15:30:20 +00:00
9af968b8e7 fix(PortProxy): Optimize connection cleanup logic in PortProxy by removing unnecessary delays. 2025-02-27 15:30:20 +00:00
b3ba0c21e8 3.16.5 2025-02-27 15:05:38 +00:00
ef707a5870 fix(PortProxy): Improved connection cleanup process with added asynchronous delays 2025-02-27 15:05:38 +00:00
6ca14edb38 3.16.4 2025-02-27 14:23:44 +00:00
5a5686b6b9 fix(PortProxy): Fix and enhance port proxy handling 2025-02-27 14:23:44 +00:00
2080f419cb 3.16.3 2025-02-27 13:04:01 +00:00
659aae297b fix(PortProxy): Refactored PortProxy to support multiple listening ports and improved modularity. 2025-02-27 13:04:01 +00:00
fcd0f61b5c 3.16.2 2025-02-27 12:54:15 +00:00
7ee35a98e3 fix(PortProxy): Fix port-based routing logic in PortProxy 2025-02-27 12:54:14 +00:00
ea0f6d2270 3.16.1 2025-02-27 12:42:50 +00:00
621ad9e681 fix(core): Updated minor version numbers in dependencies for patch release. 2025-02-27 12:42:50 +00:00
7cea5773ee 3.16.0 2025-02-27 12:41:20 +00:00
a2cb56ba65 feat(PortProxy): Enhancements made to PortProxy settings and capabilities 2025-02-27 12:41:20 +00:00
408b793149 3.15.0 2025-02-27 12:25:48 +00:00
f6c3d2d3d0 feat(classes.portproxy): Add support for port range-based routing with enhanced IP and port validation. 2025-02-27 12:25:48 +00:00
422eb5ec40 3.14.2 2025-02-26 19:00:09 +00:00
45390c4389 fix(PortProxy): Fix cleanup timer reset for PortProxy 2025-02-26 19:00:09 +00:00
0f2e6d688c 3.14.1 2025-02-26 12:56:00 +00:00
3bd7b70c19 fix(PortProxy): Increased default maxConnectionLifetime for PortProxy to 600000 ms 2025-02-26 12:56:00 +00:00
07a82a09be 3.14.0 2025-02-26 10:29:21 +00:00
23253a2731 feat(PortProxy): Introduce max connection lifetime feature 2025-02-26 10:29:21 +00:00
be31a9b553 3.13.0 2025-02-25 00:56:02 +00:00
a1051f78e8 feat(core): Add support for tagging iptables rules with comments and cleaning them up on process exit 2025-02-25 00:56:01 +00:00
aa756bd698 3.12.0 2025-02-24 23:27:48 +00:00
ff4f44d6fc feat(IPTablesProxy): Introduce IPTablesProxy class for managing iptables NAT rules 2025-02-24 23:27:48 +00:00
63ebad06ea 3.11.0 2025-02-24 10:00:57 +00:00
31e15b65ec feat(Port80Handler): Add automatic certificate issuance with ACME client 2025-02-24 10:00:57 +00:00
11 changed files with 1038 additions and 250 deletions

View File

@ -1,5 +1,148 @@
# Changelog
## 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)
Fix handling of SNI re-negotiation in PortProxy
- Removed connection locking to the initially negotiated SNI
- Improved handling of SNI during renegotiation in PortProxy
## 2025-02-27 - 3.17.0 - feat(smartproxy)
Enhance description clarity and improve SNI handling with domain locking.
- Improved package description in package.json, readme.md, and npmextra.json for better clarity and keyword optimization.
- Enhanced SNI handling in PortProxy by adding domain locking and extra checks to terminate connections if a different SNI is detected post-handshake.
- Refactored readme.md to better explain the usage and functionalities of the proxy features including SSL redirection, WebSocket handling, and dynamic routing.
## 2025-02-27 - 3.16.9 - fix(portproxy)
Extend domain input validation to support string arrays in port proxy configurations.
- Modify IDomainConfig interface to allow domain specification as string array.
- Update connection setup logic to handle multiple domain patterns.
- Enhance domain rejection logging to include all domain patterns.
## 2025-02-27 - 3.16.8 - fix(PortProxy)
Fix IP filtering for domain and global default allowed lists and improve port-based routing logic.
- Improved logic to prioritize domain-specific allowed IPs over global defaults.
- Fixed port-based rules application to handle global port ranges more effectively.
- Enhanced rejection handling for unauthorized IP addresses in both domain-specific and default global lists.
## 2025-02-27 - 3.16.7 - fix(PortProxy)
Improved IP validation logic in PortProxy to ensure correct domain matching and fallback
- Refactored the setupConnection function inside PortProxy to enhance IP address validation.
- Domain-specific allowed IP preference is applied before default list lookup.
- Removed redundant condition checks to streamline connection rejection paths.
## 2025-02-27 - 3.16.6 - fix(PortProxy)
Optimize connection cleanup logic in PortProxy by removing unnecessary delays.
- Removed multiple await plugins.smartdelay.delayFor(0) calls.
- Improved performance by ensuring timely resource release during connection termination.
## 2025-02-27 - 3.16.5 - fix(PortProxy)
Improved connection cleanup process with added asynchronous delays
- Connection cleanup now includes asynchronous delays for reliable order of operations.
## 2025-02-27 - 3.16.4 - fix(PortProxy)
Fix and enhance port proxy handling
- Ensure that all created proxy servers are correctly checked for listening state.
- Corrected the handling of ports and domain configurations within port proxy setups.
- Expanded test coverage for handling multiple concurrent and chained proxy connections.
## 2025-02-27 - 3.16.3 - fix(PortProxy)
Refactored PortProxy to support multiple listening ports and improved modularity.
- Updated PortProxy to allow multiple listening ports with flexible configuration.
- Moved helper functions for IP and port range checks outside the class for cleaner code structure.
## 2025-02-27 - 3.16.2 - fix(PortProxy)
Fix port-based routing logic in PortProxy
- Optimized the handling and checking of local ports in the global port range.
- Fixed the logic for rejecting or accepting connections based on predefined port ranges.
- Improved handling of the default and specific domain configurations during port-based connections.
## 2025-02-27 - 3.16.1 - fix(core)
Updated minor version numbers in dependencies for patch release.
- No specific file changes detected.
- Dependencies versioning adjusted for stability.
## 2025-02-27 - 3.16.0 - feat(PortProxy)
Enhancements made to PortProxy settings and capabilities
- Added 'forwardAllGlobalRanges' and 'targetIP' to IPortProxySettings.
- Improved PortProxy to forward connections based on domain-specific configurations.
- Added comprehensive handling for global port-range based connection forwarding.
- Enabled forwarding of all connections on global port ranges directly to global target IP.
## 2025-02-27 - 3.15.0 - feat(classes.portproxy)
Add support for port range-based routing with enhanced IP and port validation.
- Introduced globalPortRanges in IPortProxySettings for routing based on port ranges.
- Improved connection handling with port range and domain configuration validations.
- Updated connection logging to include the local port information.
## 2025-02-26 - 3.14.2 - fix(PortProxy)
Fix cleanup timer reset for PortProxy
- Resolved an issue where the cleanup timer in the PortProxy class did not reset correctly if both incoming and outgoing data events were triggered without clearing flags.
## 2025-02-26 - 3.14.1 - fix(PortProxy)
Increased default maxConnectionLifetime for PortProxy to 600000 ms
- Updated PortProxy settings to extend default maxConnectionLifetime to 10 minutes.
## 2025-02-26 - 3.14.0 - feat(PortProxy)
Introduce max connection lifetime feature
- Added an optional maxConnectionLifetime setting for PortProxy.
- Forces cleanup of long-lived connections based on inactivity or lifetime limit.
## 2025-02-25 - 3.13.0 - feat(core)
Add support for tagging iptables rules with comments and cleaning them up on process exit
- Extended IPTablesProxy class to include tagging rules with unique comments.
- Added feature to clean up iptables rules via comments during process exit.
## 2025-02-24 - 3.12.0 - feat(IPTablesProxy)
Introduce IPTablesProxy class for managing iptables NAT rules
- Added IPTablesProxy class to facilitate basic port forwarding using iptables.
- Introduced IIpTableProxySettings interface for configuring IPTablesProxy.
- Implemented start and stop methods for managing iptables rules dynamically.
## 2025-02-24 - 3.11.0 - feat(Port80Handler)
Add automatic certificate issuance with ACME client
- Implemented automatic certificate issuance using 'acme-client' for Port80Handler.
- Converts account key and CSR from Buffers to strings for processing.
- Implemented HTTP-01 challenge handling for certificate acquisition.
- New certificates are fetched and added dynamically.
## 2025-02-24 - 3.10.5 - fix(portproxy)
Fix incorrect import path in test file

View File

@ -5,22 +5,26 @@
"githost": "code.foss.global",
"gitscope": "push.rocks",
"gitrepo": "smartproxy",
"description": "a proxy for handling high workloads of proxying",
"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.",
"npmPackagename": "@push.rocks/smartproxy",
"license": "MIT",
"projectDomain": "push.rocks",
"keywords": [
"proxy",
"network traffic",
"network",
"traffic management",
"SSL",
"TLS",
"WebSocket",
"port proxying",
"dynamic routing",
"authentication",
"real-time applications",
"high workload",
"http",
"https",
"websocket",
"network routing",
"ssl redirect",
"port mapping",
"HTTPS",
"reverse proxy",
"authentication"
"server",
"network security"
]
}
},

View File

@ -1,8 +1,8 @@
{
"name": "@push.rocks/smartproxy",
"version": "3.10.5",
"version": "3.18.2",
"private": false,
"description": "a proxy for handling high workloads of proxying",
"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",
"typings": "dist_ts/index.d.ts",
"type": "module",
@ -31,6 +31,7 @@
"@tsclass/tsclass": "^4.4.0",
"@types/minimatch": "^5.1.2",
"@types/ws": "^8.5.14",
"acme-client": "^5.4.0",
"minimatch": "^9.0.3",
"pretty-ms": "^9.2.0",
"ws": "^8.18.0"
@ -52,16 +53,20 @@
],
"keywords": [
"proxy",
"network traffic",
"network",
"traffic management",
"SSL",
"TLS",
"WebSocket",
"port proxying",
"dynamic routing",
"authentication",
"real-time applications",
"high workload",
"http",
"https",
"websocket",
"network routing",
"ssl redirect",
"port mapping",
"HTTPS",
"reverse proxy",
"authentication"
"server",
"network security"
],
"homepage": "https://code.foss.global/push.rocks/smartproxy#readme",
"repository": {

187
pnpm-lock.yaml generated
View File

@ -32,6 +32,9 @@ importers:
'@types/ws':
specifier: ^8.5.14
version: 8.5.14
acme-client:
specifier: ^5.4.0
version: 5.4.0
minimatch:
specifier: ^9.0.3
version: 9.0.5
@ -683,6 +686,39 @@ packages:
'@pdf-lib/upng@1.0.1':
resolution: {integrity: sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==}
'@peculiar/asn1-cms@2.3.15':
resolution: {integrity: sha512-B+DoudF+TCrxoJSTjjcY8Mmu+lbv8e7pXGWrhNp2/EGJp9EEcpzjBCar7puU57sGifyzaRVM03oD5L7t7PghQg==}
'@peculiar/asn1-csr@2.3.15':
resolution: {integrity: sha512-caxAOrvw2hUZpxzhz8Kp8iBYKsHbGXZPl2KYRMIPvAfFateRebS3136+orUpcVwHRmpXWX2kzpb6COlIrqCumA==}
'@peculiar/asn1-ecc@2.3.15':
resolution: {integrity: sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA==}
'@peculiar/asn1-pfx@2.3.15':
resolution: {integrity: sha512-E3kzQe3J2xV9DP6SJS4X6/N1e4cYa2xOAK46VtvpaRk8jlheNri8v0rBezKFVPB1rz/jW8npO+u1xOvpATFMWg==}
'@peculiar/asn1-pkcs8@2.3.15':
resolution: {integrity: sha512-/PuQj2BIAw1/v76DV1LUOA6YOqh/UvptKLJHtec/DQwruXOCFlUo7k6llegn8N5BTeZTWMwz5EXruBw0Q10TMg==}
'@peculiar/asn1-pkcs9@2.3.15':
resolution: {integrity: sha512-yiZo/1EGvU1KiQUrbcnaPGWc0C7ElMMskWn7+kHsCFm+/9fU0+V1D/3a5oG0Jpy96iaXggQpA9tzdhnYDgjyFg==}
'@peculiar/asn1-rsa@2.3.15':
resolution: {integrity: sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg==}
'@peculiar/asn1-schema@2.3.15':
resolution: {integrity: sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==}
'@peculiar/asn1-x509-attr@2.3.15':
resolution: {integrity: sha512-TWJVJhqc+IS4MTEML3l6W1b0sMowVqdsnI4dnojg96LvTuP8dga9f76fjP07MUuss60uSyT2ckoti/2qHXA10A==}
'@peculiar/asn1-x509@2.3.15':
resolution: {integrity: sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg==}
'@peculiar/x509@1.12.3':
resolution: {integrity: sha512-+Mzq+W7cNEKfkNZzyLl6A6ffqc3r21HGZUezgfKxpZrkORfOqgRXnS80Zu0IV6a9Ue9QBJeKD7kN0iWfc3bhRQ==}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@ -1563,6 +1599,10 @@ packages:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
acme-client@5.4.0:
resolution: {integrity: sha512-mORqg60S8iML6XSmVjqjGHJkINrCGLMj2QvDmFzI9vIlv1RGlyjmw3nrzaINJjkNsYXC41XhhD5pfy7CtuGcbA==}
engines: {node: '>= 16'}
agent-base@6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
@ -1626,6 +1666,10 @@ packages:
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
engines: {node: '>=8'}
asn1js@3.0.5:
resolution: {integrity: sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==}
engines: {node: '>=12.0.0'}
astral-regex@2.0.0:
resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
engines: {node: '>=8'}
@ -1643,6 +1687,9 @@ packages:
resolution: {integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==}
engines: {node: '>=4'}
axios@1.7.9:
resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==}
b4a@1.6.7:
resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==}
@ -3463,6 +3510,13 @@ packages:
resolution: {integrity: sha512-+vZPU8iBSdCx1Kn5hHas80fyo0TiVyMeqLGv/1dygX2HKhAZjO9YThadbRTCoTYq0yWw+w/CysldPsEekDtjDQ==}
engines: {node: '>=14.1.0'}
pvtsutils@1.3.6:
resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==}
pvutils@1.1.3:
resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==}
engines: {node: '>=6.0.0'}
qs@6.13.0:
resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
engines: {node: '>=0.6'}
@ -3508,6 +3562,9 @@ packages:
resolution: {integrity: sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==}
engines: {node: '>= 14.18.0'}
reflect-metadata@0.2.2:
resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
regenerator-runtime@0.14.1:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
@ -3897,6 +3954,10 @@ packages:
engines: {node: '>=18.0.0'}
hasBin: true
tsyringe@4.8.0:
resolution: {integrity: sha512-YB1FG+axdxADa3ncEtRnQCFq/M0lALGLxSZeVNbTU8NqhOVc51nnv2CISTcvc1kyv6EGPtXVr0v6lWeDxiijOA==}
engines: {node: '>= 6.0.0'}
turndown-plugin-gfm@1.0.2:
resolution: {integrity: sha512-vwz9tfvF7XN/jE0dGoBei3FXWuvll78ohzCZQuOb+ZjWrs3a0XhQVomJEb2Qh4VHTPNRO4GPZh0V7VRbiWwkRg==}
@ -5212,6 +5273,96 @@ snapshots:
dependencies:
pako: 1.0.11
'@peculiar/asn1-cms@2.3.15':
dependencies:
'@peculiar/asn1-schema': 2.3.15
'@peculiar/asn1-x509': 2.3.15
'@peculiar/asn1-x509-attr': 2.3.15
asn1js: 3.0.5
tslib: 2.8.1
'@peculiar/asn1-csr@2.3.15':
dependencies:
'@peculiar/asn1-schema': 2.3.15
'@peculiar/asn1-x509': 2.3.15
asn1js: 3.0.5
tslib: 2.8.1
'@peculiar/asn1-ecc@2.3.15':
dependencies:
'@peculiar/asn1-schema': 2.3.15
'@peculiar/asn1-x509': 2.3.15
asn1js: 3.0.5
tslib: 2.8.1
'@peculiar/asn1-pfx@2.3.15':
dependencies:
'@peculiar/asn1-cms': 2.3.15
'@peculiar/asn1-pkcs8': 2.3.15
'@peculiar/asn1-rsa': 2.3.15
'@peculiar/asn1-schema': 2.3.15
asn1js: 3.0.5
tslib: 2.8.1
'@peculiar/asn1-pkcs8@2.3.15':
dependencies:
'@peculiar/asn1-schema': 2.3.15
'@peculiar/asn1-x509': 2.3.15
asn1js: 3.0.5
tslib: 2.8.1
'@peculiar/asn1-pkcs9@2.3.15':
dependencies:
'@peculiar/asn1-cms': 2.3.15
'@peculiar/asn1-pfx': 2.3.15
'@peculiar/asn1-pkcs8': 2.3.15
'@peculiar/asn1-schema': 2.3.15
'@peculiar/asn1-x509': 2.3.15
'@peculiar/asn1-x509-attr': 2.3.15
asn1js: 3.0.5
tslib: 2.8.1
'@peculiar/asn1-rsa@2.3.15':
dependencies:
'@peculiar/asn1-schema': 2.3.15
'@peculiar/asn1-x509': 2.3.15
asn1js: 3.0.5
tslib: 2.8.1
'@peculiar/asn1-schema@2.3.15':
dependencies:
asn1js: 3.0.5
pvtsutils: 1.3.6
tslib: 2.8.1
'@peculiar/asn1-x509-attr@2.3.15':
dependencies:
'@peculiar/asn1-schema': 2.3.15
'@peculiar/asn1-x509': 2.3.15
asn1js: 3.0.5
tslib: 2.8.1
'@peculiar/asn1-x509@2.3.15':
dependencies:
'@peculiar/asn1-schema': 2.3.15
asn1js: 3.0.5
pvtsutils: 1.3.6
tslib: 2.8.1
'@peculiar/x509@1.12.3':
dependencies:
'@peculiar/asn1-cms': 2.3.15
'@peculiar/asn1-csr': 2.3.15
'@peculiar/asn1-ecc': 2.3.15
'@peculiar/asn1-pkcs9': 2.3.15
'@peculiar/asn1-rsa': 2.3.15
'@peculiar/asn1-schema': 2.3.15
'@peculiar/asn1-x509': 2.3.15
pvtsutils: 1.3.6
reflect-metadata: 0.2.2
tslib: 2.8.1
tsyringe: 4.8.0
'@pkgjs/parseargs@0.11.0':
optional: true
@ -6783,6 +6934,16 @@ snapshots:
mime-types: 2.1.35
negotiator: 0.6.3
acme-client@5.4.0:
dependencies:
'@peculiar/x509': 1.12.3
asn1js: 3.0.5
axios: 1.7.9(debug@4.4.0)
debug: 4.4.0
node-forge: 1.3.1
transitivePeerDependencies:
- supports-color
agent-base@6.0.2:
dependencies:
debug: 4.3.4
@ -6834,6 +6995,12 @@ snapshots:
array-union@2.1.0: {}
asn1js@3.0.5:
dependencies:
pvtsutils: 1.3.6
pvutils: 1.1.3
tslib: 2.8.1
astral-regex@2.0.0: {}
async-mutex@0.3.2:
@ -6846,6 +7013,14 @@ snapshots:
axe-core@4.10.2: {}
axios@1.7.9(debug@4.4.0):
dependencies:
follow-redirects: 1.15.9(debug@4.4.0)
form-data: 4.0.1
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
b4a@1.6.7: {}
bail@2.0.2: {}
@ -8954,6 +9129,12 @@ snapshots:
- supports-color
- utf-8-validate
pvtsutils@1.3.6:
dependencies:
tslib: 2.8.1
pvutils@1.1.3: {}
qs@6.13.0:
dependencies:
side-channel: 1.1.0
@ -9008,6 +9189,8 @@ snapshots:
readdirp@4.1.1: {}
reflect-metadata@0.2.2: {}
regenerator-runtime@0.14.1: {}
registry-auth-token@5.0.3:
@ -9488,6 +9671,10 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
tsyringe@4.8.0:
dependencies:
tslib: 1.14.1
turndown-plugin-gfm@1.0.2: {}
turndown@7.2.0:

202
readme.md
View File

@ -1,6 +1,6 @@
# @push.rocks/smartproxy
A proxy for handling high workloads of proxying.
A robust and versatile proxy package designed to handle high workloads, offering features like SSL redirection, port proxying, WebSocket support, and customizable routing and authentication.
## Install
@ -14,19 +14,19 @@ This will add `@push.rocks/smartproxy` to your project's dependencies.
## Usage
`@push.rocks/smartproxy` is a versatile package for setting up and handling proxies with various capabilities such as SSL redirection, port proxying, and creating network proxies with complex routing rules. Below is a comprehensive guide on using its features.
`@push.rocks/smartproxy` is a comprehensive package that provides advanced functionalities for handling proxy tasks efficiently, including SSL redirection, port proxying, WebSocket support, and dynamic routing with authentication capabilities. Here is an extensive guide on how to utilize these features effectively, ensuring robust and secure proxy operations.
### Setting Up a Network Proxy
### Initial Setup
Create a network proxy to route incoming HTTPS requests to different local servers based on the hostname.
Before exploring the advanced features of `smartproxy`, you need to set up a basic proxy server. This setup serves as the foundation for incorporating additional functionalities later on:
```typescript
import { NetworkProxy } from '@push.rocks/smartproxy';
// Instantiate the NetworkProxy with desired options
// Create an instance of NetworkProxy with the desired configuration
const myNetworkProxy = new NetworkProxy({ port: 443 });
// Define your reverse proxy configurations
// Define reverse proxy configurations for the domains you wish to proxy
const proxyConfigs = [
{
destinationIp: '127.0.0.1',
@ -39,70 +39,194 @@ PRIVATE_KEY_CONTENT
CERTIFICATE_CONTENT
-----END CERTIFICATE-----`,
},
// Add more reverse proxy configurations here
// Additional configurations can be added here
];
// Start the network proxy
// Start the network proxy to enable forwarding
await myNetworkProxy.start();
// Update proxy configurations dynamically
// Apply the configurations you defined earlier
await myNetworkProxy.updateProxyConfigs(proxyConfigs);
// Optionally, add default headers to all responses
// Optionally, you can set default headers to be included in all responses
await myNetworkProxy.addDefaultHeaders({
'X-Powered-By': 'smartproxy',
});
```
### Port Proxying
### Configuring SSL Redirection
You can also set up a port proxy to forward traffic from one port to another, which is useful for dynamic port forwarding scenarios.
```typescript
import { PortProxy } from '@push.rocks/smartproxy';
// Create a PortProxy to forward traffic from port 5000 to port 3000
const myPortProxy = new PortProxy(5000, 3000);
// Start the port proxy
await myPortProxy.start();
// To stop the port proxy, simply call
await myPortProxy.stop();
```
### Enabling SSL Redirection
Easily redirect HTTP traffic to HTTPS using the `SslRedirect` class. This is particularly useful when ensuring all traffic uses encryption.
A critical feature of modern proxy servers is the ability to redirect HTTP traffic to secure HTTPS endpoints. The `SslRedirect` class in `smartproxy` simplifies this process by automatically redirecting requests from HTTP port 80 to HTTPS:
```typescript
import { SslRedirect } from '@push.rocks/smartproxy';
// Instantiate the SslRedirect on port 80 (HTTP)
// Create an SslRedirect instance to listen on port 80
const mySslRedirect = new SslRedirect(80);
// Start listening and redirecting to HTTPS
// Start the redirect to enforce HTTPS
await mySslRedirect.start();
// To stop the redirection, use
// To stop HTTP redirection, use the following command:
await mySslRedirect.stop();
```
### Advanced Usage
### Managing Port Proxying
The package integrates seamlessly with TypeScript, allowing for advanced use cases, such as implementing custom routing logic, authentication mechanisms, and handling WebSocket connections through the network proxy.
Port proxying is essential for forwarding traffic from one port to another, an important feature for services that require dynamic port changes without downtime. Smartproxy's `PortProxy` class efficiently handles these scenarios:
For a more advanced setup involving WebSocket proxying and dynamic configuration reloading, refer to the network proxy example provided above. The WebSocket support demonstrates how seamless it is to work with real-time applications.
```typescript
import { PortProxy } from '@push.rocks/smartproxy';
Remember, when dealing with certificates and private keys for HTTPS configurations, always secure your keys and store them appropriately.
// Set up a PortProxy to forward traffic from port 5000 to 3000
const myPortProxy = new PortProxy(5000, 3000);
`@push.rocks/smartproxy` provides a solid foundation for handling high workloads and complex proxying requirements with ease, whether you're implementing SSL redirections, port forwarding, or extensive routing and WebSocket support in your network.
// Initiate the port proxy
await myPortProxy.start();
For more information on how to use the features, refer to the in-depth documentation available in the package's repository or the npm package description.
// To halt the port proxy, execute:
await myPortProxy.stop();
```
For more intricate setups—such as forwarding based on specific domain rules or IP allowances—smartproxy allows detailed configurations:
```typescript
import { PortProxy } from '@push.rocks/smartproxy';
// Configure complex port proxy rules
const advancedPortProxy = new PortProxy({
fromPort: 6000,
toPort: 3000,
domains: [
{
domain: 'api.example.com',
allowedIPs: ['192.168.0.*', '127.0.0.1'],
targetIP: '192.168.1.100'
}
// Additional domain rules can be added as needed
],
sniEnabled: true, // Server Name Indication (SNI) support
defaultAllowedIPs: ['*'],
});
// Activate the proxy with conditional rules
await advancedPortProxy.start();
```
### WebSocket Handling
With real-time applications becoming more prevalent, effective WebSocket handling is crucial in a proxy server. Smartproxy natively incorporates WebSocket support to manage WebSocket traffic securely and efficiently:
```typescript
import { NetworkProxy } from '@push.rocks/smartproxy';
// Create a NetworkProxy instance for WebSocket traffic
const wsNetworkProxy = new NetworkProxy({ port: 443 });
// Define proxy configurations targeted for WebSocket traffic
const websocketConfig = [
{
destinationIp: '127.0.0.1',
destinationPort: '8080',
hostName: 'socket.example.com',
// Include SSL details if necessary
}
];
// Start the proxy and apply WebSocket settings
await wsNetworkProxy.start();
await wsNetworkProxy.updateProxyConfigs(websocketConfig);
// Set heartbeat intervals to maintain WebSocket connections
wsNetworkProxy.heartbeatInterval = setInterval(() => {
// Logic for connection health checks
}, 60000); // every minute
// Capture and handle server errors for resiliency
wsNetworkProxy.httpsServer.on('error', (error) => console.log('Server Error:', error));
```
### Advanced Routing and Custom Features
Smartproxy shines with its dynamic routing capabilities, allowing for custom and advanced request routing based on the request's destination. This enables extensive flexibility, such as directing API requests or facilitating intricate B2B integrations:
```typescript
import { NetworkProxy } from '@push.rocks/smartproxy';
// Instantiate a proxy with dynamic routing
const routeProxy = new NetworkProxy({ port: 8443 });
routeProxy.router.setNewProxyConfigs([
{
destinationIp: '192.168.1.150',
destinationPort: '80',
hostName: 'dynamic.example.com',
authentication: {
type: 'Basic',
user: 'admin',
pass: 'password123'
}
}
]);
// Activate the routing proxy
await routeProxy.start();
```
For those who require granular traffic control, integrating tools like `iptables` offers additional power over network management:
```typescript
import { IPTablesProxy } from '@push.rocks/smartproxy';
// Set up IPTables for sophisticated network traffic management
const iptablesProxy = new IPTablesProxy({
fromPort: 8081,
toPort: 8080,
deleteOnExit: true // Clean up rules when the server shuts down
});
// Enable routing through IPTables
await iptablesProxy.start();
```
### Integrating SSL and HTTP/HTTPS Credentials
Handling sensitive data like SSL keys and certificates securely is crucial in proxy configurations:
```typescript
import { loadDefaultCertificates } from '@push.rocks/smartproxy';
try {
const { privateKey, publicKey } = loadDefaultCertificates(); // Adjust path if necessary
console.log('SSL certificates loaded successfully.');
// Use these credentials in your configurations
} catch (error) {
console.error('Error loading certificates:', error);
}
```
### Testing and Validation
Smartproxy supports extensive testing to ensure your proxy configurations operate as expected. Leveraging `tap` alongside TypeScript testing frameworks supports quality assurance:
```typescript
import { expect, tap } from '@push.rocks/tapbundle';
import { NetworkProxy } from '@push.rocks/smartproxy';
tap.test('Check proxied request returns status 200', async () => {
// Testing logic
});
tap.start();
```
### Conclusion
`@push.rocks/smartproxy` is designed for both simple and complex proxying demands, offering tools for high-performance and secure proxy management across diverse environments. Its efficient configurations are capable of supporting SSL redirection, WebSocket traffic, dynamic routing, and other advanced functionalities, making it indispensable for developers seeking robust and adaptable proxy solutions. By integrating these capabilities with ease of use, `smartproxy` stands out as an essential tool in modern software architecture.
## License and Legal Information
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

View File

@ -8,85 +8,79 @@ const TEST_SERVER_PORT = 4000;
const PROXY_PORT = 4001;
const TEST_DATA = 'Hello through port proxy!';
// Helper function to create a test TCP server
function createTestServer(port: number): Promise<net.Server> {
// Helper: Creates a test TCP server that listens on a given port and host.
function createTestServer(port: number, host: string = 'localhost'): Promise<net.Server> {
return new Promise((resolve) => {
const server = net.createServer((socket) => {
socket.on('data', (data) => {
// Echo the received data back
// Echo the received data back with a prefix.
socket.write(`Echo: ${data.toString()}`);
});
socket.on('error', (error) => {
console.error('[Test Server] Socket error:', error);
console.error(`[Test Server] Socket error on ${host}:${port}:`, error);
});
});
server.listen(port, () => {
console.log(`[Test Server] Listening on port ${port}`);
server.listen(port, host, () => {
console.log(`[Test Server] Listening on ${host}:${port}`);
resolve(server);
});
});
}
// Helper function to create a test client connection
// Helper: Creates a test client connection.
function createTestClient(port: number, data: string): Promise<string> {
return new Promise((resolve, reject) => {
const client = new net.Socket();
let response = '';
client.connect(port, 'localhost', () => {
console.log('[Test Client] Connected to server');
client.write(data);
});
client.on('data', (chunk) => {
response += chunk.toString();
client.end();
});
client.on('end', () => {
resolve(response);
});
client.on('error', (error) => {
reject(error);
});
client.on('end', () => resolve(response));
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 () => {
testServer = await createTestServer(TEST_SERVER_PORT);
portProxy = new PortProxy({
fromPort: PROXY_PORT,
toPort: TEST_SERVER_PORT,
toHost: 'localhost',
domains: [],
targetIP: 'localhost',
domainConfigs: [],
sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1']
defaultAllowedIPs: ['127.0.0.1'],
globalPortRanges: []
});
});
// Test that the proxy starts and its servers are listening.
tap.test('should start port proxy', async () => {
await portProxy.start();
expect(portProxy.netServer.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 () => {
const response = await createTestClient(PROXY_PORT, 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 () => {
// Create a new proxy instance with a custom host
const customHostProxy = new PortProxy({
fromPort: PROXY_PORT + 1,
toPort: TEST_SERVER_PORT,
toHost: '127.0.0.1',
domains: [],
targetIP: '127.0.0.1',
domainConfigs: [],
sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1']
defaultAllowedIPs: ['127.0.0.1'],
globalPortRanges: []
});
await customHostProxy.start();
@ -95,148 +89,151 @@ tap.test('should forward TCP connections to custom host', async () => {
await customHostProxy.stop();
});
tap.test('should forward connections based on domain-specific target IP', async () => {
// Create a second test server on a different port
const TEST_SERVER_PORT_2 = TEST_SERVER_PORT + 100;
const testServer2 = await createTestServer(TEST_SERVER_PORT_2);
// Test forced domain routing via port-range configuration.
// In this test, we want to forward to a different IP (using '127.0.0.2')
// while keeping the same port. We create a test server on '127.0.0.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({
fromPort: PROXY_PORT + 2,
toPort: TEST_SERVER_PORT, // default port
toHost: 'localhost', // default host
domains: [{
domain: 'domain1.test',
fromPort: forcedProxyPort,
toPort: TEST_SERVER_PORT, // default target port (unused for forced domain)
targetIP: 'localhost',
domainConfigs: [{
domains: ['forced.test'],
allowedIPs: ['127.0.0.1'],
targetIP: '127.0.0.1'
}, {
domain: 'domain2.test',
allowedIPs: ['127.0.0.1'],
targetIP: 'localhost'
targetIPs: ['127.0.0.2'], // Use a different IP than the default.
portRanges: [{ from: forcedProxyPort, to: forcedProxyPort }]
}],
sniEnabled: false, // We'll test without SNI first since this is a TCP proxy test
defaultAllowedIPs: ['127.0.0.1']
sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1'],
globalPortRanges: [{ from: forcedProxyPort, to: forcedProxyPort }]
});
await domainProxy.start();
// Test default connection (should use default host)
const response1 = await createTestClient(PROXY_PORT + 2, TEST_DATA);
expect(response1).toEqual(`Echo: ${TEST_DATA}`);
// Create another proxy with different default host
const domainProxy2 = new PortProxy({
fromPort: PROXY_PORT + 3,
toPort: TEST_SERVER_PORT,
toHost: '127.0.0.1',
domains: [],
sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1']
});
await domainProxy2.start();
const response2 = await createTestClient(PROXY_PORT + 3, TEST_DATA);
expect(response2).toEqual(`Echo: ${TEST_DATA}`);
// When connecting to forcedProxyPort, forced domain handling triggers,
// so the proxy will connect to '127.0.0.2' on the same port.
const response = await createTestClient(forcedProxyPort, TEST_DATA);
expect(response).toEqual(`Echo: ${TEST_DATA}`);
await domainProxy.stop();
await domainProxy2.stop();
await new Promise<void>((resolve) => testServer2.close(() => resolve()));
});
// Test handling of multiple concurrent connections.
tap.test('should handle multiple concurrent connections', async () => {
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}`)
);
const responses = await Promise.all(requests);
responses.forEach((response, i) => {
expect(response).toEqual(`Echo: ${TEST_DATA} ${i + 1}`);
});
});
// Test connection timeout handling.
tap.test('should handle connection timeouts', async () => {
const client = new net.Socket();
await new Promise<void>((resolve) => {
client.connect(PROXY_PORT, 'localhost', () => {
// Don't send any data, just wait for timeout
client.on('close', () => {
resolve();
});
// Do not send any data to trigger a timeout.
client.on('close', () => resolve());
});
});
});
// Test stopping the port proxy.
tap.test('should stop port proxy', async () => {
await portProxy.stop();
expect(portProxy.netServer.listening).toBeFalse();
expect((portProxy as any).netServers.every((server: net.Server) => !server.listening)).toBeTrue();
});
// Cleanup
// Test chained proxies with and without source IP preservation.
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({
fromPort: PROXY_PORT + 4,
toPort: PROXY_PORT + 5,
toHost: 'localhost',
domains: [],
targetIP: 'localhost',
domainConfigs: [],
sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1', '::ffff:127.0.0.1']
defaultAllowedIPs: ['127.0.0.1', '::ffff:127.0.0.1'],
globalPortRanges: []
});
const secondProxyDefault = new PortProxy({
fromPort: PROXY_PORT + 5,
toPort: TEST_SERVER_PORT,
toHost: 'localhost',
domains: [],
targetIP: 'localhost',
domainConfigs: [],
sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1', '::ffff:127.0.0.1']
defaultAllowedIPs: ['127.0.0.1', '::ffff:127.0.0.1'],
globalPortRanges: []
});
await secondProxyDefault.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);
expect(response1).toEqual(`Echo: ${TEST_DATA}`);
await firstProxyDefault.stop();
await secondProxyDefault.stop();
// Test 2: With IP preservation
// Chained proxies with IP preservation.
const firstProxyPreserved = new PortProxy({
fromPort: PROXY_PORT + 6,
toPort: PROXY_PORT + 7,
toHost: 'localhost',
domains: [],
targetIP: 'localhost',
domainConfigs: [],
sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1'],
preserveSourceIP: true
preserveSourceIP: true,
globalPortRanges: []
});
const secondProxyPreserved = new PortProxy({
fromPort: PROXY_PORT + 7,
toPort: TEST_SERVER_PORT,
toHost: 'localhost',
domains: [],
targetIP: 'localhost',
domainConfigs: [],
sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1'],
preserveSourceIP: true
preserveSourceIP: true,
globalPortRanges: []
});
await secondProxyPreserved.start();
await firstProxyPreserved.start();
// This should work with just IPv4 because source IP is preserved
const response2 = await createTestClient(PROXY_PORT + 6, TEST_DATA);
expect(response2).toEqual(`Echo: ${TEST_DATA}`);
await firstProxyPreserved.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 () => {
await new Promise<void>((resolve) => testServer.close(() => resolve()));
});
@ -245,9 +242,9 @@ process.on('exit', () => {
if (testServer) {
testServer.close();
}
if (portProxy && portProxy.netServer) {
if (portProxy && (portProxy as any).netServers) {
portProxy.stop();
}
});
export default tap.start();
export default tap.start();

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartproxy',
version: '3.10.5',
description: 'a proxy for handling high workloads of proxying'
version: '3.18.2',
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.'
}

183
ts/classes.iptablesproxy.ts Normal file
View File

@ -0,0 +1,183 @@
import { exec, execSync } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
/**
* Settings for IPTablesProxy.
*/
export interface IIpTableProxySettings {
fromPort: number;
toPort: number;
toHost?: string; // Target host for proxying; defaults to 'localhost'
preserveSourceIP?: boolean; // If true, the original source IP is preserved.
deleteOnExit?: boolean; // If true, clean up marked iptables rules before process exit.
}
/**
* IPTablesProxy sets up iptables NAT rules to forward TCP traffic.
* It only supports basic port forwarding and uses iptables comments to tag rules.
*/
export class IPTablesProxy {
public settings: IIpTableProxySettings;
private rulesInstalled: boolean = false;
private ruleTag: string;
constructor(settings: IIpTableProxySettings) {
this.settings = {
...settings,
toHost: settings.toHost || 'localhost',
};
// Generate a unique identifier for the rules added by this instance.
this.ruleTag = `IPTablesProxy:${Date.now()}:${Math.random().toString(36).substr(2, 5)}`;
// If deleteOnExit is true, register cleanup handlers.
if (this.settings.deleteOnExit) {
const cleanup = () => {
try {
IPTablesProxy.cleanSlateSync();
} catch (err) {
console.error('Error cleaning iptables rules on exit:', err);
}
};
process.on('exit', cleanup);
process.on('SIGINT', () => {
cleanup();
process.exit();
});
process.on('SIGTERM', () => {
cleanup();
process.exit();
});
}
}
/**
* Sets up iptables rules for port forwarding.
* The rules are tagged with a unique comment so that they can be identified later.
*/
public async start(): Promise<void> {
const dnatCmd = `iptables -t nat -A PREROUTING -p tcp --dport ${this.settings.fromPort} ` +
`-j DNAT --to-destination ${this.settings.toHost}:${this.settings.toPort} ` +
`-m comment --comment "${this.ruleTag}:DNAT"`;
try {
await execAsync(dnatCmd);
console.log(`Added iptables rule: ${dnatCmd}`);
this.rulesInstalled = true;
} catch (err) {
console.error(`Failed to add iptables DNAT rule: ${err}`);
throw err;
}
// If preserveSourceIP is false, add a MASQUERADE rule.
if (!this.settings.preserveSourceIP) {
const masqueradeCmd = `iptables -t nat -A POSTROUTING -p tcp -d ${this.settings.toHost} ` +
`--dport ${this.settings.toPort} -j MASQUERADE ` +
`-m comment --comment "${this.ruleTag}:MASQ"`;
try {
await execAsync(masqueradeCmd);
console.log(`Added iptables rule: ${masqueradeCmd}`);
} catch (err) {
console.error(`Failed to add iptables MASQUERADE rule: ${err}`);
// Roll back the DNAT rule if MASQUERADE fails.
try {
const rollbackCmd = `iptables -t nat -D PREROUTING -p tcp --dport ${this.settings.fromPort} ` +
`-j DNAT --to-destination ${this.settings.toHost}:${this.settings.toPort} ` +
`-m comment --comment "${this.ruleTag}:DNAT"`;
await execAsync(rollbackCmd);
this.rulesInstalled = false;
} catch (rollbackErr) {
console.error(`Rollback failed: ${rollbackErr}`);
}
throw err;
}
}
}
/**
* Removes the iptables rules that were added in start(), by matching the unique comment.
*/
public async stop(): Promise<void> {
if (!this.rulesInstalled) return;
const dnatDelCmd = `iptables -t nat -D PREROUTING -p tcp --dport ${this.settings.fromPort} ` +
`-j DNAT --to-destination ${this.settings.toHost}:${this.settings.toPort} ` +
`-m comment --comment "${this.ruleTag}:DNAT"`;
try {
await execAsync(dnatDelCmd);
console.log(`Removed iptables rule: ${dnatDelCmd}`);
} catch (err) {
console.error(`Failed to remove iptables DNAT rule: ${err}`);
}
if (!this.settings.preserveSourceIP) {
const masqueradeDelCmd = `iptables -t nat -D POSTROUTING -p tcp -d ${this.settings.toHost} ` +
`--dport ${this.settings.toPort} -j MASQUERADE ` +
`-m comment --comment "${this.ruleTag}:MASQ"`;
try {
await execAsync(masqueradeDelCmd);
console.log(`Removed iptables rule: ${masqueradeDelCmd}`);
} catch (err) {
console.error(`Failed to remove iptables MASQUERADE rule: ${err}`);
}
}
this.rulesInstalled = false;
}
/**
* Asynchronously cleans up any iptables rules in the nat table that were added by this module.
* It looks for rules with comments containing "IPTablesProxy:".
*/
public static async cleanSlate(): Promise<void> {
try {
const { stdout } = await execAsync('iptables-save -t nat');
const lines = stdout.split('\n');
const proxyLines = lines.filter(line => line.includes('IPTablesProxy:'));
for (const line of proxyLines) {
const trimmedLine = line.trim();
if (trimmedLine.startsWith('-A')) {
// Replace the "-A" with "-D" to form a deletion command.
const deleteRule = trimmedLine.replace('-A', '-D');
const cmd = `iptables -t nat ${deleteRule}`;
try {
await execAsync(cmd);
console.log(`Cleaned up iptables rule: ${cmd}`);
} catch (err) {
console.error(`Failed to remove iptables rule: ${cmd}`, err);
}
}
}
} catch (err) {
console.error(`Failed to run iptables-save: ${err}`);
}
}
/**
* Synchronously cleans up any iptables rules in the nat table that were added by this module.
* It looks for rules with comments containing "IPTablesProxy:".
* This method is intended for use in process exit handlers.
*/
public static cleanSlateSync(): void {
try {
const stdout = execSync('iptables-save -t nat').toString();
const lines = stdout.split('\n');
const proxyLines = lines.filter(line => line.includes('IPTablesProxy:'));
for (const line of proxyLines) {
const trimmedLine = line.trim();
if (trimmedLine.startsWith('-A')) {
const deleteRule = trimmedLine.replace('-A', '-D');
const cmd = `iptables -t nat ${deleteRule}`;
try {
execSync(cmd);
console.log(`Cleaned up iptables rule: ${cmd}`);
} catch (err) {
console.error(`Failed to remove iptables rule: ${cmd}`, err);
}
}
}
} catch (err) {
console.error(`Failed to run iptables-save: ${err}`);
}
}
}

View File

@ -55,8 +55,8 @@ export class Port80Handler {
if (this.acmeClient) {
return this.acmeClient;
}
// Generate a new account key.
this.accountKey = await acme.forge.createPrivateKey();
// Generate a new account key and convert Buffer to string.
this.accountKey = (await acme.forge.createPrivateKey()).toString();
this.acmeClient = new acme.Client({
directoryUrl: acme.directory.letsencrypt.production, // Use production for a real certificate
// For testing, you could use:
@ -170,7 +170,11 @@ export class Port80Handler {
domainInfo.challengeKeyAuthorization = keyAuthorization;
// Notify the ACME server that the challenge is ready.
await client.verifyChallenge(authz, challenge, keyAuthorization);
// The acme-client examples show that verifyChallenge takes three arguments:
// (authorization, challenge, keyAuthorization). However, the official TypeScript
// types appear to be out-of-sync. As a workaround, we cast client to 'any'.
await (client as any).verifyChallenge(authz, challenge, keyAuthorization);
await client.completeChallenge(challenge);
// Wait until the challenge is validated.
await client.waitForValidStatus(challenge);
@ -178,9 +182,12 @@ export class Port80Handler {
}
// Generate a CSR and a new private key for the domain.
const [csr, privateKey] = await acme.forge.createCsr({
// Convert the resulting Buffers to strings.
const [csrBuffer, privateKeyBuffer] = await acme.forge.createCsr({
commonName: domain,
});
const csr = csrBuffer.toString();
const privateKey = privateKeyBuffer.toString();
// Finalize the order and obtain the certificate.
await client.finalizeOrder(order, csr);
@ -195,8 +202,7 @@ export class Port80Handler {
delete domainInfo.challengeKeyAuthorization;
console.log(`Certificate obtained for ${domain}`);
// In a real application, you would persist the certificate and key,
// then reload your TLS server with the new credentials.
// In a production system, persist the certificate and key and reload your TLS server.
} catch (error) {
console.error(`Error during certificate issuance for ${domain}:`, error);
const domainInfo = this.domainCertificates.get(domain);
@ -206,7 +212,3 @@ export class Port80Handler {
}
}
}
// Example usage:
// const handler = new Port80Handler();
// handler.addDomain('example.com');

View File

@ -1,19 +1,25 @@
import * as plugins from './plugins.js';
/** Domain configuration with per-domain allowed port ranges */
export interface IDomainConfig {
domain: string; // Glob pattern for domain
allowedIPs: string[]; // Glob patterns for allowed IPs
targetIP?: string; // Optional target IP for this domain
domains: string[]; // Glob patterns for domain(s)
allowedIPs: string[]; // Glob patterns for allowed IPs
targetIPs?: string[]; // If multiple targetIPs are given, use round robin.
portRanges?: Array<{ from: number; to: number }>; // Optional port ranges
}
export interface IProxySettings extends plugins.tls.TlsOptions {
/** Port proxy settings including global allowed port ranges */
export interface IPortProxySettings extends plugins.tls.TlsOptions {
fromPort: number;
toPort: number;
toHost?: string; // Target host to proxy to, defaults to 'localhost'
domains: IDomainConfig[];
targetIP?: string; // Global target host to proxy to, defaults to 'localhost'
domainConfigs: IDomainConfig[];
sniEnabled?: boolean;
defaultAllowedIPs?: string[];
preserveSourceIP?: boolean;
maxConnectionLifetime?: number; // (ms) force cleanup of long-lived connections
globalPortRanges: Array<{ from: number; to: number }>; // Global allowed port ranges
forwardAllGlobalRanges?: boolean; // When true, forwards all connections on global port ranges to the global targetIP
}
/**
@ -84,16 +90,21 @@ interface IConnectionRecord {
outgoing: plugins.net.Socket | null;
incomingStartTime: number;
outgoingStartTime?: number;
lockedDomain?: string; // New field to lock this connection to the initial SNI
connectionClosed: boolean;
cleanupTimer?: NodeJS.Timeout; // Timer to force cleanup after max lifetime/inactivity
}
export class PortProxy {
netServer: plugins.net.Server;
settings: IProxySettings;
private netServers: plugins.net.Server[] = [];
settings: IPortProxySettings;
// Unified record tracking each connection pair.
private connectionRecords: Set<IConnectionRecord> = new Set();
private connectionLogger: NodeJS.Timeout | null = null;
// Map to track round robin indices for each domain config.
private domainTargetIndices: Map<IDomainConfig, number> = new Map();
private terminationStats: {
incoming: Record<string, number>;
outgoing: Record<string, number>;
@ -102,10 +113,11 @@ export class PortProxy {
outgoing: {},
};
constructor(settings: IProxySettings) {
constructor(settingsArg: IPortProxySettings) {
this.settings = {
...settings,
toHost: settings.toHost || 'localhost',
...settingsArg,
targetIP: settingsArg.targetIP || 'localhost',
maxConnectionLifetime: settingsArg.maxConnectionLifetime || 600000,
};
}
@ -113,40 +125,21 @@ export class PortProxy {
this.terminationStats[side][reason] = (this.terminationStats[side][reason] || 0) + 1;
}
private getTargetIP(domainConfig: IDomainConfig): string {
if (domainConfig.targetIPs && domainConfig.targetIPs.length > 0) {
const currentIndex = this.domainTargetIndices.get(domainConfig) || 0;
const ip = domainConfig.targetIPs[currentIndex % domainConfig.targetIPs.length];
this.domainTargetIndices.set(domainConfig, currentIndex + 1);
return ip;
}
return this.settings.targetIP!;
}
public async start() {
// Helper to forcefully destroy sockets.
const cleanUpSockets = (socketA: plugins.net.Socket, socketB?: plugins.net.Socket) => {
if (!socketA.destroyed) socketA.destroy();
if (socketB && !socketB.destroyed) socketB.destroy();
};
// Normalize an IP to include both IPv4 and IPv6 representations.
const normalizeIP = (ip: string): string[] => {
if (ip.startsWith('::ffff:')) {
const ipv4 = ip.slice(7);
return [ip, ipv4];
}
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
return [ip, `::ffff:${ip}`];
}
return [ip];
};
// Check if a given IP matches any of the glob patterns.
const isAllowed = (ip: string, patterns: string[]): boolean => {
const normalizedIPVariants = normalizeIP(ip);
const expandedPatterns = patterns.flatMap(normalizeIP);
return normalizedIPVariants.some(ipVariant =>
expandedPatterns.some(pattern => plugins.minimatch(ipVariant, pattern))
);
};
// Find a matching domain config based on the SNI.
const findMatchingDomain = (serverName: string): IDomainConfig | undefined =>
this.settings.domains.find(config => plugins.minimatch(serverName, config.domain));
this.netServer = plugins.net.createServer((socket: plugins.net.Socket) => {
// Define a unified connection handler for all listening ports.
const connectionHandler = (socket: plugins.net.Socket) => {
const remoteIP = socket.remoteAddress || '';
const localPort = socket.localPort; // The port on which this connection was accepted.
const connectionRecord: IConnectionRecord = {
incoming: socket,
outgoing: null,
@ -154,17 +147,21 @@ export class PortProxy {
connectionClosed: false,
};
this.connectionRecords.add(connectionRecord);
console.log(`New connection from ${remoteIP}. Active connections: ${this.connectionRecords.size}`);
console.log(`New connection from ${remoteIP} on port ${localPort}. Active connections: ${this.connectionRecords.size}`);
let initialDataReceived = false;
let incomingTerminationReason: string | null = null;
let outgoingTerminationReason: string | null = null;
// Ensure cleanup happens only once for the entire connection record.
const cleanupOnce = () => {
const cleanupOnce = async () => {
if (!connectionRecord.connectionClosed) {
connectionRecord.connectionClosed = true;
cleanUpSockets(connectionRecord.incoming, connectionRecord.outgoing || undefined);
if (connectionRecord.cleanupTimer) {
clearTimeout(connectionRecord.cleanupTimer);
}
if (!socket.destroyed) socket.destroy();
if (connectionRecord.outgoing && !connectionRecord.outgoing.destroyed) connectionRecord.outgoing.destroy();
this.connectionRecords.delete(connectionRecord);
console.log(`Connection from ${remoteIP} terminated. Active connections: ${this.connectionRecords.size}`);
}
@ -219,28 +216,36 @@ export class PortProxy {
cleanupOnce();
};
const setupConnection = (serverName: string, initialChunk?: Buffer) => {
const defaultAllowed = this.settings.defaultAllowedIPs && isAllowed(remoteIP, this.settings.defaultAllowedIPs);
/**
* Sets up the connection to the target host.
* @param serverName - The SNI hostname (unused when forcedDomain is provided).
* @param initialChunk - Optional initial data chunk.
* @param forcedDomain - If provided, overrides SNI/domain lookup (used for port-based routing).
* @param overridePort - If provided, use this port for the outgoing connection (typically the same as the incoming port).
*/
const setupConnection = (serverName: string, initialChunk?: Buffer, forcedDomain?: IDomainConfig, overridePort?: number) => {
// If a forcedDomain is provided (port-based routing), use it; otherwise, use SNI-based lookup.
const domainConfig = forcedDomain
? forcedDomain
: (serverName ? this.settings.domainConfigs.find(config =>
config.domains.some(d => plugins.minimatch(serverName, d))
) : undefined);
if (!defaultAllowed && serverName) {
const domainConfig = findMatchingDomain(serverName);
if (!domainConfig) {
return rejectIncomingConnection('rejected', `Connection rejected: No matching domain config for ${serverName} from ${remoteIP}`);
}
// If a matching domain config exists, check its allowedIPs.
if (domainConfig) {
if (!isAllowed(remoteIP, domainConfig.allowedIPs)) {
return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed for domain ${serverName}`);
return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed for domain ${domainConfig.domains.join(', ')}`);
}
} else if (this.settings.defaultAllowedIPs) {
// Only check default allowed IPs if no domain config matched.
if (!isAllowed(remoteIP, this.settings.defaultAllowedIPs)) {
return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed by default allowed list`);
}
} else if (!defaultAllowed && !serverName) {
return rejectIncomingConnection('rejected', `Connection rejected: No SNI and IP ${remoteIP} not in default allowed list`);
} else if (defaultAllowed && !serverName) {
console.log(`Connection allowed: IP ${remoteIP} is in default allowed list`);
}
const domainConfig = serverName ? findMatchingDomain(serverName) : undefined;
const targetHost = domainConfig?.targetIP || this.settings.toHost!;
const targetHost = domainConfig ? this.getTargetIP(domainConfig) : this.settings.targetIP!;
const connectionOptions: plugins.net.NetConnectOpts = {
host: targetHost,
port: this.settings.toPort,
port: overridePort !== undefined ? overridePort : this.settings.toPort,
};
if (this.settings.preserveSourceIP) {
connectionOptions.localAddress = remoteIP.replace('::ffff:', '');
@ -251,8 +256,8 @@ export class PortProxy {
connectionRecord.outgoingStartTime = Date.now();
console.log(
`Connection established: ${remoteIP} -> ${targetHost}:${this.settings.toPort}` +
`${serverName ? ` (SNI: ${serverName})` : ''}`
`Connection established: ${remoteIP} -> ${targetHost}:${connectionOptions.port}` +
`${serverName ? ` (SNI: ${serverName})` : forcedDomain ? ` (Port-based for domain: ${forcedDomain.domains.join(', ')})` : ''}`
);
if (initialChunk) {
@ -262,6 +267,7 @@ export class PortProxy {
socket.pipe(targetSocket);
targetSocket.pipe(socket);
// Attach error and close handlers.
socket.on('error', handleError('incoming'));
targetSocket.on('error', handleError('outgoing'));
socket.on('close', handleClose('incoming'));
@ -284,8 +290,81 @@ export class PortProxy {
});
socket.on('end', handleClose('incoming'));
targetSocket.on('end', handleClose('outgoing'));
// Initialize a cleanup timer for max connection lifetime.
if (this.settings.maxConnectionLifetime) {
let incomingActive = false;
let outgoingActive = false;
const resetCleanupTimer = () => {
if (this.settings.maxConnectionLifetime) {
if (connectionRecord.cleanupTimer) {
clearTimeout(connectionRecord.cleanupTimer);
}
connectionRecord.cleanupTimer = setTimeout(() => {
console.log(`Connection from ${remoteIP} exceeded max lifetime with inactivity (${this.settings.maxConnectionLifetime}ms), forcing cleanup.`);
cleanupOnce();
}, this.settings.maxConnectionLifetime);
}
};
resetCleanupTimer();
socket.on('data', () => {
incomingActive = true;
if (incomingActive && outgoingActive) {
resetCleanupTimer();
incomingActive = false;
outgoingActive = false;
}
});
targetSocket.on('data', () => {
outgoingActive = true;
if (incomingActive && outgoingActive) {
resetCleanupTimer();
incomingActive = false;
outgoingActive = false;
}
});
}
};
// --- PORT RANGE-BASED HANDLING ---
// Only apply port-based rules if the incoming port is within one of the global port ranges.
if (this.settings.globalPortRanges && isPortInRanges(localPort, this.settings.globalPortRanges)) {
if (this.settings.forwardAllGlobalRanges) {
if (this.settings.defaultAllowedIPs && !isAllowed(remoteIP, this.settings.defaultAllowedIPs)) {
console.log(`Connection from ${remoteIP} rejected: IP ${remoteIP} not allowed in global default allowed list.`);
socket.end();
return;
}
console.log(`Port-based connection from ${remoteIP} on port ${localPort} forwarded to global target IP ${this.settings.targetIP}.`);
setupConnection('', undefined, {
domains: ['global'],
allowedIPs: this.settings.defaultAllowedIPs || [],
targetIPs: [this.settings.targetIP!],
portRanges: []
}, localPort);
return;
} else {
// Attempt to find a matching forced domain config based on the local port.
const forcedDomain = this.settings.domainConfigs.find(
domain => domain.portRanges && domain.portRanges.length > 0 && isPortInRanges(localPort, domain.portRanges)
);
if (forcedDomain) {
if (!isAllowed(remoteIP, forcedDomain.allowedIPs)) {
console.log(`Connection from ${remoteIP} rejected: IP not allowed for domain ${forcedDomain.domains.join(', ')} on port ${localPort}.`);
socket.end();
return;
}
console.log(`Port-based connection from ${remoteIP} on port ${localPort} matched domain ${forcedDomain.domains.join(', ')}.`);
setupConnection('', undefined, forcedDomain, localPort);
return;
}
// Fall through to SNI/default handling if no forced domain config is found.
}
}
// --- FALLBACK: SNI-BASED HANDLING (or default when SNI is disabled) ---
if (this.settings.sniEnabled) {
socket.setTimeout(5000, () => {
console.log(`Initial data timeout for ${remoteIP}`);
@ -297,7 +376,22 @@ export class PortProxy {
socket.setTimeout(0);
initialDataReceived = true;
const serverName = extractSNI(chunk) || '';
// Lock the connection to the negotiated SNI.
connectionRecord.lockedDomain = serverName;
console.log(`Received connection from ${remoteIP} with SNI: ${serverName}`);
// Delay adding the renegotiation listener until the next tick,
// so the initial ClientHello is not reprocessed.
setImmediate(() => {
socket.on('data', (renegChunk: Buffer) => {
if (renegChunk.length > 0 && renegChunk.readUInt8(0) === 22) {
const newSNI = extractSNI(renegChunk);
if (newSNI && newSNI !== connectionRecord.lockedDomain) {
console.log(`Rehandshake detected with different SNI: ${newSNI} vs locked ${connectionRecord.lockedDomain}. Terminating connection.`);
cleanupOnce();
}
}
});
});
setupConnection(serverName, chunk);
});
} else {
@ -307,18 +401,38 @@ export class PortProxy {
}
setupConnection('');
}
})
.on('error', (err: Error) => {
console.log(`Server Error: ${err.message}`);
})
.listen(this.settings.fromPort, () => {
console.log(
`PortProxy -> OK: Now listening on port ${this.settings.fromPort}` +
`${this.settings.sniEnabled ? ' (SNI passthrough enabled)' : ''}`
);
});
};
// Every 10 seconds log active connection count and longest running durations.
// --- SETUP LISTENERS ---
// Determine which ports to listen on.
const listeningPorts = new Set<number>();
if (this.settings.globalPortRanges && this.settings.globalPortRanges.length > 0) {
// Listen on every port defined by the global ranges.
for (const range of this.settings.globalPortRanges) {
for (let port = range.from; port <= range.to; port++) {
listeningPorts.add(port);
}
}
// Also ensure the default fromPort is listened to if it isn't already in the ranges.
listeningPorts.add(this.settings.fromPort);
} else {
listeningPorts.add(this.settings.fromPort);
}
// Create a server for each port.
for (const port of listeningPorts) {
const server = plugins.net
.createServer(connectionHandler)
.on('error', (err: Error) => {
console.log(`Server Error on port ${port}: ${err.message}`);
});
server.listen(port, () => {
console.log(`PortProxy -> OK: Now listening on port ${port}${this.settings.sniEnabled ? ' (SNI passthrough enabled)' : ''}`);
});
this.netServers.push(server);
}
// Log active connection count and longest running durations every 10 seconds.
this.connectionLogger = setInterval(() => {
const now = Date.now();
let maxIncoming = 0;
@ -339,14 +453,41 @@ export class PortProxy {
}
public async stop() {
const done = plugins.smartpromise.defer();
this.netServer.close(() => {
done.resolve();
});
// Close all servers.
const closePromises: Promise<void>[] = this.netServers.map(
server =>
new Promise<void>((resolve) => {
server.close(() => resolve());
})
);
if (this.connectionLogger) {
clearInterval(this.connectionLogger);
this.connectionLogger = null;
}
await done.promise;
await Promise.all(closePromises);
}
}
}
// Helper: Check if a port falls within any of the given port ranges.
const isPortInRanges = (port: number, ranges: Array<{ from: number; to: number }>): boolean => {
return ranges.some(range => port >= range.from && port <= range.to);
};
// Helper: Check if a given IP matches any of the glob patterns.
const isAllowed = (ip: string, patterns: string[]): boolean => {
const normalizeIP = (ip: string): string[] => {
if (ip.startsWith('::ffff:')) {
const ipv4 = ip.slice(7);
return [ip, ipv4];
}
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
return [ip, `::ffff:${ip}`];
}
return [ip];
};
const normalizedIPVariants = normalizeIP(ip);
const expandedPatterns = patterns.flatMap(normalizeIP);
return normalizedIPVariants.some(ipVariant =>
expandedPatterns.some(pattern => plugins.minimatch(ipVariant, pattern))
);
};

View File

@ -1,3 +1,5 @@
export * from './classes.iptablesproxy.js';
export * from './classes.networkproxy.js';
export * from './classes.portproxy.js';
export * from './classes.port80handler.js';
export * from './classes.sslredirect.js';