Feature/review pgsql native backend protocol
Summary
Fixes defects in the PostgreSQL native backend protocol path.
Backend TLS was effectively unusable on the native path before this branch: a
pooled TLS connection lost its encryption on handoff, and SCRAM-SHA-256-PLUS
(which PostgreSQL offers by default with ssl=on) could never complete a
handshake. A failed backend authentication could abort the whole process.
Fixes
Pooled native TLS connection lost its encryption — Critical
The native path stored its SSL and both BIOs on PgSQL_Data_Stream, which
belongs to the session and is destroyed when the session releases the backend.
A PgSQL_Connection is pooled and outlives any one session, so pooling a TLS
connection destroyed its TLS context while the socket stayed open and still
encrypted. The next session attached it to a fresh data stream with no SSL,
read TLS records as plaintext, and reported "backend closed during result
fetch". The TLS session now lives on the connection, giving it the socket's
lifetime.
SCRAM-SHA-256-PLUS over a TLS backend always failed — Critical
The native path selects -PLUS whenever the backend advertises it over TLS,
but could never complete the exchange. Three defects each masking the next,
starting with pg_scram_client_first() returning nullptr for any
channel-binding request.
Backend auth failure poisoned the connection and aborted the process
A connection that died during authentication still reported its initial
transaction status, so the pool judged it reusable and re-pooled the dead
object. The next session to pick it up aborted the process on assert(0)
The reusability check now considers the socket state.
Native framer never reclaimed consumed bytes
The buffer was only rewound when a socket read happened to end exactly on
message boundary. Otherwise already-parsed bytes stayed in place and the
read was appended after them, so the buffer grew for the whole result set
with the outcome depending on whether message size shared a factor with t
16 KB read size.
Named-portal cleanup used a stale connection pointer
The query error path used a connection pointer captured before error hand
ran, by which point the connection may already have been returned to the
or handed to another session. Cleanup then queried a connection it no lon
owned and tripped an assertion.
Observability
backend_pid and using_ssl are now reported for native connections in
stats_pgsql_free_connections. Both were previously libpq-only, so a poo
native connection could not be correlated with pg_stat_activity or
pg_stat_ssl on the server — the only way to check its real transport fr
outside ProxySQL.
Summary by cubic
Hardens the native PostgreSQL backend path: pooled TLS connections and native resets previously lost transport state or reported success without changing the backend; they now preserve TLS and execute the required reset commands. Invalid or incomplete backend traffic now fails the affected connection instead of hanging, aborting ProxySQL, or returning it to the pool.
Protocol and pooling
- Adds SCRAM-SHA-256(-PLUS) channel binding, SCRAM verifier and stored-MD5 pass-through, and cleartext and trust authentication, without forwarding plaintext backend passwords.
- Keeps a login bound to the credential selected when its handshake starts, even if the stored secret changes mid-login.
- Carries
client_encoding,options,application_name, and tracked variables through startup, restores them afterROLLBACKandDISCARD ALL, and reports a trackedSETto the client over the extended protocol. - Resynchronizes extended-query batches, rejects unsafe mid-batch resets, forwards SQL-level
DEALLOCATEandDEALLOCATE ALL, pins the query end state so a prepared statement can no longer stall a laterSET, and removes the unsafe statement-levelDescribecache. - Stops the proxy from aborting when a multi-statement query opens a transaction the tracker missed, and from looping when a backend answers a
SETwith a result set. - Bounds and reclaims native read buffers, preserves complete replies received with EOF, and polls for the direction OpenSSL actually needs.
- Drops connections that cannot resume mid-Copy or mid-TLS-record, keeps a native connection whose own BIO still holds ciphertext, retries dead connections only when no transaction was open, keeps the connected-backend count accurate on killed backends, and clears named portals and transaction state through the live connection.
- Answers unsupported startup protocol versions and GSS encryption requests the way PostgreSQL does, and reports native backend PID, TLS state, server version, and client encoding without calling libpq.
- Adds differential, adversarial, integration, concurrency, TLS, authentication, pooling, framing, and unit coverage.
Migration
- Backend pass-through requires
pgsql_users.passwordto exactly match each backend'spg_authid.rolpassword, including the salt. - The
pgsql-authentication_methodfloor applies to stored secrets; update MD5 secrets or lower the floor as needed. - Native backend connections fail closed when ProxySQL cannot build credentials instead of using
PGPASSWORDor~/.pgpass;backend_pidandusing_sslare now reported for native connections instats_pgsql_free_connections.
Summary by CodeRabbit
-
New Features
- Added PostgreSQL SCRAM-SHA-256-PLUS channel-binding support.
- Added authentication using SCRAM keys and stored MD5 credentials.
- Added native PostgreSQL TLS connection support and expanded connection statistics, including process IDs and SSL status.
- Native connections now preserve key startup settings, including client encoding, options, and application name.
-
Bug Fixes
- Improved handling of fragmented reads and large results.
- Strengthened authentication validation and failure responses.
- Improved recovery from interrupted, malformed, or unhealthy connections.
- Prevented unhealthy connections from being reused in the pool.