Skip to content

Multi protocol proxy#2

Merged
abnegate merged 83 commits intomainfrom
dev
Mar 26, 2026
Merged

Multi protocol proxy#2
abnegate merged 83 commits intomainfrom
dev

Conversation

@abnegate
Copy link
Member

@abnegate abnegate commented Mar 12, 2026

Summary by CodeRabbit

  • New Features

    • Multi-service Docker setup and runnable proxy examples; Resolver-based dynamic backend routing; read/write split for DB proxies with transaction pinning; TLS/mTLS termination for TCP.
  • Documentation

    • Major README overhaul with Resolver/benchmark guidance; new benchmarks suite and runner docs; removed legacy PERFORMANCE.md.
  • Tests

    • Extensive unit, integration, and performance test suites added.
  • Chores

    • CI workflows (lint, static analysis, tests); environment example; PHP bumped to 8.4 and Swoole to 6.0.

abnegate and others added 30 commits January 9, 2026 16:15
Remove invalid trailing comma in require section that was causing JSON parse errors.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Increase recv buffer from 64KB to 128KB (configurable via recv_buffer_size)
  Larger buffers = fewer syscalls = better throughput

- Add backend socket optimizations:
  - open_tcp_nodelay: Disable Nagle's algorithm for lower latency
  - socket_buffer_size: 2MB buffer for backend connections
  - Configurable connect timeout (default 5s, was hardcoded 30s)

- Add new config options:
  - recv_buffer_size: Control forwarding buffer size
  - backend_connect_timeout: Control backend connection timeout

- Add setConnectTimeout() method to TCP adapter

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Optimizes system for high-throughput TCP proxy testing:
- File descriptor limits (2M)
- TCP backlog (65535)
- Socket buffers (128MB max)
- TCP Fast Open, tw_reuse, window scaling
- Local port range (1024-65535)
- CPU governor (performance mode)

Usage:
  sudo ./benchmarks/setup-linux.sh           # temporary
  sudo ./benchmarks/setup-linux.sh --persist # permanent

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Conservative settings for production database proxies:
- Keeps tcp_slow_start_after_idle=1 (default) to prevent bursts
- Keeps tcp_no_metrics_save=0 (default) for cached route metrics
- Uses tcp_fin_timeout=30 instead of aggressive 10
- Adds tcp_keepalive tuning to detect dead connections
- Lower limits than benchmark script (still 1M connections)

Use setup-linux.sh for benchmarks, setup-linux-production.sh for prod.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Single command to setup fresh Ubuntu/Debian droplet and run benchmarks:
- Installs PHP 8.3 + Swoole
- Installs Composer
- Clones repo
- Applies kernel tuning
- Runs connection rate + throughput benchmarks

Usage:
  curl -sL https://raw.githubusercontent.com/utopia-php/protocol-proxy/dev/benchmarks/bootstrap-droplet.sh | sudo bash

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
New benchmark modes:
- Sustained load: continuous requests for N seconds, monitors memory/latency/errors
- Max connections: opens and holds N concurrent connections

Usage:
  # 60 second sustained load test
  BENCH_DURATION=60 BENCH_CONCURRENCY=1000 php benchmarks/tcp-sustained.php

  # 5 minute soak test
  BENCH_DURATION=300 BENCH_CONCURRENCY=2000 php benchmarks/tcp-sustained.php

  # Max connections test (hold 50k connections)
  BENCH_MODE=max_connections BENCH_TARGET_CONNECTIONS=50000 php benchmarks/tcp-sustained.php

Output includes: conn/s, req/s, error rate, active connections, throughput, latency, memory

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updated bootstrap-droplet.sh targets:
- Burst: 1M connections (was 400k)
- Throughput: 16GB (was 8GB)
- Sustained: 4000 concurrency for ~100k conn/s (was 1000)
- Max connections test: 100k (was 50k)

Added note: these are per-pod numbers, scale linearly with more pods.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@abnegate abnegate marked this pull request as ready for review March 25, 2026 05:05
@greptile-apps
Copy link

greptile-apps bot commented Mar 25, 2026

Greptile Summary

This PR is a comprehensive refactor and major feature expansion of the proxy library, renaming it from appwrite/protocol-proxy to utopia-php/proxy, bumping the minimum requirements to PHP 8.4 / Swoole 6.0, and adding multi-protocol support (HTTP, TCP, SMTP) with TLS/mTLS termination, a resolver-based routing architecture, connection pooling, byte tracking, and an extensive test suite.

The architecture is well thought out — the Resolver interface, Adapter base class, and protocol-specific servers are cleanly separated. However, several functional correctness issues were found:

  • SMTP server drops all SMTP commands silentlypackage_eof is set without the required open_eof_check: true, so Swoole never splits data by \r\n. SMTP commands will be batched or split arbitrarily in onReceive, breaking the entire SMTP proxy.
  • HTTP forwardRawRequest drops all backend response headersContent-Type, Location, Cache-Control, cookies, etc. are never forwarded to the proxied client.
  • HTTP forwardRawRequest returns truncated bodies for chunked responses — only the bytes already in the receive buffer are returned; the remaining chunks are never fetched.
  • TCP adapter routing collisions for long first-packets — raw packet data is used as the Swoole Table routing cache key; keys over 64 bytes are silently truncated, which can cause distinct connections to resolve to the same cached backend.
  • Uninitialized typed property $adaptergetStats() on the HTTP and SMTP Swoole servers will fatal before any worker has started (e.g. in tests or monitoring endpoints called from the master process).
  • Routing cache TTL is ≤ 1 second — the cache is invalidated every second, making it nearly useless for reducing resolver round-trips under real workloads.

Confidence Score: 2/5

  • Not safe to merge — the SMTP server is broken by a missing Swoole config option, the HTTP raw forwarder silently drops response headers, and chunked responses are truncated.
  • Multiple P1 correctness bugs affect all three supported protocols: the SMTP proxy will misparse every command due to the missing open_eof_check; the HTTP raw path drops all response headers and truncates chunked bodies; the TCP routing cache can collide for real database clients. These are not edge cases — they affect core forwarding functionality on the default code paths.
  • src/Server/SMTP/Swoole.php (open_eof_check missing), src/Server/HTTP/Swoole.php and src/Server/HTTP/SwooleCoroutine.php (forwardRawRequest header/chunked issues), src/Adapter/TCP.php (routing key truncation)

Important Files Changed

Filename Overview
src/Adapter.php New base adapter class with SSRF validation, Swoole Table routing cache, and byte tracking. Main concern: routing cache TTL is < 1 second (effectively per-second), making caching nearly ineffective under real workloads.
src/Adapter/TCP.php TCP adapter routing using raw packet data as the Swoole Table cache key — keys exceeding 64 bytes are silently truncated, causing routing cache collisions between distinct connections with longer startup packets (e.g. PostgreSQL).
src/Server/HTTP/Swoole.php HTTP proxy server with two issues in forwardRawRequest: (1) backend response headers are never forwarded to the client, (2) chunked responses return only the buffered partial body. Additionally, getStats() will fatal on uninitialized $adapter property before onWorkerStart is called.
src/Server/HTTP/SwooleCoroutine.php Coroutine HTTP proxy server — similar forwardRawRequest path exists here and carries the same header-forwarding and chunked-response issues as the non-coroutine variant.
src/Server/SMTP/Swoole.php SMTP proxy server with a critical misconfiguration: package_eof is set but open_eof_check is never enabled, so Swoole does not split incoming data by CRLF. getStats() also accesses the uninitialized $adapter property before onWorkerStart.
src/Server/TCP/Swoole.php Event-driven TCP proxy server with TLS termination support. Handles PostgreSQL STARTTLS and MySQL SSL correctly. Bidirectional forwarding uses exportSocket() which is idiomatic for Swoole. Looks sound.
src/Server/TCP/TLS.php TLS configuration with PostgreSQL/MySQL detection helpers. Cipher suite defaults are strong and modern; minimum TLS 1.2 is enforced. Validate() correctly checks file readability and CA requirement for mTLS.
src/Resolver.php Clean interface definition with all expected lifecycle hooks (resolve, track, purge, onConnect, onDisconnect, getStats). No issues.

Comments Outside Diff (1)

  1. src/Server/HTTP/Swoole.php, line 541-552 (link)

    P1 Uninitialized typed property $adapter accessed in getStats()

    protected Adapter $adapter; is a typed property with no default value. It is only assigned inside onWorkerStart(). In the multi-worker Swoole process model the master/manager process never calls onWorkerStart, so getStats() called from the master process — or from a test before the server has started — will throw a fatal error:

    Typed property Swoole::$adapter must not be accessed before initialization

    Declare a nullable default or guard the access:

    The same issue exists in src/Server/SMTP/Swoole.php$this->adapter->getStats() in getStats() will also fail before onWorkerStart has been called.

Reviews (1): Last reviewed commit: "(refactor): Use \go instead of Coroutine..." | Re-trigger Greptile

abnegate and others added 11 commits March 25, 2026 18:23
…le logging, SMTP constants, TLSContext rename

- Block IPv6-mapped IPv4 addresses (::ffff:) in SSRF validation
- Check send() return values in TCP fast path, forward goroutine, and SMTP forwarding
- Fix goroutine resource leak in TCP SwooleCoroutine error path
- Replace all echo/error_log with Utopia\Console (log, success, info, error)
- Extract SMTP magic numbers into class constants (RECV_BUFFER, PACKAGE_MAX_LENGTH, etc.)
- Rename TlsContext → TLSContext (acronym casing convention)
- Rename getTlsContext → getTLSContext in Config and all callers
- Add workerStart callback support to HTTP SwooleCoroutine
- Normalize timeout types to float across all Config classes
- Add utopia-php/console dependency, remove unused utopia-php/cli

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…h validation

- Replace custom isValidHostname() regex with Utopia\Validator\Hostname in both HTTP servers
- Use Utopia\Validator\Range for port validation in Adapter::validate()
- Use Utopia\Validator\IP for IP format validation in Adapter::validate()
- Use Utopia\Validator\Text for TLS certificate path validation
- Add utopia-php/validators dependency

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create Handler trait with onRequest, forwardRequest, forwardRawRequest,
  addTelemetryHeaders, and isValidHostname
- Both Swoole and SwooleCoroutine HTTP servers now use the trait
- Eliminates ~300 lines of duplicated request-handling logic
- Bug fixes in one location now apply to both server implementations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…s name instead

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Allow utopia-php/console 0.0.* to resolve alongside open-runtimes/executor 0.22.x

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@abnegate abnegate merged commit 5935ac8 into main Mar 26, 2026
4 checks passed
@abnegate abnegate deleted the dev branch March 26, 2026 10:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant