Files
smartproxy/readme.byte-counting-audit.md
2025-06-23 09:35:37 +00:00

5.7 KiB

SmartProxy Byte Counting Audit Report

Executive Summary

After a comprehensive audit of the SmartProxy codebase, I can confirm that byte counting is implemented correctly with no instances of double counting. Each byte transferred through the proxy is counted exactly once in each direction.

Byte Counting Implementation

1. Core Tracking Mechanisms

SmartProxy uses two complementary tracking systems:

  1. Connection Records (IConnectionRecord):

    • bytesReceived: Total bytes received from client
    • bytesSent: Total bytes sent to client
  2. MetricsCollector:

    • Global throughput tracking via ThroughputTracker
    • Per-connection byte tracking for route/IP metrics
    • Called via recordBytes(connectionId, bytesIn, bytesOut)

2. Where Bytes Are Counted

Bytes are counted in only two files:

a) route-connection-handler.ts

  • Line 351: TLS alert bytes when no SNI is provided
  • Lines 1286-1301: Data forwarding callbacks in setupBidirectionalForwarding()

b) http-proxy-bridge.ts

  • Line 127: Initial TLS chunk for HttpProxy connections
  • Lines 142-154: Data forwarding callbacks in setupBidirectionalForwarding()

Connection Flow Analysis

1. Direct TCP Connection (No TLS)

Client → SmartProxy → Target Server
  1. Connection arrives at RouteConnectionHandler.handleConnection()
  2. For non-TLS ports, immediately routes via routeConnection()
  3. setupDirectConnection() creates target connection
  4. setupBidirectionalForwarding() handles all data transfer:
    • onClientData: bytesReceived += chunk.length + recordBytes(chunk.length, 0)
    • onServerData: bytesSent += chunk.length + recordBytes(0, chunk.length)

Result: Each byte counted exactly once

2. TLS Passthrough Connection

Client (TLS) → SmartProxy → Target Server (TLS)
  1. Connection waits for initial data to detect TLS
  2. TLS handshake detected, SNI extracted
  3. Route matched, setupDirectConnection() called
  4. Initial chunk stored in pendingData (NOT counted yet)
  5. On target connect, pendingData written to target (still not counted)
  6. setupBidirectionalForwarding() counts ALL bytes including initial chunk

Result: Each byte counted exactly once

3. TLS Termination via HttpProxy

Client (TLS) → SmartProxy → HttpProxy (localhost) → Target Server
  1. TLS connection detected with tls.mode = "terminate"
  2. forwardToHttpProxy() called:
    • Initial chunk: bytesReceived += chunk.length + recordBytes(chunk.length, 0)
  3. Proxy connection created to HttpProxy on localhost
  4. setupBidirectionalForwarding() handles subsequent data

Result: Each byte counted exactly once

4. HTTP Connection via HttpProxy

Client (HTTP) → SmartProxy → HttpProxy (localhost) → Target Server
  1. Connection on configured HTTP port (useHttpProxy ports)
  2. Same flow as TLS termination
  3. All byte counting identical to TLS termination

Result: Each byte counted exactly once

5. NFTables Forwarding

Client → [Kernel NFTables] → Target Server
  1. Connection detected, route matched with forwardingEngine: 'nftables'
  2. Connection marked as usingNetworkProxy = true
  3. NO application-level forwarding (kernel handles packet routing)
  4. NO byte counting in application layer

Result: No counting (correct - kernel handles everything)

Special Cases

PROXY Protocol

  • PROXY protocol headers sent to backend servers are NOT counted in client metrics
  • Only actual client data is counted
  • Correct behavior: Protocol overhead is not client data

TLS Alerts

  • TLS alerts (e.g., for missing SNI) are counted as sent bytes
  • Correct behavior: Alerts are actual data sent to the client

Initial Chunks

  • Direct connections: Stored in pendingData, counted when forwarded
  • HttpProxy connections: Counted immediately upon receipt
  • Both approaches: Count each byte exactly once

Verification Methodology

  1. Code Analysis: Searched for all instances of:

    • bytesReceived += and bytesSent +=
    • recordBytes() calls
    • Data forwarding implementations
  2. Flow Tracing: Followed data path for each connection type from entry to exit

  3. Handler Review: Examined all forwarding handlers to ensure no additional counting

Findings

No Double Counting Detected

  • Each byte is counted exactly once in the direction it flows
  • Connection records and metrics are updated consistently
  • No overlapping or duplicate counting logic found

Areas of Excellence

  1. Centralized Counting: All byte counting happens in just two files
  2. Consistent Pattern: Uses setupBidirectionalForwarding() with callbacks
  3. Clear Separation: Forwarding handlers don't interfere with proxy metrics

Recommendations

  1. Debug Logging: Add optional debug logging to verify byte counts in production:

    if (settings.debugByteCount) {
      logger.log('debug', `Bytes counted: ${connectionId} +${bytes} (total: ${record.bytesReceived})`);
    }
    
  2. Unit Tests: Create specific tests to ensure byte counting accuracy:

    • Test initial chunk handling
    • Test PROXY protocol overhead exclusion
    • Test HttpProxy forwarding accuracy
  3. Protocol Overhead Tracking: Consider separately tracking:

    • PROXY protocol headers
    • TLS handshake bytes
    • HTTP headers vs body
  4. NFTables Documentation: Clearly document that NFTables-forwarded connections are not included in application metrics

Conclusion

SmartProxy's byte counting implementation is robust and accurate. The design ensures that each byte is counted exactly once, with clear separation between connection tracking and metrics collection. No remediation is required.