105 lines
5.2 KiB
Markdown
105 lines
5.2 KiB
Markdown
# RustProxy 0.26.1 implementation note
|
|
|
|
## Scope
|
|
|
|
Version 0.26.1 is a focused follow-up to the persistent protocol diagnostics and Stored Logs explorer introduced in 0.26.0. It does not change SQLite schema 8 or the saved proxy configuration format.
|
|
|
|
The release has two goals:
|
|
|
|
1. Attribute persistent `protocol_error` records to a configured vhost when that attribution is safe and deterministic.
|
|
2. Make the Stored Logs explorer practical on installations with many vhosts and useful for protocol diagnostics.
|
|
|
|
Live Logs remains a separate bounded in-memory request view. Low-level `protocol_error` records are persistence-only and must not be published to its history/broadcast path.
|
|
|
|
## One shared vhost-resolution function
|
|
|
|
All protocol adapters use the same resolver; TLS, HTTP/1, HTTP/2 and HTTP/3 must not implement independent hostname matching.
|
|
|
|
```rust
|
|
resolve_logging_vhost(
|
|
local,
|
|
tls,
|
|
routed_vhost,
|
|
authority,
|
|
host,
|
|
sni,
|
|
) -> Option<ResolvedLogVhost>
|
|
```
|
|
|
|
Resolution priority is:
|
|
|
|
1. a vhost already selected by normal routing;
|
|
2. HTTP `:authority`;
|
|
3. HTTP `Host`;
|
|
4. TLS/QUIC SNI;
|
|
5. no match, which means `_unmatched.jsonl` in per-vhost file mode.
|
|
|
|
The helper normalizes authority/Host/SNI with the existing host parser and delegates selection to the same `Snapshot::frontend` exact/wildcard and listener-binding logic used by normal request routing. It returns the configured internal vhost ID and `matched_by`; client input is never turned directly into a filename.
|
|
|
|
## Persistent protocol records
|
|
|
|
A protocol record keeps the compatibility fields from 0.26.0 and also exposes canonical diagnostics when available:
|
|
|
|
- `event = protocol_error`;
|
|
- `reason` and compatibility `protocol_error`;
|
|
- `error` and compatibility `error_detail`;
|
|
- `client_ip` and `local_addr`;
|
|
- `transport` and `http_version`;
|
|
- `vhost` when safely resolved;
|
|
- `sni` and compatibility `tls_sni`;
|
|
- `authority`;
|
|
- `matched_by = router | authority | host | sni | none`.
|
|
|
|
Diagnostic identity strings are control-character stripped and length-bounded before persistence. Existing secret redaction and error-detail limits remain in force.
|
|
|
|
In per-vhost file mode, a resolved record uses the existing configured vhost sink. Invalid internal vhost identifiers fall back to `_unmatched` instead of being sanitized into a new filename. In combined-file mode, sink behavior is unchanged.
|
|
|
|
## Protocol integration
|
|
|
|
TLS reads SNI from ClientHello before completing the handshake. A later handshake failure/timeout carries that SNI into the shared resolver. If ClientHello itself cannot be parsed, attribution remains unavailable and the record is unmatched.
|
|
|
|
HTTP/2 retains the TLS SNI for post-handshake connection/drain diagnostics. Request paths that have an HTTP request object can additionally provide authority/Host.
|
|
|
|
HTTP/3 obtains SNI from Quinn/Rustls handshake data after a successful QUIC handshake and uses it for later request, stream and connection protocol failures. Failures that happen before usable handshake identity exists remain unmatched.
|
|
|
|
No protocol path infers a vhost solely from a shared listener or local port.
|
|
|
|
## Stored Logs explorer changes
|
|
|
|
The `/logs` workspace now:
|
|
|
|
- highlights the selected log row;
|
|
- expands Record details inline directly below that row;
|
|
- removes the separate details panel below the table;
|
|
- keeps the table header sticky while the log list scrolls;
|
|
- lets each administrator choose visible Stored Logs columns and persists the choice in existing account preferences;
|
|
- offers protocol columns for transport, match source, SNI and authority;
|
|
- keeps the configured-file inventory collapsed by default;
|
|
- canonicalizes fields through the active structured-log field mapping so `client_ip` remains visible when the JSON output key was renamed;
|
|
- searches record values case-insensitively and includes `reason`, `error`, `sni`, `authority` and other stored values;
|
|
- refreshes keyword results with a short debounce while typing;
|
|
- retains the existing bounded source/byte/line/page limits and reports truncation when an `all` query contains more sources than can be scanned safely.
|
|
|
|
## Security invariants
|
|
|
|
The browser still accepts only opaque configured source IDs; it never accepts arbitrary filesystem paths. File reads remain regular-file-only and retain Unix `O_NOFOLLOW` protection.
|
|
|
|
SNI, Host and authority are lookup inputs only. They cannot select a filesystem path directly. Only a configured vhost resolved through the existing routing model can select a per-vhost log sink.
|
|
|
|
## Regression coverage
|
|
|
|
Source tests cover:
|
|
|
|
- exact and wildcard hostname precedence using the shared resolver;
|
|
- router priority over authority/Host/SNI;
|
|
- Host with port and case-insensitive normalization;
|
|
- unknown and missing SNI returning no vhost;
|
|
- mapped `client_ip` normalization in Stored Logs;
|
|
- keyword matching against protocol diagnostic values;
|
|
- safe per-vhost filename fallback;
|
|
- bounded protocol identity fields;
|
|
- protocol errors remaining outside Live Logs;
|
|
- preference validation for selectable Stored Logs columns.
|
|
|
|
A Rust-enabled release host must still run `cargo fmt --check`, Clippy with warnings denied, all-target/all-feature tests and the complete `dev.sh` release build before the package is considered compiler-verified.
|