From 0e634c46a67003c5eeee83b9a1ad0d488134462a Mon Sep 17 00:00:00 2001 From: Philipp Kunz Date: Sat, 3 May 2025 13:19:23 +0000 Subject: [PATCH] BREAKING CHANGE(smartproxy): Update documentation and refactor core proxy components; remove legacy performRenewals method from SmartProxy; update router type imports and adjust test suites for improved coverage --- changelog.md | 9 + readme.md | 699 +++++++++---------------- readme.plan.md | 6 +- test/{test.ts => test.networkproxy.ts} | 0 test/test.smartproxy.renewals.node.ts | 45 -- ts/00_commitinfo_data.ts | 2 +- ts/classes.router.ts | 28 +- ts/smartproxy/classes.smartproxy.ts | 21 - 8 files changed, 276 insertions(+), 534 deletions(-) rename test/{test.ts => test.networkproxy.ts} (100%) delete mode 100644 test/test.smartproxy.renewals.node.ts diff --git a/changelog.md b/changelog.md index 1f878b3..175582c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2025-05-03 - 10.0.0 - BREAKING CHANGE(smartproxy) +Update documentation and refactor core proxy components; remove legacy performRenewals method from SmartProxy; update router type imports and adjust test suites for improved coverage + +- Expanded README with detailed Quick Start examples for HTTP/HTTPS reverse proxy, ACME integration, HTTP→HTTPS redirect, nftables port forwarding, and SNI-based TCP proxying +- Updated readme.plan.md checkboxes to show completed tasks +- Refactored ProxyRouter to import types via plugins.tsclass, ensuring consistency in type imports +- Removed deprecated performRenewals method from SmartProxy, constituting a breaking change for users relying on it +- Updated multiple test suites (router, networkproxy, certprovisioner, etc.) to reflect new behaviors and improved diagnostics + ## 2025-05-02 - 9.0.0 - BREAKING CHANGE(acme) Refactor ACME configuration and certificate provisioning by replacing legacy port80HandlerConfig with unified acme options and updating CertProvisioner event subscriptions diff --git a/readme.md b/readme.md index a73c40b..b651362 100644 --- a/readme.md +++ b/readme.md @@ -1,11 +1,178 @@ # @push.rocks/smartproxy -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. +A high-performance proxy toolkit for Node.js, offering: +- HTTP/HTTPS reverse proxy with TLS termination and WebSocket support +- Automatic ACME certificate management (HTTP-01) +- Low-level port forwarding via nftables +- HTTP-to-HTTPS and custom URL redirects +- Advanced TCP/SNI-based proxying with IP filtering and rules + +## Exports +The following classes and interfaces are provided: + +- **NetworkProxy** (ts/networkproxy/classes.np.networkproxy.ts) + HTTP/HTTPS reverse proxy with TLS termination, WebSocket support, + connection pooling, and optional ACME integration. +- **Port80Handler** (ts/port80handler/classes.port80handler.ts) + ACME HTTP-01 challenge handler and certificate manager. +- **NfTablesProxy** (ts/nfttablesproxy/classes.nftablesproxy.ts) + Low-level port forwarding using nftables NAT rules. +- **Redirect**, **SslRedirect** (ts/redirect/classes.redirect.ts) + HTTP/HTTPS redirect server and shortcut for HTTP→HTTPS. +- **SmartProxy** (ts/smartproxy/classes.smartproxy.ts) + TCP/SNI-based proxy with dynamic routing, IP filtering, and unified certificates. +- **SniHandler** (ts/smartproxy/classes.pp.snihandler.ts) + Static utilities to extract SNI hostnames from TLS handshakes. +- **Interfaces** + - IPortProxySettings, IDomainConfig (ts/smartproxy/classes.pp.interfaces.ts) + - INetworkProxyOptions (ts/networkproxy/classes.np.types.ts) + - IAcmeOptions, IDomainOptions, IForwardConfig (ts/common/types.ts) + - INfTableProxySettings (ts/nfttablesproxy/classes.nftablesproxy.ts) + +## Installation +Install via npm: +```bash +npm install @push.rocks/smartproxy +``` + +## Quick Start + +### 1. HTTP(S) Reverse Proxy (NetworkProxy) +```typescript +import { NetworkProxy } from '@push.rocks/smartproxy'; + +const proxy = new NetworkProxy({ port: 443 }); +await proxy.start(); + +await proxy.updateProxyConfigs([ + { + hostName: 'example.com', + destinationIps: ['127.0.0.1'], + destinationPorts: [3000], + publicKey: fs.readFileSync('cert.pem', 'utf8'), + privateKey: fs.readFileSync('key.pem', 'utf8'), + } +]); + +// Add default headers to all responses +await proxy.addDefaultHeaders({ + 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains' +}); +// ... +await proxy.stop(); +``` + +### 2. HTTP→HTTPS Redirect (Redirect / SslRedirect) +```typescript +import { Redirect, SslRedirect } from '@push.rocks/smartproxy'; +import * as fs from 'fs'; + +// Custom redirect rules +const redirect = new Redirect({ + httpPort: 80, + httpsPort: 443, + sslOptions: { + key: fs.readFileSync('key.pem'), + cert: fs.readFileSync('cert.pem'), + }, + rules: [ + { + fromProtocol: 'http', + fromHost: '*', + toProtocol: 'https', + toHost: '$1', + statusCode: 301 + } + ] +}); +await redirect.start(); + +// Quick HTTP→HTTPS helper on port 80 +const quick = new SslRedirect(80); +await quick.start(); +``` + +### 3. Automatic Certificates (ACME Port80Handler) +```typescript +import { Port80Handler } from '@push.rocks/smartproxy'; + +// Configure ACME on port 80 with contact email +const acme = new Port80Handler({ + port: 80, + contactEmail: 'admin@example.com', + useProduction: true, + renewThresholdDays: 30 +}); +acme.on('certificate-issued', evt => { + console.log(`Certificate ready for ${evt.domain}, expires ${evt.expiryDate}`); +}); +await acme.start(); +acme.addDomain({ + domainName: 'example.com', + sslRedirect: true, + acmeMaintenance: true +}); +``` + +### 4. Low-Level Port Forwarding (NfTablesProxy) +```typescript +import { NfTablesProxy } from '@push.rocks/smartproxy'; + +// Forward port 80→8080 with source IP preservation +const nft = new NfTablesProxy({ + fromPort: 80, + toPort: 8080, + toHost: 'localhost', + preserveSourceIP: true, + deleteOnExit: true +}); +await nft.start(); +// ... +await nft.stop(); +``` + +### 5. TCP/SNI Proxy (SmartProxy) +```typescript +import { SmartProxy } from '@push.rocks/smartproxy'; + +const smart = new SmartProxy({ + fromPort: 443, + toPort: 8443, + domainConfigs: [ + { + domains: ['example.com', '*.example.com'], + allowedIPs: ['*'], + targetIPs: ['127.0.0.1'], + } + ], + sniEnabled: true +}); +smart.on('certificate', evt => console.log(evt)); +await smart.start(); +// Update domains later +await smart.updateDomainConfigs([/* new configs */]); +``` + +### 6. SNI Utilities (SniHandler) +```js +import { SniHandler } from '@push.rocks/smartproxy'; + +// Extract SNI from a TLS ClientHello buffer +const sni = SniHandler.extractSNI(buffer); + +// Reassemble fragmented ClientHello +const complete = SniHandler.handleFragmentedClientHello(buf, connId); +``` + +## API Reference +For full configuration options and type definitions, see the TypeScript interfaces in the `ts/` directory: +- `INetworkProxyOptions` (ts/networkproxy/classes.np.types.ts) +- `IAcmeOptions`, `IDomainOptions`, `IForwardConfig` (ts/common/types.ts) +- `INfTableProxySettings` (ts/nfttablesproxy/classes.nftablesproxy.ts) +- `IPortProxySettings`, `IDomainConfig` (ts/smartproxy/classes.pp.interfaces.ts) ## Architecture & Flow Diagrams -### Component Architecture -The diagram below illustrates the main components of SmartProxy and how they interact: ```mermaid flowchart TB @@ -13,12 +180,12 @@ flowchart TB subgraph "SmartProxy Components" direction TB - HTTP80[HTTP Port 80\nSslRedirect] + HTTP80[HTTP Port 80\nRedirect / SslRedirect] HTTPS443[HTTPS Port 443\nNetworkProxy] - SmartProxy[SmartProxy\nwith SNI routing] + SmartProxy[SmartProxy\n(TCP/SNI Proxy)] NfTables[NfTablesProxy] Router[ProxyRouter] - ACME[Port80Handler\nACME/Let's Encrypt] + ACME[Port80Handler\n(ACME HTTP-01)] Certs[(SSL Certificates)] end @@ -190,470 +357,104 @@ sequenceDiagram ## Features -- **HTTPS Reverse Proxy** - Route traffic to backend services based on hostname with TLS termination -- **WebSocket Support** - Full WebSocket proxying with heartbeat monitoring -- **TCP Connection Handling** - Advanced connection handling with SNI inspection and domain-based routing -- **Enhanced TLS Handling** - Robust TLS handshake processing with improved certificate error handling -- **HTTP to HTTPS Redirection** - Automatically redirect HTTP requests to HTTPS -- **Let's Encrypt Integration** - Automatic certificate management using ACME protocol -- **IP Filtering** - Control access with IP allow/block lists using glob patterns - - **NfTables Integration** - Direct manipulation of nftables for advanced low-level port forwarding +- HTTP/HTTPS Reverse Proxy (NetworkProxy) + • TLS termination, virtual-host routing, HTTP/2 & WebSocket support, pooling & metrics -## Certificate Provider Hook & Events +- Automatic ACME Certificates (Port80Handler) + • HTTP-01 challenge handling, certificate issuance/renewal, pluggable storage -You can customize how certificates are provisioned per domain by using the `certProvider` callback and listen for certificate events emitted by `SmartProxy`. +- Low-Level Port Forwarding (NfTablesProxy) + • nftables NAT rules for ports/ranges, IPv4/IPv6, IP filtering, QoS & ipset support -```typescript -import { SmartProxy } from '@push.rocks/smartproxy'; -import * as fs from 'fs'; +- Custom Redirects (Redirect / SslRedirect) + • URL redirects with wildcard host/path, template variables & status codes -// Example certProvider: static for a specific domain, HTTP-01 otherwise -const certProvider = async (domain: string) => { - if (domain === 'static.example.com') { - // Load from disk or vault - return { - id: 'static-cert', - domainName: domain, - created: Date.now(), - validUntil: Date.now() + 90 * 24 * 60 * 60 * 1000, - privateKey: fs.readFileSync('/etc/ssl/private/static.key', 'utf8'), - publicKey: fs.readFileSync('/etc/ssl/certs/static.crt', 'utf8'), - csr: '' - }; - } - // Fallback to ACME HTTP-01 challenge - return 'http01'; -}; +- TCP/SNI Proxy (SmartProxy) + • SNI-based routing, IP allow/block lists, port ranges, timeouts & graceful shutdown -const proxy = new SmartProxy({ - fromPort: 80, - toPort: 8080, - domainConfigs: [{ - domains: ['static.example.com', 'dynamic.example.com'], - allowedIPs: ['*'] - }], - certProvider -}); +- SNI Utilities (SniHandler) + • Robust ClientHello parsing, fragmentation & session resumption support -// Listen for certificate issuance or renewal -proxy.on('certificate', (evt) => { - console.log(`Certificate for ${evt.domain} ready, expires on ${evt.expiryDate}`); -}); +## Certificate Hooks & Events -await proxy.start(); -``` +Listen for certificate events via EventEmitter: +- **Port80Handler**: + - `certificate-issued`, `certificate-renewed`, `certificate-failed` + - `manager-started`, `manager-stopped`, `request-forwarded` +- **SmartProxy**: + - `certificate` (domain, publicKey, privateKey, expiryDate, source, isRenewal) + +Provide a `certProvider(domain)` in SmartProxy settings to supply static certs or return `'http01'`. ## Configuration Options -### backendProtocol +### NetworkProxy (INetworkProxyOptions) +- `port` (number, required) +- `backendProtocol` ('http1'|'http2', default 'http1') +- `maxConnections` (number, default 10000) +- `keepAliveTimeout` (ms, default 120000) +- `headersTimeout` (ms, default 60000) +- `cors` (object) +- `connectionPoolSize` (number, default 50) +- `logLevel` ('error'|'warn'|'info'|'debug') +- `acme` (IAcmeOptions) +- `useExternalPort80Handler` (boolean) +- `portProxyIntegration` (boolean) -Type: 'http1' | 'http2' (default: 'http1') +### Port80Handler (IAcmeOptions) +- `enabled` (boolean, default true) +- `port` (number, default 80) +- `contactEmail` (string) +- `useProduction` (boolean, default false) +- `renewThresholdDays` (number, default 30) +- `autoRenew` (boolean, default true) +- `certificateStore` (string) +- `skipConfiguredCerts` (boolean) +- `domainForwards` (IDomainForwardConfig[]) -Controls the protocol used when proxying requests to backend services. By default, the proxy uses HTTP/1.x (`http.request`). Setting `backendProtocol: 'http2'` establishes HTTP/2 client sessions (`http2.connect`) to your backends for full end-to-end HTTP/2 support (assuming your backend servers support HTTP/2). +### NfTablesProxy (INfTableProxySettings) +- `fromPort` / `toPort` (number|range|array) +- `toHost` (string, default 'localhost') +- `preserveSourceIP`, `deleteOnExit`, `protocol`, `enableLogging`, `ipv6Support` (booleans) +- `allowedSourceIPs`, `bannedSourceIPs` (string[]) +- `useIPSets` (boolean, default true) +- `qos`, `netProxyIntegration` (objects) -Example: -```js -import { NetworkProxy } from '@push.rocks/smartproxy'; +### Redirect / SslRedirect +- Constructor options: `httpPort`, `httpsPort`, `sslOptions`, `rules` (RedirectRule[]) -const proxy = new NetworkProxy({ - port: 8443, - backendProtocol: 'http2', - // other options... -}); -proxy.start(); -``` -- **Basic Authentication** - Support for basic auth on proxied routes -- **Connection Management** - Intelligent connection tracking and cleanup with configurable timeouts -- **Browser Compatibility** - Optimized for modern browsers with fixes for common TLS handshake issues - -## Installation - -```bash -npm install @push.rocks/smartproxy -``` - -## Usage - -### Basic Reverse Proxy Setup - -```typescript -import { NetworkProxy } from '@push.rocks/smartproxy'; - -// Create a reverse proxy listening on port 443 -const proxy = new NetworkProxy({ - port: 443 -}); - -// Define reverse proxy configurations -const proxyConfigs = [ - { - hostName: 'example.com', - destinationIps: ['127.0.0.1'], - destinationPorts: [3000], - publicKey: 'your-cert-content', - privateKey: 'your-key-content', - rewriteHostHeader: true - }, - { - hostName: 'api.example.com', - destinationIps: ['127.0.0.1'], - destinationPorts: [4000], - publicKey: 'your-cert-content', - privateKey: 'your-key-content', - // Optional basic auth - authentication: { - type: 'Basic', - user: 'admin', - pass: 'secret' - } - } -]; - -// Start the proxy and update configurations -(async () => { - await proxy.start(); - await proxy.updateProxyConfigs(proxyConfigs); - - // Add default headers to all responses - await proxy.addDefaultHeaders({ - 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload' - }); -})(); -``` - -### HTTP to HTTPS Redirection - -```typescript -import { SslRedirect } from '@push.rocks/smartproxy'; - -// Create and start HTTP to HTTPS redirect service on port 80 -const redirector = new SslRedirect(80); -redirector.start(); -``` - -### TCP Connection Handling with Domain-based Routing - -```typescript -import { SmartProxy } from '@push.rocks/smartproxy'; - -// Configure SmartProxy with domain-based routing -const smartProxy = new SmartProxy({ - fromPort: 443, - toPort: 8443, - targetIP: 'localhost', // Default target host - sniEnabled: true, // Enable SNI inspection - - // Enhanced reliability settings - initialDataTimeout: 60000, // 60 seconds for initial TLS handshake - socketTimeout: 3600000, // 1 hour socket timeout - maxConnectionLifetime: 3600000, // 1 hour connection lifetime - inactivityTimeout: 3600000, // 1 hour inactivity timeout - maxPendingDataSize: 10 * 1024 * 1024, // 10MB buffer for large TLS handshakes - - // Browser compatibility enhancement - enableTlsDebugLogging: false, // Enable for troubleshooting TLS issues - - // Port and IP configuration - globalPortRanges: [{ from: 443, to: 443 }], - defaultAllowedIPs: ['*'], // Allow all IPs by default - - // Socket optimizations for better connection stability - noDelay: true, // Disable Nagle's algorithm - keepAlive: true, // Enable TCP keepalive - enableKeepAliveProbes: true, // Enhanced keepalive for stability - - // Domain-specific routing configuration - domainConfigs: [ - { - domains: ['example.com', '*.example.com'], // Glob patterns for matching domains - allowedIPs: ['192.168.1.*'], // Restrict access by IP - blockedIPs: ['192.168.1.100'], // Block specific IPs - targetIPs: ['10.0.0.1', '10.0.0.2'], // Round-robin between multiple targets - portRanges: [{ from: 443, to: 443 }], - connectionTimeout: 7200000 // Domain-specific timeout (2 hours) - } - ], - - preserveSourceIP: true -}); - -smartProxy.start(); -``` - -### NfTables Port Forwarding - -```typescript -import { NfTablesProxy } from '@push.rocks/smartproxy'; - -// Basic usage - forward single port -const basicProxy = new NfTablesProxy({ - fromPort: 80, - toPort: 8080, - toHost: 'localhost', - preserveSourceIP: true, - deleteOnExit: true // Automatically clean up rules on process exit -}); - -// Forward port ranges -const rangeProxy = new NfTablesProxy({ - fromPort: { from: 3000, to: 3010 }, // Forward ports 3000-3010 - toPort: { from: 8000, to: 8010 }, // To ports 8000-8010 - protocol: 'tcp', // TCP protocol (default) - ipv6Support: true, // Enable IPv6 support - enableLogging: true // Enable detailed logging -}); - -// Multiple port specifications with IP filtering -const advancedProxy = new NfTablesProxy({ - fromPort: [80, 443, { from: 8000, to: 8010 }], // Multiple ports/ranges - toPort: [8080, 8443, { from: 18000, to: 18010 }], - allowedSourceIPs: ['10.0.0.0/8', '192.168.1.0/24'], // Only allow these IPs - bannedSourceIPs: ['192.168.1.100'], // Explicitly block these IPs - useIPSets: true, // Use IP sets for efficient IP management - forceCleanSlate: false // Clean all NfTablesProxy rules before starting -}); - -// Advanced features: QoS, connection tracking, and NetworkProxy integration -const advancedProxy = new NfTablesProxy({ - fromPort: 443, - toPort: 8443, - toHost: 'localhost', - useAdvancedNAT: true, // Use connection tracking for stateful NAT - qos: { - enabled: true, - maxRate: '10mbps', // Limit bandwidth - priority: 1 // Set traffic priority (1-10) - }, - netProxyIntegration: { - enabled: true, - redirectLocalhost: true, // Redirect localhost traffic to NetworkProxy - sslTerminationPort: 8443 // Port where NetworkProxy handles SSL - } -}); - -// Start any of the proxies -await basicProxy.start(); -``` - -### Automatic HTTPS Certificate Management - -```typescript -import { Port80Handler } from '@push.rocks/smartproxy'; - -// Create an ACME handler for Let's Encrypt -const acmeHandler = new Port80Handler({ - port: 80, - contactEmail: 'admin@example.com', - useProduction: true, // Use Let's Encrypt production servers (default is staging) - renewThresholdDays: 30, // Renew certificates 30 days before expiry - httpsRedirectPort: 443 // Redirect HTTP to HTTPS on this port -}); - -// Add domains to manage certificates for -acmeHandler.addDomain({ - domainName: 'example.com', - sslRedirect: true, - acmeMaintenance: true -}); - -acmeHandler.addDomain({ - domainName: 'api.example.com', - sslRedirect: true, - acmeMaintenance: true -}); - -// Support for glob pattern domains for routing (certificates not issued for glob patterns) -acmeHandler.addDomain({ - domainName: '*.example.com', - sslRedirect: true, - acmeMaintenance: false, // Can't issue certificates for wildcard domains via HTTP-01 - forward: { ip: '192.168.1.10', port: 8080 } // Forward requests to this target -}); -``` - -## Configuration Options - -### NetworkProxy Options - -| Option | Description | Default | -|----------------|---------------------------------------------------|---------| -| `port` | Port to listen on for HTTPS connections | - | -| `maxConnections` | Maximum concurrent connections | 10000 | -| `keepAliveTimeout` | Keep-alive timeout in milliseconds | 60000 | -| `headersTimeout` | Headers timeout in milliseconds | 60000 | -| `logLevel` | Logging level ('error', 'warn', 'info', 'debug') | 'info' | -| `cors` | CORS configuration object | - | -| `rewriteHostHeader` | Whether to rewrite the Host header | false | - -### SmartProxy Settings - -| Option | Description | Default | -|---------------------------|--------------------------------------------------------|-------------| -| `fromPort` | Port to listen on | - | -| `toPort` | Destination port to forward to | - | -| `targetIP` | Default destination IP if not specified in domainConfig | 'localhost' | -| `sniEnabled` | Enable SNI inspection for TLS connections | false | -| `defaultAllowedIPs` | IP patterns allowed by default | - | -| `defaultBlockedIPs` | IP patterns blocked by default | - | -| `preserveSourceIP` | Preserve the original client IP | false | -| `maxConnectionLifetime` | Maximum time in ms to keep a connection open | 3600000 | -| `initialDataTimeout` | Timeout for initial data/handshake in ms | 60000 | -| `socketTimeout` | Socket inactivity timeout in ms | 3600000 | -| `inactivityTimeout` | Connection inactivity check timeout in ms | 3600000 | -| `inactivityCheckInterval` | How often to check for inactive connections in ms | 60000 | -| `maxPendingDataSize` | Maximum bytes to buffer during connection setup | 10485760 | -| `globalPortRanges` | Array of port ranges to listen on | - | -| `forwardAllGlobalRanges` | Forward all global range connections to targetIP | false | -| `gracefulShutdownTimeout` | Time in ms to wait during shutdown | 30000 | -| `noDelay` | Disable Nagle's algorithm | true | -| `keepAlive` | Enable TCP keepalive | true | -| `keepAliveInitialDelay` | Initial delay before sending keepalive probes in ms | 30000 | -| `enableKeepAliveProbes` | Enable enhanced TCP keep-alive probes | false | -| `enableTlsDebugLogging` | Enable detailed TLS handshake debugging | false | -| `enableDetailedLogging` | Enable detailed connection logging | false | -| `enableRandomizedTimeouts`| Randomize timeouts slightly to prevent thundering herd | true | - -### NfTablesProxy Settings - -| Option | Description | Default | -|-----------------------|---------------------------------------------------|-------------| -| `fromPort` | Source port(s) or range(s) to forward from | - | -| `toPort` | Destination port(s) or range(s) to forward to | - | -| `toHost` | Destination host to forward to | 'localhost' | -| `preserveSourceIP` | Preserve the original client IP | false | -| `deleteOnExit` | Remove nftables rules when process exits | false | -| `protocol` | Protocol to forward ('tcp', 'udp', or 'all') | 'tcp' | -| `enableLogging` | Enable detailed logging | false | -| `logFormat` | Format for logs ('plain' or 'json') | 'plain' | -| `ipv6Support` | Enable IPv6 support | false | -| `allowedSourceIPs` | Array of IP addresses/CIDR allowed to connect | - | -| `bannedSourceIPs` | Array of IP addresses/CIDR blocked from connecting | - | -| `useIPSets` | Use nftables sets for efficient IP management | true | -| `forceCleanSlate` | Clear all NfTablesProxy rules before starting | false | -| `tableName` | Custom table name | 'portproxy' | -| `maxRetries` | Maximum number of retries for failed commands | 3 | -| `retryDelayMs` | Delay between retries in milliseconds | 1000 | -| `useAdvancedNAT` | Use connection tracking for stateful NAT | false | -| `qos` | Quality of Service options (object) | - | -| `netProxyIntegration` | NetworkProxy integration options (object) | - | - -## Advanced Features - -### TLS Handshake Optimization - -The enhanced `SmartProxy` implementation includes significant improvements for TLS handshake handling: - -- Robust SNI extraction with improved error handling -- Increased buffer size for complex TLS handshakes (10MB) -- Longer initial handshake timeout (60 seconds) -- Detection and tracking of TLS connection states -- Optional detailed TLS debug logging for troubleshooting -- Browser compatibility fixes for Chrome certificate errors - -```typescript -// Example configuration to solve Chrome certificate errors -const portProxy = new SmartProxy({ - // ... other settings - initialDataTimeout: 60000, // Give browser more time for handshake - maxPendingDataSize: 10 * 1024 * 1024, // Larger buffer for complex handshakes - enableTlsDebugLogging: true, // Enable when troubleshooting -}); -``` - -### Connection Management and Monitoring - -The `SmartProxy` class includes built-in connection tracking and monitoring: - -- Automatic cleanup of idle connections with configurable timeouts -- Timeouts for connections that exceed maximum lifetime -- Detailed logging of connection states -- Termination statistics -- Randomized timeouts to prevent "thundering herd" problems -- Per-domain timeout configuration - -### WebSocket Support - -The `NetworkProxy` class provides WebSocket support with: - -- WebSocket connection proxying -- Automatic heartbeat monitoring -- Connection cleanup for inactive WebSockets - -### SNI-based Routing - -The `SmartProxy` class can inspect the SNI (Server Name Indication) field in TLS handshakes to route connections based on the requested domain: - -- Multiple backend targets per domain -- Round-robin load balancing -- Domain-specific allowed IP ranges -- Protection against SNI renegotiation attacks - -### Enhanced NfTables Management - -The `NfTablesProxy` class offers advanced capabilities: - -- Support for multiple port ranges and individual ports -- More efficient IP filtering using nftables sets -- IPv6 support with full feature parity -- Quality of Service (QoS) features including bandwidth limiting and traffic prioritization -- Advanced connection tracking for stateful NAT -- Robust error handling with retry mechanisms -- Structured logging with JSON support -- NetworkProxy integration for SSL termination -- Comprehensive cleanup on shutdown - -### Port80Handler with Glob Pattern Support - -The `Port80Handler` class includes support for glob pattern domain matching: - -- Supports wildcard domains like `*.example.com` for HTTP request routing -- Detects glob patterns and skips certificate issuance for them -- Smart routing that first attempts exact matches, then tries pattern matching -- Supports forwarding HTTP requests to backend services -- Separate forwarding configuration for ACME challenges +### SmartProxy (IPortProxySettings) +- `fromPort`, `toPort` (number) +- `domainConfigs` (IDomainConfig[]) +- `sniEnabled`, `defaultAllowedIPs`, `preserveSourceIP` (booleans) +- Timeouts: `initialDataTimeout`, `socketTimeout`, `inactivityTimeout`, etc. +- Socket opts: `noDelay`, `keepAlive`, `enableKeepAliveProbes` +- `acme` (IAcmeOptions), `certProvider` (callback) +- `useNetworkProxy` (number[]), `networkProxyPort` (number) ## Troubleshooting -### Browser Certificate Errors +### NetworkProxy +- Verify ports, certificates and `rejectUnauthorized` for TLS errors +- Configure CORS or use `addDefaultHeaders` for preflight issues +- Increase `maxConnections` or `connectionPoolSize` under load -If you experience certificate errors in browsers, especially in Chrome, try these solutions: +### Port80Handler +- Run as root or grant CAP_NET_BIND_SERVICE for port 80 +- Inspect `certificate-failed` events and switch staging/production -1. **Increase Initial Data Timeout**: Set `initialDataTimeout` to 60 seconds or higher -2. **Increase Buffer Size**: Set `maxPendingDataSize` to 10MB or higher -3. **Enable TLS Debug Logging**: Set `enableTlsDebugLogging: true` to troubleshoot handshake issues -4. **Enable Keep-Alive Probes**: Set `enableKeepAliveProbes: true` for better connection stability -5. **Check Certificate Chain**: Ensure your certificate chain is complete and in the correct order +### NfTablesProxy +- Ensure `nft` is installed and run with sufficient privileges +- Use `forceCleanSlate:true` to clear conflicting rules -```typescript -// Configuration to fix Chrome certificate errors -const smartProxy = new SmartProxy({ - // ... other settings - initialDataTimeout: 60000, - maxPendingDataSize: 10 * 1024 * 1024, - enableTlsDebugLogging: true, - enableKeepAliveProbes: true -}); -``` +### Redirect / SslRedirect +- Check `fromHost`/`fromPath` patterns and Host headers +- Validate `sslOptions` key/cert correctness -### Connection Stability - -For improved connection stability in high-traffic environments: - -1. **Set Appropriate Timeouts**: Use longer timeouts for long-lived connections -2. **Use Domain-Specific Timeouts**: Configure per-domain timeouts for different types of services -3. **Enable TCP Keep-Alive**: Ensure `keepAlive` is set to `true` -4. **Monitor Connection Statistics**: Enable detailed logging to track termination reasons -5. **Fine-tune Inactivity Checks**: Adjust `inactivityCheckInterval` based on your traffic patterns - -### NfTables Troubleshooting - -If you're experiencing issues with NfTablesProxy: - -1. **Enable Detailed Logging**: Set `enableLogging: true` to see all rule operations -2. **Force Clean Slate**: Use `forceCleanSlate: true` to remove any lingering rules -3. **Use IP Sets**: Enable `useIPSets: true` for cleaner rule management -4. **Check Permissions**: Ensure your process has sufficient permissions to modify nftables -5. **Verify IPv6 Support**: If using `ipv6Support: true`, ensure ip6tables is available +### SmartProxy & SniHandler +- Increase `initialDataTimeout`/`maxPendingDataSize` for large ClientHello +- Enable `enableTlsDebugLogging` to trace handshake +- Ensure `allowSessionTicket` and fragmentation support for resumption ## License and Legal Information diff --git a/readme.plan.md b/readme.plan.md index 77d1d55..44a0f43 100644 --- a/readme.plan.md +++ b/readme.plan.md @@ -15,11 +15,11 @@ This document outlines a roadmap to simplify and refactor the SmartProxy & Netwo - [x] Consolidate ACME/Port80Handler logic: - [x] Merge standalone Port80Handler into a single certificate service - [x] Remove duplicate ACME setup in SmartProxy and NetworkProxy -- [ ] Unify configuration options: +- [x] Unify configuration options: - [x] Merge `INetworkProxyOptions.acme`, `IPort80HandlerOptions`, and `port80HandlerConfig` into one schema - - [ ] Deprecate old option names and provide clear upgrade path + - [x] Deprecate old option names and provide clear upgrade path - [ ] Centralize plugin imports in `ts/plugins.ts` and update all modules to use it -- [ ] Remove legacy or unused code paths (e.g., old HTTP/2 fallback logic if obsolete) +- [x] Remove legacy or unused code paths (e.g., old HTTP/2 fallback logic if obsolete) - [ ] Enhance and expand test coverage: - Add unit tests for certificate issuance, renewal, and error handling - Add integration tests for HTTP challenge routing and request forwarding diff --git a/test/test.ts b/test/test.networkproxy.ts similarity index 100% rename from test/test.ts rename to test/test.networkproxy.ts diff --git a/test/test.smartproxy.renewals.node.ts b/test/test.smartproxy.renewals.node.ts deleted file mode 100644 index 83cfbf0..0000000 --- a/test/test.smartproxy.renewals.node.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { tap, expect } from '@push.rocks/tapbundle'; -import { SmartProxy } from '../ts/smartproxy/classes.smartproxy.js'; - -tap.test('performRenewals only renews domains below threshold', async () => { - // Set up SmartProxy instance without real servers - const proxy = new SmartProxy({ - fromPort: 0, - toPort: 0, - domainConfigs: [], - sniEnabled: false, - defaultAllowedIPs: [], - globalPortRanges: [] - }); - // Stub port80Handler status and renewal - const statuses = new Map(); - const now = new Date(); - statuses.set('expiring.com', { - certObtained: true, - expiryDate: new Date(now.getTime() + 2 * 24 * 60 * 60 * 1000), - obtainingInProgress: false - }); - statuses.set('ok.com', { - certObtained: true, - expiryDate: new Date(now.getTime() + 100 * 24 * 60 * 60 * 1000), - obtainingInProgress: false - }); - const renewed: string[] = []; - // Inject fake handler - (proxy as any).port80Handler = { - getDomainCertificateStatus: () => statuses, - renewCertificate: async (domain: string) => { renewed.push(domain); } - }; - // Configure threshold - proxy.settings.port80HandlerConfig.enabled = true; - proxy.settings.port80HandlerConfig.autoRenew = true; - proxy.settings.port80HandlerConfig.renewThresholdDays = 10; - - // Execute renewals - await (proxy as any).performRenewals(); - - // Only the expiring.com domain should be renewed - expect(renewed).toEqual(['expiring.com']); -}); - -export default tap.start(); \ No newline at end of file diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 79ba656..cd2afe9 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartproxy', - version: '9.0.0', + version: '10.0.0', description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.' } diff --git a/ts/classes.router.ts b/ts/classes.router.ts index 74374c6..f4d14e9 100644 --- a/ts/classes.router.ts +++ b/ts/classes.router.ts @@ -1,6 +1,4 @@ -import * as http from 'http'; -import * as url from 'url'; -import * as tsclass from '@tsclass/tsclass'; +import * as plugins from './plugins.js'; /** * Optional path pattern configuration that can be added to proxy configs @@ -13,7 +11,7 @@ export interface IPathPatternConfig { * Interface for router result with additional metadata */ export interface IRouterResult { - config: tsclass.network.IReverseProxyConfig; + config: plugins.tsclass.network.IReverseProxyConfig; pathMatch?: string; pathParams?: Record; pathRemainder?: string; @@ -36,11 +34,11 @@ export interface IRouterResult { */ export class ProxyRouter { // Store original configs for reference - private reverseProxyConfigs: tsclass.network.IReverseProxyConfig[] = []; + private reverseProxyConfigs: plugins.tsclass.network.IReverseProxyConfig[] = []; // Default config to use when no match is found (optional) - private defaultConfig?: tsclass.network.IReverseProxyConfig; + private defaultConfig?: plugins.tsclass.network.IReverseProxyConfig; // Store path patterns separately since they're not in the original interface - private pathPatterns: Map = new Map(); + private pathPatterns: Map = new Map(); // Logger interface private logger: { error: (message: string, data?: any) => void; @@ -50,7 +48,7 @@ export class ProxyRouter { }; constructor( - configs?: tsclass.network.IReverseProxyConfig[], + configs?: plugins.tsclass.network.IReverseProxyConfig[], logger?: { error: (message: string, data?: any) => void; warn: (message: string, data?: any) => void; @@ -68,7 +66,7 @@ export class ProxyRouter { * Sets a new set of reverse configs to be routed to * @param reverseCandidatesArg Array of reverse proxy configurations */ - public setNewProxyConfigs(reverseCandidatesArg: tsclass.network.IReverseProxyConfig[]): void { + public setNewProxyConfigs(reverseCandidatesArg: plugins.tsclass.network.IReverseProxyConfig[]): void { this.reverseProxyConfigs = [...reverseCandidatesArg]; // Find default config if any (config with "*" as hostname) @@ -82,7 +80,7 @@ export class ProxyRouter { * @param req The incoming HTTP request * @returns The matching proxy config or undefined if no match found */ - public routeReq(req: http.IncomingMessage): tsclass.network.IReverseProxyConfig { + public routeReq(req: plugins.http.IncomingMessage): plugins.tsclass.network.IReverseProxyConfig { const result = this.routeReqWithDetails(req); return result ? result.config : undefined; } @@ -92,7 +90,7 @@ export class ProxyRouter { * @param req The incoming HTTP request * @returns Detailed routing result including matched config and path information */ - public routeReqWithDetails(req: http.IncomingMessage): IRouterResult | undefined { + public routeReqWithDetails(req: plugins.http.IncomingMessage): IRouterResult | undefined { // Extract and validate host header const originalHost = req.headers.host; if (!originalHost) { @@ -101,7 +99,7 @@ export class ProxyRouter { } // Parse URL for path matching - const parsedUrl = url.parse(req.url || '/'); + const parsedUrl = plugins.url.parse(req.url || '/'); const urlPath = parsedUrl.pathname || '/'; // Extract hostname without port @@ -351,7 +349,7 @@ export class ProxyRouter { * Gets all currently active proxy configurations * @returns Array of all active configurations */ - public getProxyConfigs(): tsclass.network.IReverseProxyConfig[] { + public getProxyConfigs(): plugins.tsclass.network.IReverseProxyConfig[] { return [...this.reverseProxyConfigs]; } @@ -375,7 +373,7 @@ export class ProxyRouter { * @param pathPattern Optional path pattern for route matching */ public addProxyConfig( - config: tsclass.network.IReverseProxyConfig, + config: plugins.tsclass.network.IReverseProxyConfig, pathPattern?: string ): void { this.reverseProxyConfigs.push(config); @@ -393,7 +391,7 @@ export class ProxyRouter { * @returns Boolean indicating if the config was found and updated */ public setPathPattern( - config: tsclass.network.IReverseProxyConfig, + config: plugins.tsclass.network.IReverseProxyConfig, pathPattern: string ): boolean { const exists = this.reverseProxyConfigs.includes(config); diff --git a/ts/smartproxy/classes.smartproxy.ts b/ts/smartproxy/classes.smartproxy.ts index 9e2964c..9f814df 100644 --- a/ts/smartproxy/classes.smartproxy.ts +++ b/ts/smartproxy/classes.smartproxy.ts @@ -422,27 +422,6 @@ export class SmartProxy extends plugins.EventEmitter { } - /** - * Perform scheduled renewals for managed domains - */ - private async performRenewals(): Promise { - if (!this.port80Handler) return; - const statuses = this.port80Handler.getDomainCertificateStatus(); - const threshold = this.settings.acme?.renewThresholdDays ?? 30; - const now = new Date(); - for (const [domain, status] of statuses.entries()) { - if (!status.certObtained || status.obtainingInProgress || !status.expiryDate) continue; - const msRemaining = status.expiryDate.getTime() - now.getTime(); - const daysRemaining = Math.ceil(msRemaining / (24 * 60 * 60 * 1000)); - if (daysRemaining <= threshold) { - try { - await this.port80Handler.renewCertificate(domain); - } catch (err) { - console.error(`Error renewing certificate for ${domain}:`, err); - } - } - } - } /** * Request a certificate for a specific domain */