smartproxy/readme.hints.md

7.9 KiB
Raw Blame History

SmartProxy Project Hints

Project Overview

  • Package: @push.rocks/smartproxy high-performance proxy supporting HTTP(S), TCP, WebSocket, and ACME integration.
  • Written in TypeScript, compiled output in dist_ts/, uses ESM with NodeNext resolution.

Important: ACME Configuration in v19.0.0

  • Breaking Change: ACME configuration must be placed within individual route TLS settings, not at the top level
  • Route-level ACME config is the ONLY way to enable SmartAcme initialization
  • SmartCertManager requires email in route config for certificate acquisition
  • Top-level ACME configuration is ignored in v19.0.0

Repository Structure

  • ts/ TypeScript source files:
    • index.ts exports main modules.
    • plugins.ts centralizes native and third-party imports.
    • Subdirectories: networkproxy/, nftablesproxy/, port80handler/, redirect/, smartproxy/.
    • Key classes: ProxyRouter (classes.router.ts), SmartProxy (classes.smartproxy.ts), plus handlers/managers.
  • dist_ts/ transpiled .js and .d.ts files mirroring ts/ structure.
  • test/ test suites in TypeScript:
    • test.router.ts routing logic (hostname matching, wildcards, path parameters, config management).
    • test.smartproxy.ts proxy behavior tests (TCP forwarding, SNI handling, concurrency, chaining, timeouts).
    • test/helpers/ utilities (e.g., certificates).
  • assets/certs/ placeholder certificates for ACME and TLS.

Development Setup

  • Requires pnpm (v10+).
  • Install dependencies: pnpm install.
  • Build: pnpm build (runs tsbuild --web --allowimplicitany).
  • Test: pnpm test (runs tstest test/).
  • Format: pnpm format (runs gitzone format).

Testing Framework

  • Uses @push.rocks/tapbundle (tap, expect, expactAsync).
  • Test files: must start with test. and use .ts extension.
  • Run specific tests via tsx, e.g., tsx test/test.router.ts.

Coding Conventions

  • Import modules via plugins.ts:
    import * as plugins from './plugins.ts';
    const server = new plugins.http.Server();
    
  • Reference plugins with full path: plugins.acme, plugins.smartdelay, plugins.minimatch, etc.
  • Path patterns support globs (*) and parameters (:param) in ProxyRouter.
  • Wildcard hostname matching leverages minimatch patterns.

Key Components

  • ProxyRouter
    • Methods: routeReq, routeReqWithDetails.
    • Hostname matching: case-insensitive, strips port, supports exact, wildcard, TLD, complex patterns.
    • Path routing: exact, wildcard, parameter extraction (pathParams), returns pathMatch and pathRemainder.
    • Config API: setNewProxyConfigs, addProxyConfig, removeProxyConfig, getHostnames, getProxyConfigs.
  • SmartProxy
    • Manages one or more net.Server instances to forward TCP streams.
    • Options: preserveSourceIP, defaultAllowedIPs, globalPortRanges, sniEnabled.
    • DomainConfigManager: round-robin selection for multiple target IPs.
    • Graceful shutdown in stop(), ensures no lingering servers or sockets.

Notable Points

  • TSConfig: module: NodeNext, verbatimModuleSyntax, allows .js extension imports in TS.
  • Mermaid diagrams and architecture flows in readme.md illustrate component interactions and protocol flows.
  • CLI entrypoint (cli.js) supports command-line usage (ACME, proxy controls).
  • ACME and certificate handling via Port80Handler and helpers.certificates.ts.

ACME/Certificate Configuration Example (v19.0.0)

const proxy = new SmartProxy({
  routes: [{
    name: 'example.com',
    match: { domains: 'example.com', ports: 443 },
    action: {
      type: 'forward',
      target: { host: 'localhost', port: 8080 },
      tls: {
        mode: 'terminate',
        certificate: 'auto',
        acme: {  // ACME config MUST be here, not at top level
          email: 'ssl@example.com',
          useProduction: false,
          challengePort: 80
        }
      }
    }
  }]
});

TODOs / Considerations

  • Ensure import extensions in source match build outputs (.ts vs .js).
  • Update plugins.ts when adding new dependencies.
  • Maintain test coverage for new routing or proxy features.
  • Keep ts/ and dist_ts/ in sync after refactors.
  • Consider implementing top-level ACME config support for backward compatibility

HTTP-01 ACME Challenge Fix (v19.3.8)

Issue

Non-TLS connections on ports configured in useHttpProxy were not being forwarded to HttpProxy. This caused ACME HTTP-01 challenges to fail when the ACME port (usually 80) was included in useHttpProxy.

Root Cause

In the RouteConnectionHandler.handleForwardAction method, only connections with TLS settings (mode: 'terminate' or 'terminate-and-reencrypt') were being forwarded to HttpProxy. Non-TLS connections were always handled as direct connections, even when the port was configured for HttpProxy.

Solution

Added a check for non-TLS connections on ports listed in useHttpProxy:

// No TLS settings - check if this port should use HttpProxy
const isHttpProxyPort = this.settings.useHttpProxy?.includes(record.localPort);

if (isHttpProxyPort && this.httpProxyBridge.getHttpProxy()) {
  // Forward non-TLS connections to HttpProxy if configured
  this.httpProxyBridge.forwardToHttpProxy(/*...*/);
  return;
}

Test Coverage

  • test/test.http-fix-unit.ts - Unit tests verifying the fix
  • Tests confirm that non-TLS connections on HttpProxy ports are properly forwarded
  • Tests verify that non-HttpProxy ports still use direct connections

Configuration Example

const proxy = new SmartProxy({
  useHttpProxy: [80], // Enable HttpProxy for port 80
  httpProxyPort: 8443,
  acme: {
    email: 'ssl@example.com',
    port: 80
  },
  routes: [
    // Your routes here
  ]
});

ACME Certificate Provisioning Timing Fix (v19.3.9)

Issue

Certificate provisioning would start before ports were listening, causing ACME HTTP-01 challenges to fail with connection refused errors.

Root Cause

SmartProxy initialization sequence:

  1. Certificate manager initialized → immediately starts provisioning
  2. Ports start listening (too late for ACME challenges)

Solution

Deferred certificate provisioning until after ports are ready:

// SmartCertManager.initialize() now skips automatic provisioning
// SmartProxy.start() calls provisionAllCertificates() directly after ports are listening

Test Coverage

  • test/test.acme-timing-simple.ts - Verifies proper timing sequence

Migration

Update to v19.3.9+, no configuration changes needed.

Socket Handler Race Condition Fix (v19.5.0)

Issue

Initial data chunks were being emitted before async socket handlers had completed setup, causing data loss when handlers performed async operations before setting up data listeners.

Root Cause

The handleSocketHandlerAction method was using process.nextTick to emit initial chunks regardless of whether the handler was sync or async. This created a race condition where async handlers might not have their listeners ready when the initial data was emitted.

Solution

Differentiated between sync and async handlers:

const result = route.action.socketHandler(socket);

if (result instanceof Promise) {
  // Async handler - wait for completion before emitting initial data
  result.then(() => {
    if (initialChunk && initialChunk.length > 0) {
      socket.emit('data', initialChunk);
    }
  }).catch(/*...*/);
} else {
  // Sync handler - use process.nextTick as before
  if (initialChunk && initialChunk.length > 0) {
    process.nextTick(() => {
      socket.emit('data', initialChunk);
    });
  }
}

Test Coverage

  • test/test.socket-handler-race.ts - Specifically tests async handlers with delayed listener setup
  • Verifies that initial data is received even when handler sets up listeners after async work

Usage Note

Socket handlers require initial data from the client to trigger routing (not just a TLS handshake). Clients must send at least one byte of data for the handler to be invoked.