v0.34.0_8
This commit is contained in:
1 parent
b6a3e12225
commit
480c3ec769
92 files changed
+298
-10035
No files matched your search
+1
-1
@@ -21,7 +21,7 @@ additional requests receive 429. Ambiguous duplicate control headers and
|
||||
request bodies are rejected. See
|
||||
[CACHE](CACHE.md#http-ban--purge-from-authorized-client-ips) for request examples,
|
||||
scoping, confirmation and reverse-proxy requirements, and
|
||||
[0.31.0 security review](SECURITY_AUDIT_0.31.0.md) for audit findings. The authenticated
|
||||
[security review](SECURITY_AUDIT.md) for audit findings. The authenticated
|
||||
`/api/cache/invalidate` endpoint remains unchanged.
|
||||
|
||||
## Browser documentation and anonymous surface
|
||||
|
||||
+1
-1
@@ -232,7 +232,7 @@ separate from the administrator-only selective invalidation API. If an
|
||||
application changes a GET resource after POST/PUT/PATCH/DELETE, use origin
|
||||
freshness/validators or one of the explicit invalidation paths; RustProxy
|
||||
does not guess the affected URLs. For exposure review and test scenarios, see
|
||||
[0.31.0 security audit](SECURITY_AUDIT_0.31.0.md).
|
||||
[security audit](SECURITY_AUDIT.md).
|
||||
|
||||
## Eligibility and freshness
|
||||
|
||||
|
||||
@@ -54,15 +54,12 @@ Native Rust test file: `src/cache/tests/hot_hit_034_tests.rs`.
|
||||
./dev.sh check
|
||||
CARGO_INCREMENTAL=0 ./dev.sh test
|
||||
CARGO_INCREMENTAL=0 cargo build --release --locked
|
||||
python3 scripts/tests/cache_hot_034_static_test.py
|
||||
cargo test --release --locked --lib manual_hot_lookup_microbenchmark -- --ignored --nocapture
|
||||
```
|
||||
|
||||
For real HTTP RPS use the supplied process regression and same-host nginx
|
||||
benchmark (`scripts/performance/README.md`) with **identical** configs. The
|
||||
standard nginx comparator benchmark explicitly disables the cache; it cannot
|
||||
measure this HOT-specific improvement. Compare two compiled RustProxy 0.34.0
|
||||
binaries with the same cache-enabled, cache-warmed vhost and fixed no-query URL.
|
||||
For real HTTP RPS, compare two compiled RustProxy 0.34.0 binaries
|
||||
with the same cache-enabled, cache-warmed vhost and fixed no-query URL.
|
||||
A cache-disabled nginx comparison cannot measure this HOT-specific improvement.
|
||||
Check `X-Cache: HIT`, body SHA-256, exact `Cache-Status`, transform marker, Age,
|
||||
ETag, p95/p99, CPU/request, memory and warm-up/eviction counts while measuring.
|
||||
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
# RustProxy 0.34.0 DEV — 1 KiB lock contention follow-up
|
||||
|
||||
## Goal and baseline
|
||||
|
||||
**Priority 1: reverse proxy, HTTP/1.1 keep-alive, uncompressed 1,040-byte
|
||||
responses, cache OFF.** Aiming for approximately 100,000 RPS and at least 80%
|
||||
of nginx under the same pinned-CPU benchmark. Priority 2: local static WWW,
|
||||
measured separately. No feature removals and **no version bump**.
|
||||
|
||||
The user supplied a **new executable** (SHA-256
|
||||
`1a84c58b20945bba9fdd1bc1a44d9e9d6ac7659d714343a46a1a8511a78331fa`,
|
||||
`rust-proxy 0.34.0`), apparently built from the prior DEV revision. It passed
|
||||
151 actual black-box HTTP tests with 0 failures and 1 skipped optional zstd
|
||||
decoder. This runtime coverage belongs to the **supplied executable only**,
|
||||
not to the freshly edited source below. Neither `cargo` nor `rustc` is
|
||||
available in the editing environment; the new revision has NOT been compiled.
|
||||
|
||||
## Modified source
|
||||
|
||||
### `Cache::record` and `Cache::record_reason`
|
||||
|
||||
Before, every HIT, MISS and BYPASS acquired the same `Store` mutex used by the
|
||||
HOT object store, exact LRU and cache inventory. With cache disabled, a 1 KiB
|
||||
request still contested that mutex to increment `cache_disabled`.
|
||||
|
||||
`src/cache/stats.rs` now holds bounded per-(scope,domain) statistics separately:
|
||||
|
||||
- An `ArcSwap`-published immutable, borrowed-lookup BTreeMap of domain handles.
|
||||
**The normal path does not lock or allocate**, even for BYPASS. New domains
|
||||
alone use a registration mutex and a copy-on-first-visit registry update.
|
||||
- Atomic counts for HIT, MISS, BYPASS, stored objects and saved body bytes.
|
||||
The common static reasons (`cache_disabled`, `cache_hit`, `cache_miss`,
|
||||
`request_cookie`) use direct atomic counters. Other reason names use another
|
||||
immutable published index and atomic counters; they are registered once.
|
||||
- Hard cardinality remains **4096 (scope, domain) pairs**. When full, new
|
||||
unregistered domains are skipped as before. Scope purge preserves stats.
|
||||
Only a revision-bearing global purge resets the per-domain registry.
|
||||
- The dashboard still exposes the same `DomainCounters`, reason strings,
|
||||
saved byte counts, ratio and per-vhost/object views. Counter snapshots are
|
||||
quiescent-exact, but under concurrent requests individual fields can reflect
|
||||
slightly different instants, consistent with other atomic metrics.
|
||||
- HOT object and COLD representation/LRU access, barriers, memory accounting,
|
||||
cache statuses, hit policy, retry and backend security paths are unchanged.
|
||||
|
||||
Trade-offs: a first-visit registration copies the bounded domain index and
|
||||
allocates one counter set; this is not an adaptive, unbounded cache. The hot
|
||||
path's ArcSwap guard and lookup still cost CPU. Measure that cost in profiles;
|
||||
compare under high-cardinality traffic to identify any excessive registration
|
||||
cost before production acceptance.
|
||||
|
||||
### Traffic analytics queue saturation
|
||||
|
||||
Previously `Exchange::drop` cloned the vhost, route, method, path, IP, user
|
||||
agent and country before trying to enqueue the `Sample`. If the bounded
|
||||
2,048-element MPSC queue was full, all those clones were discarded.
|
||||
|
||||
`Analytics::push_lazy` now acquires the same bounded Tokio queue permit with
|
||||
`try_reserve` first and creates the sample only after a successful reservation.
|
||||
Rejections increment `queue_dropped` exactly as before. Neither the analytics
|
||||
worker, counters/top lists, reset behavior nor request log path is removed.
|
||||
This improves allocation costs **on overflow** but does not turn dropped
|
||||
analytics into delivered samples; monitor the existing drop counter.
|
||||
|
||||
### Per-vhost duration histogram atomics
|
||||
|
||||
Previously `VhostMetrics::complete` incremented every cumulative latency bucket
|
||||
whose threshold covered the observation and the `+Inf` bucket. For an ordinary
|
||||
short response this meant **11 shared atomic RMWs** for the histogram alone,
|
||||
on top of existing exchange/status/byte/latency metrics.
|
||||
|
||||
It now increments **exactly one exclusive bucket** chosen from the same 10
|
||||
latency boundaries. Both the regular vhost and Docker-vhost Prometheus exporters
|
||||
compute the original cumulative `le` buckets, `+Inf` and `_count` from the 11
|
||||
exclusive buckets **at scrape time**. The metric names, labels, boundaries,
|
||||
body-lifetime timing and measurement collection remain intact. The cumulative
|
||||
snapshot may be concurrent with request completions, as before. Added boundary
|
||||
unit tests for exact thresholds and long-duration `+Inf`.
|
||||
|
||||
This is source-only: neither the throughput benefit nor compiled Rust behavior
|
||||
has been verified in the editing environment. Review exporter parity and
|
||||
under-load histogram correctness on the newly compiled executable.
|
||||
|
||||
## Verification and untested claims
|
||||
|
||||
- PASS: source preflight, cache/HOT/LB/RPS/histogram/static source guards and <=500-line
|
||||
production Rust modules.
|
||||
- PASS: SQLite migration + preset constraints, upgrade/lock and source
|
||||
replacement tests.
|
||||
- PASS: compiled user-supplied binary: 151 real HTTP checks, 0 failed,
|
||||
1 optional zstd skip. This binary predates the source changes above.
|
||||
- NOT RUN: Rust compile, Rust unit tests, native regressions or RPS after
|
||||
rebuilding the new source. No numerical speedup attributed to this patch.
|
||||
- Current diagnostic A/B on the user-supplied binary may use
|
||||
`scripts/performance/ab_1kb_dev.py` and must remain labelled *old vs already
|
||||
supplied binary*, **not** old vs newly edited source. The `wrk` 30 s x 7
|
||||
pinned-CPU benchmark remains the production acceptance gate.
|
||||
|
||||
## Repeat on a compiler-enabled benchmark host
|
||||
|
||||
```bash
|
||||
./dev.sh doctor
|
||||
./dev.sh check
|
||||
CARGO_INCREMENTAL=0 ./dev.sh test
|
||||
CARGO_INCREMENTAL=0 cargo build --release --locked
|
||||
python3 scripts/performance/regression.py \
|
||||
--binary previous=/absolute/path/to/supplied-rust-proxy \
|
||||
--binary edited="$(realpath target/release/rust-proxy)" \
|
||||
--out /tmp/rustproxy034-counter-http
|
||||
python3 scripts/performance/lb_regression.py \
|
||||
--binary previous=/absolute/path/to/supplied-rust-proxy \
|
||||
--binary edited="$(realpath target/release/rust-proxy)" \
|
||||
--out /tmp/rustproxy034-counter-lb
|
||||
python3 scripts/performance/transport_regression.py \
|
||||
--binary previous=/absolute/path/to/supplied-rust-proxy \
|
||||
--binary edited="$(realpath target/release/rust-proxy)" \
|
||||
--out /tmp/rustproxy034-counter-transport
|
||||
python3 scripts/performance/benchmark.py \
|
||||
--binary previous=/absolute/path/to/supplied-rust-proxy \
|
||||
--binary edited="$(realpath target/release/rust-proxy)" \
|
||||
--nginx /usr/sbin/nginx --wrk /absolute/path/to/wrk \
|
||||
--out /tmp/rustproxy034-counter-benchmark \
|
||||
--modes proxy --sizes small --concurrency 16,64,128 \
|
||||
--duration 30 --warmup 5 --repeats 7
|
||||
```
|
||||
|
||||
Explicitly set `max_idle_per_host=64` on **both** before/after vhosts, with the
|
||||
same TLS/H1/H2/health/cache/logging/settings. Verify 1,040 bytes and SHA-256,
|
||||
no failures, latency percentiles, per-request CPU, RSS, file descriptors,
|
||||
backend connects and all log/traffic drop counters. Test concurrent global
|
||||
purges and per-vhost purges during traffic and verify the stats reset behavior
|
||||
and cache HIT/MISS/BYPASS/reason distribution. Run long soaks and both cold/HOT
|
||||
cache tests. The 100k RPS / nginx 80% goal is still **OPEN**.
|
||||
|
||||
## Build fix after first compiler feedback (OPT2-FIX1)
|
||||
|
||||
The first user build of OPT2 failed with six Rust `E0753` diagnostics because
|
||||
`src/cache/stats.rs` starts with `//!` *inner module* documentation but is
|
||||
injected via `include!("stats.rs")` after other items in `src/cache/mod.rs`.
|
||||
The six documentation lines are now ordinary `//` comments; there is no
|
||||
behavior change. Three unnecessary `mut` bindings in
|
||||
`src/cache/dashboard.rs` were also removed (warnings only). Version remains
|
||||
0.34.0; no performance or feature semantics have changed in this fix.
|
||||
|
||||
The compiler-enabled host should rerun `./dev.sh check`,
|
||||
`CARGO_INCREMENTAL=0 ./dev.sh test` and an optimized release build, then
|
||||
performance benchmarks. This editing environment still lacks `cargo` and
|
||||
`rustc`, so the fix has been statically checked, **not compiled** here.
|
||||
@@ -1,137 +0,0 @@
|
||||
# RustProxy 0.34.0 DEV: 1 KiB reverse-proxy throughput audit
|
||||
|
||||
## Goal and evidence boundaries
|
||||
|
||||
Priority 1 is short (~1 KiB) HTTP reverse-proxy responses: aim for approximately
|
||||
100,000 requests/s, and eventually at least 80% of nginx under the **same**
|
||||
controlled benchmark, without disabling authentication, ACLs, caching semantics,
|
||||
rate limits, traffic accounting, request IDs, logs, body streaming or protocol
|
||||
support. Priority 2 is local WWW serving, to be tuned and measured separately.
|
||||
This is a development revision; Cargo/package version remains **0.34.0**.
|
||||
|
||||
**This source is not compiled in the current environment.** Neither `cargo`
|
||||
nor `rustc` is available. Only the user's pre-existing executable (SHA-256
|
||||
`453757748455d7b9b50a6140e322ee4d4cf4a030eea29318686f2e28c594b644`)
|
||||
was run. Live configuration A/B tests therefore substantiate a **configuration
|
||||
bottleneck**, not the throughput of this edited source.
|
||||
|
||||
## Real baseline experiment (existing executable only)
|
||||
|
||||
Disposable loopback nginx upstream, 1,040-byte body, HTTP/1.1 persistent
|
||||
frontend connections, caching disabled, identity response encoding, four Tokio
|
||||
workers, ApacheBench (`ab -k -n 12000`), 16/64 concurrent frontend clients.
|
||||
Checks outside the timed intervals verified HTTP 200, exact 1,040 bytes and
|
||||
SHA-256 `62a08d75cdde0416c883d72458385b5e56ae8c14466934ad784587ddb93a548b`;
|
||||
0 failed requests in the shown samples. The host has shared CPU resources;
|
||||
results vary and are not a pinned-CPU or production acceptance benchmark.
|
||||
|
||||
| Existing executable configuration | 16 clients, RPS | 64 clients, RPS |
|
||||
|---|---:|---:|
|
||||
| max_idle_per_host = 4 | 18,226 | 17,935 |
|
||||
| max_idle_per_host = 64 | 30,906 | 24,872 |
|
||||
| Relative change (configuration only) | +69.6% | +38.7% |
|
||||
|
||||
A separate repeat experiment at 64 clients (two runs per variant, in order)
|
||||
produced: `idle4` 14,028 / 14,834; `idle16` 22,205 / 24,711;
|
||||
`idle32` 25,580 / 29,088; `idle64` 36,521 / 33,847;
|
||||
`idle64 + recycle 250,000` 31,168 / 29,753;
|
||||
`idle32_again` 34,798 / 29,194; `idle64_again` 34,677 / 29,965;
|
||||
`idle4_again` 23,503 / 23,514 RPS. The order drift warns against
|
||||
reporting a single percentage as a robust speedup. Raising generation
|
||||
recycling from 25,000 to 250,000 did not establish any improvement; its
|
||||
original limit is retained. All shown variants reported zero HTTP failures.
|
||||
|
||||
A preliminary nginx comparison gave ~75k/117k RPS at 16/64 clients, but later
|
||||
nginx samples were ~50k/93k on the shared host. Do not compute a definitive
|
||||
"% nginx" from these non-isolated runs. The 100k/80% goals remain **OPEN**.
|
||||
|
||||
## Source modifications
|
||||
|
||||
| Location | Change | Preserved behavior / caveat |
|
||||
|---|---|---|
|
||||
| `src/balance/model.rs` | New vhost default `max_idle_per_host`: **4 -> 64** | Configured values **1..64** remain validated; existing explicitly saved `4` stays `4`. Disabled keepalive, 30 s idle timeout, 25k-request and 3,600 s recycling rules are unchanged. Increased idle sockets use more file descriptors and memory. |
|
||||
| `src/admin/web/vhosts/editor.js` | Match the new `64` default when the field is absent | Existing values, limits and all editor settings remain unchanged. |
|
||||
| `src/proxy/body.rs` | At `Exchange::drop`, use exclusive `Mutex::get_mut()` rather than lock/unlock; move owned `route`, `client_ip`, `country`, `upstream_error` into final observation where possible | Exactly the same log fields, analytics sample, counters, permit lifetime, errors and websocket/H3 fields. Analytics still gets a clone when a vhost sample is queued; history still receives the original. |
|
||||
| `src/proxy/headers.rs` | Skip reserved cookie sanitization immediately if the corresponding `Cookie`/`Set-Cookie` header is absent | Full sanitization, admin-cookie separation and restoration restrictions still apply when the header is present. |
|
||||
| `src/balance/tests.rs`, `src/proxy/body/tests.rs`, `src/proxy/headers/tests.rs` | Add compatibility, completion and cookie regression assertions | These Rust tests **require** a compiler before they count as run. |
|
||||
| `scripts/tests/*hotpath*` | Update source-only guards for the new **bounded** default | Static assertions do not prove functional or runtime performance equivalence. |
|
||||
|
||||
**Persistence note:** old vhosts may store `keepalive.max_idle_per_host=4`
|
||||
explicitly; the new code default does not rewrite them. To apply the measured
|
||||
configuration improvement before rebuilding, change that option to `64` in the
|
||||
relevant vhost and verify actual idle socket/FD usage. Where hundreds of origins
|
||||
share a host, test `16` or `32` if the FD/memory trade-off is preferable.
|
||||
|
||||
## Bottlenecks to profile next, not silently disable
|
||||
|
||||
1. `src/cache/dashboard.rs::Cache::record`: obtains a **shared cache-store
|
||||
mutex on every outcome**, including `BYPASS` when cache is disabled through
|
||||
`src/proxy/forward/cache_lookup.rs`. This may compete with HOT cache
|
||||
operations and admin dashboard reads; profile `pthread_mutex`/futex and
|
||||
lock hold times. Candidate: separate bounded, sharded domain/reason
|
||||
counters from the HOT object store while preserving exact dashboard totals,
|
||||
reason labels and reset semantics.
|
||||
2. `src/logging/format.rs::Logs::push`: per-request bounded history
|
||||
`try_lock`, potentially causing recorded log drops under high concurrency.
|
||||
Preserve *existing* drops/accounting, live subscribers and external sinks.
|
||||
Candidate: worker-local bounded batches or sharded history with deterministic
|
||||
bounded merge; validate log order, recent history, retention and drops.
|
||||
3. `src/proxy/body.rs::Exchange::drop` + `src/traffic.rs::Analytics::push`:
|
||||
request-sized log strings, sampling queue sends, atomics and lifecycle
|
||||
observations. Profile allocations and CPU per completed request. Candidate:
|
||||
compact shared immutable identity fields or conditional cloning solely
|
||||
where consumers require ownership, without removing logs or analytics.
|
||||
4. `src/proxy/forward/route_cache.rs`: request-target path copies,
|
||||
header policy and several route/cache eligibility steps. Measure with
|
||||
sampling `perf record -g` before extracting additional fast paths; preserve
|
||||
query handling, canonical host, ACL, authentication, retries and WebSockets.
|
||||
5. `src/upstream/*`: profile H1 pool misses, TCP connect rate, Hyper
|
||||
connection reuse and generation swaps. Increasing recycle thresholds or
|
||||
changing HTTP/2/H2C modes without an isolated performance case is not
|
||||
recommended.
|
||||
6. **Separate WWW priority:** benchmark short static objects independently;
|
||||
existing `sendfile: auto` applies to eligible cleartext H1 objects >=64 KiB
|
||||
and cannot accelerate 1 KiB bodies.
|
||||
|
||||
No lock removal, tracing/log disabling, caching-policy weakening, bypass of
|
||||
security checks, or transformation of complete bodies into buffered copies is
|
||||
proposed without a test proving semantic equivalence.
|
||||
|
||||
## Acceptance gate on a machine with Rust toolchain and isolated load generator
|
||||
|
||||
```bash
|
||||
./dev.sh doctor
|
||||
./dev.sh check
|
||||
CARGO_INCREMENTAL=0 ./dev.sh test
|
||||
CARGO_INCREMENTAL=0 cargo build --release --locked
|
||||
python3 scripts/performance/regression.py \
|
||||
--binary after="$(realpath target/release/rust-proxy)" \
|
||||
--out /tmp/rustproxy034-1kb-regression
|
||||
python3 scripts/performance/lb_regression.py \
|
||||
--binary before=/absolute/path/to/old-rust-proxy \
|
||||
--binary after="$(realpath target/release/rust-proxy)" \
|
||||
--out /tmp/rustproxy034-1kb-lb
|
||||
python3 scripts/performance/transport_regression.py \
|
||||
--binary before=/absolute/path/to/old-rust-proxy \
|
||||
--binary after="$(realpath target/release/rust-proxy)" \
|
||||
--out /tmp/rustproxy034-1kb-transport
|
||||
python3 scripts/performance/benchmark.py \
|
||||
--binary before=/absolute/path/to/old-rust-proxy \
|
||||
--binary after="$(realpath target/release/rust-proxy)" \
|
||||
--nginx /usr/sbin/nginx --wrk /absolute/path/to/wrk \
|
||||
--out /tmp/rustproxy034-1kb-benchmark \
|
||||
--modes proxy --sizes small --concurrency 16,64,128 \
|
||||
--duration 30 --warmup 5 --repeats 7
|
||||
```
|
||||
|
||||
Use the **same explicit** `max_idle_per_host=64` and all other vhost settings
|
||||
for the *source A/B* test. Run 4 vs 64 as a separate *configuration A/B* test;
|
||||
never attribute a settings difference to code. Verify full content checksums,
|
||||
HTTP 2xx/4xx/5xx, p50/p95/p99, drop counters, backend connections,
|
||||
FD/RSS trends, CPU/request and packet/TCP retransmit rates. Test H1/H2/H3,
|
||||
TLS, WebSockets, sticky/rate/ACL, cache MISS/HIT/BYPASS, admin sessions, active
|
||||
failover, cancellation, idle timeouts and a sustained memory/connection soak.
|
||||
Profile the freshly **built** executable separately from timed RPS runs.
|
||||
|
||||
Until compilation and those gates pass, this is a **development source-only
|
||||
candidate**, not a claim of reaching 100k RPS or 80% nginx.
|
||||
@@ -1,88 +0,0 @@
|
||||
# RustProxy 0.34.0 DEV — 1,040-byte response, OPT3
|
||||
|
||||
## Scope
|
||||
|
||||
Primary: uncached reverse proxy, HTTP/1.1 keep-alive, 1,040-byte body,
|
||||
no compression, ACL/security/logging/analytics/counters fully operational.
|
||||
Goal: ~100,000 RPS and >=80% of nginx on the **same isolated host**.
|
||||
Secondary: local Web Server/static files, measured independently.
|
||||
**Version remains exactly 0.34.0**; existing configuration and defaults are
|
||||
unchanged; no functions, checks or logs are disabled.
|
||||
|
||||
## New code optimizations (relative to OPT2-FIX1)
|
||||
|
||||
1. `src/acl.rs` / `src/proxy/cache_helpers.rs`: borrow ordinary validated
|
||||
routing paths using `Cow<str>` during immutable route selection. Calls
|
||||
with escaped (`%xx`) or repeated separators still use the original strict
|
||||
allocator/decoder. Invalid control bytes, dot segments, UTF-8 and ambiguous
|
||||
encodings remain rejected; the original wire path is not rewritten.
|
||||
2. `src/proxy/forward_entry.rs` plus cache/Web Service call sites: retain the
|
||||
original `http::Uri` value so normal paths can remain borrowed during the
|
||||
entire mutable forwarding/rewrite operation. Cache-refresh tasks that need
|
||||
ownership still allocate explicitly and hold their own `String`.
|
||||
3. `src/forwarding.rs`: a **direct, untrusted** TCP peer now obtains the same
|
||||
seven forwarding headers without allocating hop-chain `Vec` structures.
|
||||
Trust chains and the configured `trust_forwarded_proto` logic use the
|
||||
unchanged full parser. IPv4/IPv6 RFC 7239 formatting and true listener
|
||||
port/scheme are preserved; untrusted spoofed values are still ignored.
|
||||
4. `src/proxy/forward_entry.rs`: when effective client IP is identical to
|
||||
the transport peer, reuse the IP stored by observation. No redundant
|
||||
`IpAddr::to_string()` and no per-exchange mutex write. Different/trusted or
|
||||
IPv4-mapped addresses are still stored in canonical form.
|
||||
5. `src/proxy/headers.rs`: plain single-valued `Connection: keep-alive`
|
||||
avoids the temporary `Vec<HeaderName>` while all standard hop-by-hop fields
|
||||
are removed; comma-separated, repeated and complex Connection fields keep
|
||||
the original removal parser to prevent nominated-header leaks.
|
||||
6. `src/proxy/request_id.rs`: preserve SHA-256, CSPRNG, per-process sequence,
|
||||
unpredictable 64-char hexadecimal IDs and wire headers, but write the hex
|
||||
digits into one preallocated byte buffer before UTF-8 verification.
|
||||
7. `src/cache/stats.rs`: select the most frequent `cache_disabled` reason
|
||||
counter directly. The same exact atomic totals, reason labels, 4096-domain
|
||||
limit and reset behavior are retained.
|
||||
|
||||
The changes avoid repeated work in common cases; no numerical throughput
|
||||
benefit has been measured for this new source. In particular, no cache, log,
|
||||
metrics, retry or auth feature was switched off for a synthetic benchmark.
|
||||
|
||||
## Evidence and known limits
|
||||
|
||||
- Provided newly compiled *OPT2-FIX1* executable SHA-256:
|
||||
`6037e98d059672b716f2e1411976bf305cac927901a6f6662d8b703829ff8196`.
|
||||
- PASS **89** HTTP proxy/policy/cache black-box assertions, zero failures,
|
||||
testing that supplied executable, **not** the new OPT3 code.
|
||||
- Source checks, migration/upgrade checks and ZIP checks are separate
|
||||
static/offline verification; they are not equivalent to cargo/test/live.
|
||||
- A single interrupted diagnostic A/B sample at c=64, 3 s, same idle=64,
|
||||
gave older binary 33,752 RPS and OPT2-FIX1 binary 33,556 RPS, zero failed
|
||||
requests; do **not** infer a true performance difference from one sample.
|
||||
- NOT RUN here: `cargo check`, `cargo test`, rebuilt OPT3 integration tests,
|
||||
post-build A/B RPS, long soaking, real TLS/H2/H3 compatibility and 80%-of-nginx
|
||||
acceptance. No Rust toolchain is available in this editing container.
|
||||
- Resource/latency trade-offs (including log/analytics drops, RSS/FDs) require
|
||||
the same measured and pinned server/peer/generator CPU sets for before/after.
|
||||
|
||||
## Rebuild and measure
|
||||
|
||||
From the unpacked `RustProxy/` directory:
|
||||
|
||||
```bash
|
||||
./dev.sh check
|
||||
CARGO_INCREMENTAL=0 ./dev.sh test
|
||||
bash dev.sh
|
||||
python3 scripts/performance/regression.py \
|
||||
--binary new=/ABSOLUTE/PATH/NEW/rust-proxy --out /tmp/rustproxy-opt3-http
|
||||
python3 scripts/performance/ab_1kb_dev.py \
|
||||
--binary before=/ABSOLUTE/PATH/OPT2FIX1/rust-proxy \
|
||||
--binary after=/ABSOLUTE/PATH/OPT3/rust-proxy \
|
||||
--nginx /usr/sbin/nginx --ab /usr/bin/ab \
|
||||
--out /tmp/rustproxy-opt3-1kb --concurrency 16,64 \
|
||||
--duration 5 --repeats 3 --workers 4 --max-idle 64
|
||||
```
|
||||
|
||||
For an acceptance-grade evaluation, run the `scripts/performance/benchmark.py`
|
||||
paired before/after/nginx test with separate pinned CPU sets, a verified origin
|
||||
and 30-second runs repeated 7 times. Report median and p95/p99 latency,
|
||||
errors, CPU, RSS, file descriptors, dropped log/analytics events, and all HTTP
|
||||
status/body hashes. Retest TLS/H2/H3/WebSocket, auth, cache semantics and
|
||||
connection/backpressure limits. Compare the **rebuilt** OPT3 binary against
|
||||
OPT2-FIX1 at exactly the same listener/backend settings and compiler profile.
|
||||
@@ -1,97 +0,0 @@
|
||||
# RustProxy 0.34.0 DEV / OPT4: HTTP/1.1 small-body follow-up
|
||||
|
||||
## Objective and validity
|
||||
|
||||
Primary goal: 1,040-byte reverse-proxy response, HTTP/1.1 keep-alive,
|
||||
cache disabled; target about 100k RPS and at least 80% of nginx on the **same
|
||||
isolated host**. Secondary goal: local static Web Server, benchmarked separately.
|
||||
No version bump or feature, metric, logging, ACL, authentication, TLS,
|
||||
HTTP/2/3, stream, retry or cache-policy bypass is introduced.
|
||||
|
||||
The **provided and compiled OPT3 binary** (SHA-256
|
||||
`2efe273340f9cf92c5900c85c0bd1b775c87a9d1eaff1a10ec7635d268b080b1`)
|
||||
passed 151 black-box HTTP assertions: 0 failures and 1 optional Zstandard
|
||||
case skipped. This is evidence for the **input** binary, not for the new
|
||||
OPT4 sources. OPT4 still requires a native Rust build, cargo tests and a
|
||||
post-build benchmark. The diagnostic same-host CPU shares resources and is
|
||||
not an acceptance benchmark.
|
||||
|
||||
## Code changes relative to OPT3
|
||||
|
||||
1. `src/proxy/forward/route_cache.rs`: store normal public path/query as
|
||||
`Cow<'_, str>` referencing the independently retained, immutable original
|
||||
URI. Without a path rewrite both public/origin suffixes stay borrowed;
|
||||
strict encoded/rewrite handling remains an owned validated string.
|
||||
Cache refresh tasks explicitly call `.to_string()` only when they actually
|
||||
must retain an owned target across spawned tasks.
|
||||
2. `src/proxy/upstream_helpers.rs`: skip allocating a request-body metering
|
||||
adapter **only** when `Body::is_end_stream()` establishes that it contains
|
||||
no DATA frames or trailers. Every nonempty/streaming request retains the
|
||||
original metering, timeout, backend lease accounting and cancellation.
|
||||
The response adapter still retains the exchange/inflight permit and handles
|
||||
completion/response bytes for empty requests.
|
||||
3. `src/proxy/types.rs`, `src/proxy/forward/upstream.rs`: consume owned
|
||||
`http::Uri` when preparing the upstream target. HTTP/1.1 returns it directly;
|
||||
H2 takes the same URI into parts for strict authority rewriting. Remove an
|
||||
unnecessary intermediate URI clone; replay still builds independent URIs.
|
||||
4. `src/forwarding.rs`: reuse the formatted IPv4 peer address when creating
|
||||
RFC 7239 `Forwarded` as well as X-Real-IP/X-Forwarded-For. Preserve quoted
|
||||
IPv6 bracket and proxy-chain logic for trusted and untrusted peers.
|
||||
5. `src/proxy/upstream_helpers.rs`: fast-path byte-safe truncation of short
|
||||
or ASCII host/path log values, keeping the original max-*characters*
|
||||
semantics for non-ASCII UTF-8.
|
||||
6. `src/proxy/headers.rs`: avoid nominated-field token parsing for absent,
|
||||
lone `keep-alive`, or lone `close` Connection values. A `close` field on
|
||||
the wire still forces the general parser, so a nominated `Close` header
|
||||
is removed exactly as before. Multi-value, comma lists and malformed tokens
|
||||
retain the previous defensive parser.
|
||||
7. `src/proxy/headers.rs`: similarly avoid the generic token parser when
|
||||
checking HTTP upgrade on plain keep-alive, close, or absent Connection.
|
||||
Complex multi-valued requests and explicit Upgrade remain unchanged.
|
||||
|
||||
## Tests and limits
|
||||
|
||||
- New Rust cases validate empty vs nonempty request-body permits and bytes,
|
||||
H1 URI/authority/query, ASCII/Unicode truncation, and Connection variants
|
||||
including a literal nominated `Close` field. Existing regression coverage
|
||||
still includes sticky backends, cache, ACL and trusted forwarded identity.
|
||||
- Source, SQLite migration and upgrade tests are necessary but **not** Rust
|
||||
compilation or performance evidence. The compile check must run before use.
|
||||
- Run `./dev.sh check && CARGO_INCREMENTAL=0 ./dev.sh test`, followed by
|
||||
the native black-box acceptance suite (proxy, policy, cache, transform,
|
||||
compression, static serving), plus TLS/H2/H3 and websocket tests.
|
||||
|
||||
## Measurements
|
||||
|
||||
An initial diagnostic same-host `ab` sample at concurrency 16 gave
|
||||
OPT2 33,042.55 RPS and compiled OPT3 36,962.11 RPS, without HTTP errors;
|
||||
this single sample is not proof of a reliable uplift. A separate complete
|
||||
12-sample run (two 3-second repetitions each at 16/64 clients) produced:
|
||||
|
||||
| Concurrency | OPT2 median RPS | OPT3 median RPS | nginx median RPS | OPT3/nginx |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| 16 | 21,608 | 29,906 | 92,586 | 32.3% |
|
||||
| 64 | 38,060 | 34,781 | 123,809 | 28.1% |
|
||||
|
||||
All 12 samples had valid 200 responses and correct 1040-byte body checks.
|
||||
OPT3 was slower than OPT2 at concurrency 64; the variability is too large to
|
||||
claim a sustained uplift or regression based on two short same-host samples.
|
||||
Raw evidence includes run order, response checksum and nginx comparator.
|
||||
No result here measures OPT4, which was not built in this environment.
|
||||
|
||||
## Next evaluation commands
|
||||
|
||||
```bash
|
||||
./dev.sh check
|
||||
CARGO_INCREMENTAL=0 ./dev.sh test
|
||||
bash dev.sh
|
||||
python3 scripts/performance/regression.py --binary opt4=/absolute/path/target/debug/rust-proxy --out /tmp/opt4-http
|
||||
python3 scripts/performance/ab_1kb_dev.py --binary opt3=/absolute/path/opt3/rust-proxy --binary opt4=/absolute/path/target/debug/rust-proxy --nginx /usr/sbin/nginx --ab /usr/bin/ab --out /tmp/opt4-ab --duration 5 --repeats 3 --concurrency 16,64 --workers 4 --max-idle 64
|
||||
```
|
||||
|
||||
For authoritative acceptance: pin client/server/origin to nonoverlapping CPU
|
||||
sets, align build profile and all flags, compare 30s x7 runs, record median,
|
||||
p95/p99, correct 1040 B response checksum and status, CPU, RSS, FDs, cache
|
||||
stats, analytic/sink queue drops, errors, and both HTTP/1.1 and optional
|
||||
HTTP/2 endpoints. Reject changes on new 4xx/5xx, body mismatches, regressions
|
||||
of access controls or material memory/fd increases.
|
||||
@@ -1,93 +0,0 @@
|
||||
# RustProxy 0.34.0 DEV — OPT5, 1,040-byte reverse-proxy optimization
|
||||
|
||||
## Acceptance boundary
|
||||
|
||||
The source package preserves **0.34.0** and all existing configurable services,
|
||||
logs, dashboards, cache/ACL/auth policies, streaming, TLS/H2/H3/WebSocket,
|
||||
failover, retry and bounded-resource behavior. OPT5 is a **source-only** candidate:
|
||||
no Rust compiler is available in the editing environment. The new code's
|
||||
compilation, Rust unit tests, binary throughput and full-protocol interoperability
|
||||
are unverified until the developer builds it. The compiled OPT4 executable is
|
||||
the known test input, **not** a benchmark of OPT5.
|
||||
|
||||
## Compiled OPT4 input verification
|
||||
|
||||
Input: `/mnt/data/rust-proxy(4)`, SHA-256
|
||||
`1c4c701f11b57b1293020061c8af21d5a9df0c86f03290018a90edc007667b24`.
|
||||
|
||||
- The real HTTP regression runner completed **151 passing**, **0 failing** cases;
|
||||
**1 skipped** because Python `zstandard` is not installed. Protocol tests for
|
||||
TLS/H2/H3 are a separate acceptance gate and were **not** passed by this run.
|
||||
- Same-host ApacheBench HTTP/1.1 keep-alive, 1,040-byte body verified by exact
|
||||
SHA-256, cache OFF, 4 Tokio workers, 64 idle HTTP1 upstream connections,
|
||||
3-second warm-and-measure samples, two repeats per variant/concurrency:
|
||||
|
||||
| RPS, median of 2 samples | 16 clients | 64 clients |
|
||||
| --- | ---: | ---: |
|
||||
| OPT3 | 37,017 | 40,668 |
|
||||
| OPT4 | 39,054 | 36,425 |
|
||||
| nginx | 99,808 | 110,783 |
|
||||
| OPT4 vs OPT3 | +5.5% | -10.4% |
|
||||
| OPT4 / nginx | 39.1% | 32.9% |
|
||||
|
||||
There were zero HTTP failures and the response body was verified before/after
|
||||
measurements. Shared CPU and only two short samples are diagnostic, NOT a stable
|
||||
speedup or production proof. The 100k RPS and 80%-of-nginx goals remain unmet.
|
||||
|
||||
## OPT5 changes (8 coherent hot-path optimizations)
|
||||
|
||||
1. `src/proxy/request_id.rs`: eliminate per-request SHA-256. Keep the existing
|
||||
OS-seeded/reseeded thread-local CSPRNG and 256-bit ID; XOR a monotonic
|
||||
sequence into the first 8 nonce bytes. Output remains uniformly random,
|
||||
64 lowercase hex characters and the protected `X-Request-ID` correlation
|
||||
remains. Random IDs continue to be unique with overwhelming probability.
|
||||
2. `src/forwarding.rs` and `src/proxy/forward_entry.rs`: only construct
|
||||
`Context.original_url` for a frontend with configured Tinyauth. This is the
|
||||
*only* runtime consumer of that absolute URL. Public `context(...)` still
|
||||
constructs it by default. Trusted/untrusted forwarding and all validation
|
||||
still execute identically; IPv4/IPv6 and `x-original-url` for Tinyauth remain.
|
||||
3. `src/balance/lease_runtime.rs`: borrow `"ok"` for successful backend health
|
||||
observations instead of allocating a `String`. 5xx failures still record the
|
||||
exact original `status=NNN` detail and slow/passive health counters are kept.
|
||||
4. `src/traffic.rs`: receive 64 samples at a time (Tokio `recv_many`) and apply
|
||||
them under a single dashboard-state mutex. Order within the existing bounded
|
||||
2,048-sample queue is preserved; waiting is outside the mutex and producer
|
||||
backpressure and drop accounting are unchanged.
|
||||
5. `src/traffic.rs`: reuse map entries with borrowed vhost names. New keys are
|
||||
cloned only when a domain first appears, preserving the max 64-vhost bound.
|
||||
6. `src/traffic.rs`: classify status 1xx–5xx using static labels; preserve
|
||||
unknown extension status labels such as `7xx`.
|
||||
7. `src/traffic.rs`: replace per-sample lowercase allocation for User-Agent
|
||||
family detection with ASCII-insensitive byte-window comparisons, and avoid
|
||||
copying all TopTable maps when displaying a single vhost.
|
||||
8. `src/proxy/upstream_helpers.rs`: skip resolving inherited/vhost/web-service
|
||||
compression settings when no encoding is accepted even under the global
|
||||
codec ceiling (for example `Accept-Encoding: identity`). Existing compression
|
||||
handling is retained for every accepted codec and request method.
|
||||
|
||||
The caller's front-end policies remain evaluated on every request. No log,
|
||||
metric, access control or authentication feature was disabled or made optional.
|
||||
|
||||
## Verification and outstanding gates
|
||||
|
||||
Static source, SQL migration and managed-upgrade tests are executed and their
|
||||
outcomes supplied in the external evidence archive. New Rust test fixtures cover
|
||||
request IDs, forwarding/trusted IPv6, compression, healthy/5xx backend detail,
|
||||
analytics batching, top classes, country-independent User-Agent matching,
|
||||
reset and the bounded queue.
|
||||
|
||||
To verify the new build on the developer's system:
|
||||
|
||||
```bash
|
||||
./dev.sh check
|
||||
CARGO_INCREMENTAL=0 ./dev.sh test
|
||||
./dev.sh build
|
||||
```
|
||||
|
||||
After building, repeat the actual HTTP regression, LB regression and TLS/H2/H3
|
||||
transport tests and collect at least seven 30-second CPU-isolated, warmed,
|
||||
interleaved OPT4/OPT5/nginx measurements with `wrk`, p95/p99 and CPU/RSS.
|
||||
Treat the non-tested binaries, short shared-host tests and simulated Python
|
||||
checks as distinct types of evidence. Measure the secondary Web Server path
|
||||
separately. Do not alter the version or silently relax cache, auth or timeout
|
||||
policies to reach a RPS target.
|
||||
@@ -1,84 +0,0 @@
|
||||
# RustProxy 0.34.0 — reverse-proxy hot-path candidate
|
||||
|
||||
## What was changed
|
||||
|
||||
1. **Routes:** `Snapshot.routes` and `fallbacks` now hold
|
||||
`Arc<CompiledRoute>` handles. The same compiled location is shared across
|
||||
hostname aliases. `observe` resolves vhost/location once from the accepted
|
||||
listener/TLS-state and stores a request-local handle. `forward_request`
|
||||
consumes the same immutable snapshot/route; internal error fallbacks still
|
||||
resolve the explicitly configured named location independently.
|
||||
2. **Host matching:** wildcard candidate strings are generated lazily only
|
||||
after an exact host failed, without changing longest-specific-suffix order.
|
||||
3. **LB:** single-backend non-cookie pools use a direct admitted lease without
|
||||
temporary candidate vectors or scheduler mutex. Multi-backend weighted
|
||||
round-robin retains its ordering and mutex; least-connection/traffic,
|
||||
adaptive, IP hash and cookie-affinity retain their behavior. IP hash avoids
|
||||
repeated canonicalization/temporary `format!` strings for each candidate.
|
||||
4. **Health:** a `healthy_cached` atomic allows checking healthy backends
|
||||
without the health mutex; all state transitions remain inside the existing
|
||||
state machine, including passive quarantine and half-open recovery.
|
||||
Resolved active/passive per-member policies are held in shared immutable
|
||||
`Arc` handles so a new request lease does not deep-clone health paths,
|
||||
headers or optional host strings.
|
||||
5. **Fallback/retry:** request body+HeaderMap replay templates are made only
|
||||
for configured error fallbacks. Retry headers are cloned only for eligible
|
||||
safe replay requests where another member or H2 graceful close makes retry
|
||||
possible; writes, streaming bodies and WebSockets are not replayed.
|
||||
6. **Telemetry:** three per-request mutex-backed scalar tags are now atomic;
|
||||
their original event strings and `Option<bool>` log fields are decoded at
|
||||
exchange completion. Request ID, method, host and path are moved into the
|
||||
terminal log record instead of deep-cloned in Drop; the traffic sample
|
||||
still receives its own required copies. Logs avoid a broadcast clone if
|
||||
there are no subscribers, preserving history, sink and counters.
|
||||
|
||||
## Preserved boundaries
|
||||
|
||||
All vhost, ACL, GeoIP, Basic Auth, Tinyauth, WebSocket, retry, cache,
|
||||
transform, compression, log, metrics, `sendfile`, PHP-FPM, HTTPS/H2/H3 and
|
||||
admin panel configuration paths are intended to remain compatible. Defaults
|
||||
and SQLite schema are unchanged; no new performance toggle was added. The
|
||||
optimization reuses route objects within one immutable snapshot but does not
|
||||
allow stale snapshot cache fills (existing revision ticket barrier remains).
|
||||
Sticky cookies deliberately retain the general selection path, and upstream
|
||||
request-count recycling is unchanged (default 25,000 per generation).
|
||||
|
||||
## Locally executed checks vs required checks
|
||||
|
||||
- **Executed locally:** scoped-cache/sendfile/hot-path *source-only* guards,
|
||||
Python syntax, SQLite migration and upgrade preservation tests, selected JS
|
||||
panel scripts and simulated editor tests. These do **not** prove Rust builds.
|
||||
- **Prepared, NOT executed:** Rust unit tests for shared routing, single-member
|
||||
LB capacity/health, passive ejection/recovery, atomic log fields, retained
|
||||
sendfile, cache and protocol behavior.
|
||||
- **Unmeasured:** 0.34.0 requests/sec, latency, CPU cost, memory and nginx gap.
|
||||
Do not infer a throughput gain from fewer source-code locks/allocations.
|
||||
|
||||
## Build and acceptance
|
||||
|
||||
Run on the intended Linux builder with exactly the same compiler, dependencies,
|
||||
release flags and workload as the 0.33.8 reference:
|
||||
|
||||
```bash
|
||||
./dev.sh doctor
|
||||
./dev.sh check
|
||||
CARGO_INCREMENTAL=0 ./dev.sh test
|
||||
CARGO_INCREMENTAL=0 cargo build --release --locked
|
||||
python3 scripts/tests/proxy_hotpath_034_static_test.py
|
||||
python3 scripts/tests/cache_scope_034_static_test.py
|
||||
python3 scripts/tests/sendfile_034_static_test.py
|
||||
python3 scripts/tests/dashboard_presets_sql_test.py
|
||||
python3 scripts/tests/upgrade_performance_test.py
|
||||
```
|
||||
|
||||
Next run `scripts/performance/regression.py`, `lb_regression.py`,
|
||||
`transport_regression.py` and `concurrency_regression.py` against both binary
|
||||
versions. After functional acceptance, run the paired
|
||||
`scripts/performance/benchmark.py` suite against the unmodified baseline,
|
||||
this candidate and nginx; use the instructions in
|
||||
`scripts/performance/README.md` including separate CPU affinities.
|
||||
|
||||
Measure RPS/p95/p99, CPU microseconds per request, RSS, FDs, pool rotations,
|
||||
health transitions, cache HIT ratio and logger drops. For each payload report
|
||||
`(0.34.0 RPS / 0.33.8 RPS - 1)*100` and `0.34.0 RPS/nginx RPS*100` from real
|
||||
medians. A source-only revision is **not production-accepted**.
|
||||
@@ -1,78 +0,0 @@
|
||||
# RustProxy 0.34.0: hot-path throughput revision
|
||||
|
||||
**Status: uncompiled source candidate.** This revision does not change the
|
||||
application version, public API, dashboard controls, cache policy or tuning
|
||||
defaults. It must not be labelled production-ready until the new Rust binary
|
||||
passes compile, unit tests, live regressions and paired performance comparisons.
|
||||
|
||||
## Code changes and intended cost reductions
|
||||
|
||||
| Focus | Change | Kept behavior / risk to verify |
|
||||
|---|---|---|
|
||||
| Per-request identity | Draw a 256-bit nonce from the OS-seeded/reseeded thread-local CSPRNG instead of asking `getrandom` on every request. Preserve SHA-256 of nonce+monotonic sequence and the existing 64-hex ID format. | IDs remain non-predictable. Check ID uniqueness and raw upstream headers. |
|
||||
| Upstream HTTP request target | Compile the validated scheme/authority `http::Uri` once with each immutable backend; parse only the request path+query and rebuild using typed `Uri::from_parts`. | Never accept a client-supplied authority, bypass DNS/address policies or change SNI. Test H1, H2, IPv6, query and percent-encoding. |
|
||||
| Load balancing | Do not clone the member ID string for an existing smooth-WRR scoreboard entry. Continue the previously introduced no-mutex single-member non-cookie path. | Sticky-cookie modes, capacity limits, retry exclusions, health and drain remain unchanged. |
|
||||
| Active health | Use already-compiled `Arc<HealthCheck>` when dispatching probes, avoiding a clone of its path/headers for each scheduled probe. | Same 250-ms supervisor, timeout, enabled/claim gate and health state transitions. |
|
||||
| Per-frame streaming | Capture the leased backend once at first streamed request DATA (response already captured at wrapper construction). Do not take `Exchange.backend` mutex on every upload chunk. | Request/response byte counters, mid-stream failures, idle timeouts and lease lifetime remain active. |
|
||||
| Pool recycling | Preserve exact request-count and elapsed-time recycling checks per request, with the existing independent 250-ms maintenance cycle. The pool factory/connection policy is unchanged. | Exact lifecycle semantics are more important than saving one cheap clock read; tune idle/age limits only after profiling. |
|
||||
| Log history | Move completed `LogEntry` directly into bounded history when there are no live viewers or enabled external sinks, instead of deeply cloning its strings. Continue cloning and delivering to live viewers/sinks when enabled. | Same JSON fields, history size, drop counters, file/Loki/Logstash forwarding. |
|
||||
| Route/HTTP instrumentation | Remove redundant route-ID mutex/write in forwarding and collapse H1/H2 version tallying into one branch. | Route is recorded once before forwarding; H1/H2 counters remain. |
|
||||
|
||||
These historical changes build on the previous 0.34.0 route-Arc, single-backend LB,
|
||||
conditional retry/fallback-header cloning, atomic exchange flags and cache HOT
|
||||
LRU changes. There are no new UI toggles. This **earlier** revision retained `max_idle_per_host`
|
||||
4 and `pool_recycle_requests` 25,000. The **subsequent DEV 1 KiB revision**
|
||||
changes only the default idle limit to 64, based on live configuration A/B
|
||||
with the old binary (not source A/B); see `docs/PROXY_1KB_HOTPATH_DEV_0.34.0.md`. `sendfile: auto` remains opt-in and applies only to eligible Linux
|
||||
plaintext H1 static WWW responses, not reverse proxy or TLS/H2/H3.
|
||||
|
||||
## Cache transform/query policy
|
||||
|
||||
An enabled `bypass_query` is a deliberate policy: `/theme.css?v=...` is a
|
||||
`BYPASS` even when the origin sends `Cache-Control: public`. Turning the bypass
|
||||
off permits a transformed CSS object to follow `MISS -> HIT` and appear in the
|
||||
cache dashboard. An existing transformed HOT row with hit count and TTL is
|
||||
positive evidence of this. This throughput revision does not modify the
|
||||
cache eligibility logic or discard Age/ETag/Cache-Control headers.
|
||||
|
||||
## Verification already possible here
|
||||
|
||||
- **Old/reproducible reference executable only:** 110 passed, 0 failed, 1 skipped
|
||||
(`compression.codec.zstd` skipped because the optional decoder was missing),
|
||||
from live proxy/policy/cache/transform loopback regression. This does **not**
|
||||
exercise the newly modified Rust source.
|
||||
- **Old/reproducible reference executable only:** query bypass enabled gives
|
||||
`BYPASS -> BYPASS`, disabled gives `MISS -> HIT` and a transformed representation;
|
||||
the older Age regression is also reproducible on this old executable.
|
||||
- Source-only invariants: `scripts/tests/rps_hotpath_034_static_test.py` plus
|
||||
existing route/cache/sendfile checks. SQL migration and upgrade scripts pass.
|
||||
- **Not performed:** Rust compilation or newly added Rust unit tests, live
|
||||
behavior of the optimized binary, HTTP/2 live TLS, comparative RPS, CPU
|
||||
profiling and long-duration memory/health soak. The environment does not
|
||||
include `cargo`, `rustc` or `wrk`. No post-change RPS gain is claimed.
|
||||
|
||||
## Acceptance commands on a Rust-capable Linux host
|
||||
|
||||
```bash
|
||||
./dev.sh check
|
||||
CARGO_INCREMENTAL=0 ./dev.sh test
|
||||
CARGO_INCREMENTAL=0 cargo build --release --locked
|
||||
python3 scripts/tests/rps_hotpath_034_static_test.py
|
||||
python3 scripts/tests/cache_age_transform_034_live.py \
|
||||
--binary "$(realpath target/release/rust-proxy)" \
|
||||
--output /tmp/rustproxy034-cache-after
|
||||
python3 scripts/performance/regression.py \
|
||||
--binary optimized="$(realpath target/release/rust-proxy)" \
|
||||
--out /tmp/rustproxy034-http-regression
|
||||
```
|
||||
|
||||
Run `scripts/performance/benchmark.py` from the documented reproducible
|
||||
before/after/nginx command in `scripts/performance/README.md`. Compare the
|
||||
unchanged old 0.34.0 binary with the newly compiled source, not a baseline
|
||||
with different feature flags. Record RPS at 1,040 B, 64 KiB, 1 MiB, p95/p99,
|
||||
CPU/request, RSS, connection counts, errors and exact response-body hashes.
|
||||
Use `perf record -g -p <proxy-pid>` during a stable request interval and inspect
|
||||
`perf report --stdio` to identify the next true bottleneck.
|
||||
|
||||
Do not interpret Python origin self-tests, source inspections, or speculative
|
||||
Amdahl models as measured RustProxy throughput.
|
||||
+2
-4
@@ -7,7 +7,6 @@ This is the canonical index for the current project documentation. The authentic
|
||||
- [Project README](../README.md) - product overview, installation entry points and current release notes.
|
||||
- [ROADMAP](../ROADMAP.md) - planned and completed release scope.
|
||||
- [CHANGELOG](../CHANGELOG.md) - user/operator-visible changes by release.
|
||||
- [TODO](TODO.md) - open work only; completed work is not retained as TODO.
|
||||
|
||||
## Functional documentation
|
||||
|
||||
@@ -42,7 +41,6 @@ This is the canonical index for the current project documentation. The authentic
|
||||
- [MOCK_SERVER](MOCK_SERVER.md) - local Python mock origin used by development/integration checks.
|
||||
- [MOCK_AUTH](MOCK_AUTH.md) - local fake Tinyauth peer for development only.
|
||||
- [MEMORY_STRESS](MEMORY_STRESS.md) - sustained-memory audit, offline load simulations, real RustProxy staging stress and RSS measurement procedure.
|
||||
- [TEST_REPORT_0.34.0](TEST_REPORT_0.34.0.md) - 0.34.0 source-stage test evidence, simulated cache retention and required acceptance gates.
|
||||
|
||||
## Technical documentation
|
||||
|
||||
@@ -61,7 +59,7 @@ Completed roadmap history is retained separately as [ROADMAP_HISTORY](ROADMAP_HI
|
||||
|
||||
### Retained audits and historical context
|
||||
|
||||
- [SECURITY_AUDIT](SECURITY_AUDIT.md) and [SECURITY_AUDIT_0.31.0](SECURITY_AUDIT_0.31.0.md) - current security audit material and the 0.31.0 audit.
|
||||
- [SECURITY_AUDIT](SECURITY_AUDIT.md) - security audit material.
|
||||
- [CHANGELOG_TECHNICAL_ARCHIVE](CHANGELOG_TECHNICAL_ARCHIVE.md) and [ROADMAP_HISTORY](ROADMAP_HISTORY.md) - retained long-form technical history and completed milestones.
|
||||
- [ACME_RFC2136](ACME_RFC2136.md) - historical DNS provider details.
|
||||
- [INTEGRATIONS_040](INTEGRATIONS_040.md) and [MANAGEMENT](MANAGEMENT.md) - retained design and integration notes.
|
||||
@@ -72,7 +70,7 @@ The old per-version implementation notes and audits removed from this trimmed so
|
||||
## Maintenance rules
|
||||
|
||||
- Put user/operator-visible release changes in `CHANGELOG.md`.
|
||||
- Put future release scope in `ROADMAP.md` and unresolved work in `docs/TODO.md`.
|
||||
- Put future release scope and unresolved work in `ROADMAP.md`.
|
||||
- Keep detailed implementation evidence in technical documents or version audits, not in CHANGELOG.
|
||||
- When functionality changes, update the functional document, this index, Reader coverage and cross-links in the same change.
|
||||
- Remove obsolete or duplicated documents when they no longer provide historical or technical value; retain useful audits with an explicit historical label.
|
||||
@@ -1,93 +0,0 @@
|
||||
# RustProxy 0.34.0 — poprawki cache, transformacji i kontrola RPS
|
||||
|
||||
Status: **zmodyfikowane źródła, niekompilowane lokalnie** (brak `cargo` i `rustc`). Numer wersji: 0.34.0.
|
||||
|
||||
## Co odtworzono na binarce użytkownika
|
||||
|
||||
Uruchomiono binarkę Linux ELF w izolowanej konfiguracji z lokalnym origin HTTP, z włączonym cache, transformacją CSS, `max_ttl_seconds=86400`, diagnostyką i API administracyjnym. HTTP/1.1 localhost, pełne GET, brak kontaktu z witryną produkcyjną.
|
||||
|
||||
| Odpowiedź | Pierwszy GET | Drugi GET | Liczba pobrań z origin |
|
||||
|---|---|---|---|
|
||||
| `/normal.css` | MISS | HIT | 1 |
|
||||
| `/aged.css` | MISS | MISS | 2 |
|
||||
| `/br.css` | MISS | HIT | 1 |
|
||||
| `/aged-br.css` | MISS | MISS | 2 |
|
||||
| `/private.css` | MISS | MISS | 2 |
|
||||
| `/no-store.css` | MISS | MISS | 2 |
|
||||
|
||||
Zwykły CSS i skompresowany Brotli dostają HIT. Warianty z `Age: 1553483` mają `MISS → MISS` mimo `Cache-Control: public, max-age=2592000`. Przypadki `private` / `no-store` słusznie nie trafiają do cache. Zmiana keep-alive vhosta nie usuwała poprawnie zachowanego obiektu. `expose_only_hits=true` celowo ukrywa nagłówki na MISS.
|
||||
|
||||
## Główna przyczyna i poprawka
|
||||
|
||||
W `src/cache/policy/response.rs` obowiązywał wzór `ttl = min(origin_max_age, local_max_ttl)` oraz `remaining = ttl - corrected_age`. To jest sprzeczne z zadaniem lokalnego limitu: świeżość w origin nadal trwa, ale CDN `Age > local_max_ttl` dawał sztuczne 0 sekund lokalnej świeżości.
|
||||
|
||||
Poprawiony wzór:
|
||||
|
||||
```text
|
||||
corrected_initial_age = max(apparent_age, upstream_age + response_delay)
|
||||
remaining_origin_freshness = max(0, origin_max_age - corrected_initial_age)
|
||||
remaining_local_freshness = min(remaining_origin_freshness, local_max_ttl)
|
||||
entry_fresh_until_age = corrected_initial_age + remaining_local_freshness
|
||||
```
|
||||
|
||||
Przykład z przesłanych nagłówków: wiek upstream 1 553 483 s = prawie 18 dni. Pierwotna świeżość 2 592 000 s (30 dni), więc pozostało około 12 dni w origin. Po naprawie RustProxy będzie mógł przechować świeżą odpowiedź przez lokalny limit (domyślnie 86 400 s = 1 dzień). Nagłówek Age nadal będzie około 1,55 mln sekund i będzie rósł na HIT — to poprawne.
|
||||
|
||||
`Age` nie wynika z `Last-Modified: 2016`. Najczęściej jest przekazany przez upstream/CDN, a stara data `Date` też mogłaby podnieść apparent_age. Rozróżnienie: `Age` = HTTP wiek całkowity; `resident_age_seconds` = pobyt w HOT/COLD.
|
||||
|
||||
## HOT RPS i panel
|
||||
|
||||
1. HOT HIT nie wyjmuje już i nie wkłada od nowa wpisu oraz indeksu `Vary`, tylko aktualizuje licznik HIT i kolejność LRU w miejscu. Zmiana ma zmniejszyć alokacje i krytyczną sekcję; **zysk RPS jeszcze niezmierzony**.
|
||||
2. Panel Cache ma osobne kolumny **Age** i **Czas w RustProxy**. API `dashboard.objects[]` dodaje `resident_age_seconds` także dla COLD. Pozostałe nazwy pól nie zostały zmienione.
|
||||
3. Licznik `x-cache-hits` dotyczy konkretnego aktualnego wpisu. Gdy obiekt zostanie wyparte z HOT (limit wpisów/pamięć), usunięty lub zastąpiony po MISS, licznik restartuje. W próbce użytkownika TTL wynosił około 604 tys. s (7 dni), więc późniejszy MISS po ~4 minutach **nie wynikał z normalnego wygaśnięcia TTL**. Konkretnej przyczyny epizodu nie można ustalić bez trendów `evictions`, `purges`, `hot_demotions`, `cold_hits`, kluczy/Vary oraz zmian konfiguracji.
|
||||
4. Brak `x-cache` na MISS jest oczekiwany, gdy włączono **nagłówki diagnostyczne tylko na HIT**. Brak wpisu w API może mieć inne przyczyny (np. cache-control `private`, `no-store`, brak polityki origin, limit obiektu) i nie należy ich obchodzić na siłę.
|
||||
5. Nie zmieniano uprawnień, `private`/`no-store`, kluczy Vary, TTL po stronie origin ani zasad selektywnej invalidacji.
|
||||
|
||||
## Wynik symulacji TTL
|
||||
|
||||
Wykonano **100 000 deterministycznych scenariuszy**. Dla każdego sprawdzono: po naprawie lokalna świeżość nie przekracza `local_max_ttl` ani świeżości pozostałej w origin.
|
||||
|
||||
| Scenariusz | Stary pozostały TTL | Po poprawce (symulacja) |
|
||||
|---|---:|---:|
|
||||
| Age = 1 553 483, local cap 1 dzień | 0 s | 86 400 s |
|
||||
| Age = 1 553 483, local cap 7 dni | 0 s | 604 800 s |
|
||||
| Origin kończy świeżość za 10 s | 0 s | 10 s |
|
||||
| Origin jest już nieświeży | 0 s | 0 s |
|
||||
|
||||
W symulacji 33 387 / 100 000 losowych przypadków stara polityka błędnie dawała zero zamiast dodatniego TTL. **To nie jest procent regresji w realnym ruchu**, a wynik losowania parametrów testowych.
|
||||
|
||||
## Nginx — rzeczywiste pomiary starej binarki
|
||||
|
||||
Archiwalne wyniki samej binarki `rust-proxy 0.34.0` na tym środowisku (Linux, 4 vCPU, HTTP/1.1 loopback, ApacheBench, 64 połączenia, cache OFF, 3 powtórzenia), z nginx 1.26.3. Brak miarodajnego benchmarku **nowych źródeł** przed ponowną kompilacją.
|
||||
|
||||
| Endpoint | RustProxy RPS (mediana) | nginx RPS (mediana) | RustProxy / nginx |
|
||||
|---|---:|---:|---:|
|
||||
| proxy 1 KiB | 10,544 | 58,709 | 18.0% |
|
||||
| proxy 64 KiB | 7,893 | 17,358 | 45.5% |
|
||||
| proxy 1 MiB | 1,010 | 1,666 | 60.6% |
|
||||
| www 1 KiB | 11,984 | 71,318 | 16.8% |
|
||||
| www 64 KiB | 9,224 | 26,600 | 34.7% |
|
||||
| www 1 MiB | 1,264 | 1,098 | 115.2% |
|
||||
|
||||
Ograniczenia porównania: ApacheBench może limitować klienta; wyniki nie reprezentują TLS/H2/H3 ani ruchu produkcyjnego. **Nie wolno tych RPS przypisywać nowej rewizji.**
|
||||
|
||||
## Zestaw testów dla nowej kompilacji
|
||||
|
||||
```bash
|
||||
CARGO_INCREMENTAL=0 cargo test --locked --lib
|
||||
CARGO_INCREMENTAL=0 cargo build --release --locked
|
||||
python3 scripts/tests/cache_age_policy_034_model.py
|
||||
python3 scripts/tests/cache_age_transform_034_live.py --binary target/release/rust-proxy --output /tmp/cache-test-034
|
||||
python3 scripts/tests/cache_scope_034_static_test.py
|
||||
python3 scripts/tests/proxy_hotpath_034_static_test.py
|
||||
python3 scripts/tests/sendfile_034_static_test.py
|
||||
```
|
||||
|
||||
Do testów jednostkowych dodano Age, transformowane CSS/HOT, democję do COLD i odczyt COLD po restarcie, logikę LRU/HIT oraz kontrolę zakazów `private`/`no-store`. Test lokalny sprawdza CSS/Brotli, origin count, API, keep-alive config i ukryte nagłówki. Test po kompilacji bez przełącznika `--expect-old-age-bug` ma wykazać `MISS → HIT` dla Age/Brotli. Na starej binarce odtwarzanie regresji przechodzi z `--expect-old-age-bug`.
|
||||
|
||||
Kontrole wykonane na źródłach: źródłowe testy cache scope, proxy hotpath, sendfile, składnia JS, parsowanie tłumaczeń. **Nie uruchomiono testów Rust ani benchmarków poprawionej binarki** — środowisko nie ma `cargo/rustc`.
|
||||
|
||||
## Następny pomiar po kompilacji
|
||||
|
||||
Porównać tę rewizję 0.34.0 z poprzednią 0.34.0 oraz nginx dla 1 KiB / 64 KiB / 1 MiB; osobno cache OFF, HOT HIT i transformacja/Brotli. Raportować RPS, p95/p99, procesor, RSS, nowe TCP, `cache.evictions` i `cache.purges`; nie modyfikować ustawień cache podczas benchmarku.
|
||||
|
||||
Uwaga: `curl -I -X GET` miesza zachowanie HEAD i GET; do testów reprezentacji zalecane `curl -sS -D - -o /dev/null URL`.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Historical roadmap archive
|
||||
|
||||
> Archived during the RustProxy 0.25 documentation cleanup. This file preserves completed historical roadmap entries. The active roadmap is [`../ROADMAP.md`](../ROADMAP.md); unresolved work is tracked in [`TODO.md`](TODO.md).
|
||||
> Archived during the RustProxy 0.25 documentation cleanup. This file preserves completed historical roadmap entries. The active roadmap is [`../ROADMAP.md`](../ROADMAP.md).
|
||||
|
||||
# RustProxy roadmap
|
||||
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
# RustProxy 0.31.0 - API, panel and HTTP cache-maintenance security review
|
||||
|
||||
Scope: source snapshot supplied for the v0.31.0 maintenance follow-up, reviewed
|
||||
2026-10-10. This is a **code/contract review with offline regressions**, not a
|
||||
penetration test or proof of production safety. The supplied archive has no
|
||||
`.git` directory; the review does not establish parity with remote `master`.
|
||||
|
||||
## Interfaces and trust boundaries
|
||||
|
||||
| Surface | Exposure | Authentication and mutation rules | Assessment |
|
||||
|---|---|---|---|
|
||||
| Public administration bootstrap (`/`, login JS/CSS, `/api/auth/status`, setup/login POST) | Login/public administration listener | Host/origin validation; request marker for unsafe bootstrap actions; one-time setup capability and login throttling | Source-reviewed; live browser flow pending |
|
||||
| Private panel HTML, JS, documentation, OpenAPI | Administration listener | Administrator session; `Cache-Control: no-store`; CSP, frame and MIME protections | Source and anonymous-response test reviewed; runtime pending |
|
||||
| `/api/status`, `/api/system-info`, `/api/traffic`, `/api/cache`, logs, config, preferences, audit | Administration listener | Administrator cookie session; sensitive reads not on anonymous allowlist | Source and regression tests added; runtime pending |
|
||||
| `/api/events`, live-log SSE | Administration listener | Session on subscribe and repeated validity checks; bounded viewers; heartbeat/disconnect cleanup | Source-reviewed; reconnect/load pending |
|
||||
| State-changing admin APIs (including cache/config/preferences) | Administration listener | Valid session, Origin/Host, `X-RustProxy-Request: 1`, CSRF token; audit on cache commands | Source and regression tests added; runtime pending |
|
||||
| Certificate API (`/api/certificates/{id}/reload`, deploy) | Administration listener | Narrow-scoped Key ID + Bearer grant; no general read access; HTTPS required remotely; origin validation when supplied | Source-reviewed; token rotation/revocation live tests pending |
|
||||
| Public `BAN`/`PURGE` | Configured public vhost listener | **No admin session**: separate explicit IP/CIDR allowlist (empty by default), matched listener/vhost/ACL and optional rate limit | Hardened below; reverse/HTTP tests pending |
|
||||
| Separate Prometheus metrics exporter | Distinct optional listener | Configured IP CIDRs and bearer-token policy; not the admin session | Unchanged; deployment exposure audit pending |
|
||||
|
||||
## Findings addressed in this follow-up
|
||||
|
||||
1. **E0716 / compilation defect (high functional impact).** `error()` accepts
|
||||
`&'static str`; a reference to `format!(...)` was a temporary. Invalid
|
||||
selector errors now log the detailed reason server-side and return a fixed,
|
||||
non-sensitive message with status 422. This removes the reported lifetime
|
||||
violation; the Rust compiler must still verify the entire crate.
|
||||
2. **Cache protocol-scope confusion (medium).** Path-only `BAN`/`PURGE`
|
||||
selectors could affect representations of the same host/path across both
|
||||
HTTP and HTTPS. Targets are now built with the trusted external scheme,
|
||||
validated to `http`/`https`, and the matched Host/vhost. IPv6 literal Hosts
|
||||
are bracketed for the URL parser. Other tenants, schemes and hosts remain
|
||||
outside the selector.
|
||||
3. **Unbounded blocking maintenance work (medium availability risk).** Cache
|
||||
invalidation may scan hot/cold entries. Both public HTTP and authenticated
|
||||
admin cache purge/selective invalidation share two maintenance slots and
|
||||
execute scans with `spawn_blocking`. Bursts receive 429 instead of piling
|
||||
up unbounded blocking work on Tokio core threads. The permit stays with the
|
||||
worker after a client disconnects. This is a concurrency bound, **not** a
|
||||
per-client rate quota or a hard time limit for disk work.
|
||||
4. **Ambiguous maintenance input (medium).** Duplicate TTL/confirmation or
|
||||
`Content-Length` headers, transfer-encoded bodies, nonempty bodies and
|
||||
upgrade requests are rejected; `Content-Length: 0` remains compatible with
|
||||
CLI clients. TTL is limited to 1..604800 s. Broad `BAN /` requires the
|
||||
explicit confirmation header; `PURGE` cannot use BAN-only controls.
|
||||
5. **Audit ordering (low/medium).** Removal of an administrator-created BAN
|
||||
rule now records the audit entry before mutating state. Unavailable audit
|
||||
storage blocks the mutation rather than allowing an unaudited removal.
|
||||
6. **Operator safety/usability (low).** An EN/PL help popover beside the HTTP
|
||||
allowlist gives exact `curl` syntax, IP-only authentication semantics and
|
||||
reverse-proxy identity requirements. No new global config variable is added.
|
||||
|
||||
## Existing protections checked in source
|
||||
|
||||
- Admin router's exact anonymous method/path allowlist contains only the
|
||||
expected authentication bootstrap APIs; private docs and assets remain
|
||||
session-protected. Request guard rejects duplicate Host/Origin and checks
|
||||
browser `Sec-Fetch-Site` cross-site signals.
|
||||
- Mutation guard uses an Origin/Host match, per-session CSRF comparison and
|
||||
`X-RustProxy-Request` marker; cookie sessions are HttpOnly/SameSite=Strict
|
||||
and use Secure under HTTPS/trusted forwarded protocol.
|
||||
- Secure response policy includes CSP, `X-Frame-Options: DENY`,
|
||||
`X-Content-Type-Options: nosniff`, referrer restrictions and no-store for
|
||||
private material. A static scan checked 27 first-party browser JS files for
|
||||
direct unsafe HTML injection and dynamic `eval`/`Function` sinks; this scan
|
||||
**cannot establish that the entire DOM is XSS-free**.
|
||||
- IP trust comes from the transport peer or a validated forwarding chain only
|
||||
when the peer is configured trusted. An untrusted client-supplied XFF does
|
||||
not independently grant BAN/PURGE permission. The cache allowlist rejects
|
||||
`/0`, invalid CIDRs, duplicates and more than 32 entries. Requests are never
|
||||
forwarded upstream.
|
||||
- Certificate API tokens have explicit operation/ID grants and cannot use the
|
||||
general admin read API; Bearer grant operations are intentionally separate
|
||||
from cookie + CSRF authentication.
|
||||
|
||||
## HTTP maintenance CLI (allowlisted client only)
|
||||
|
||||
```sh
|
||||
# One URL, including the query string: 200 with JSON result.
|
||||
curl -i -X PURGE 'https://site.example/assets/app.js?v=3'
|
||||
|
||||
# Lazily ban previously cached objects under this path: 202 with JSON result.
|
||||
curl -i -X BAN 'https://site.example/assets/'
|
||||
|
||||
# Entire host: explicit opt-in confirmation header.
|
||||
curl -i -X BAN 'https://site.example/' -H 'X-RustProxy-Confirm-Wide: 1'
|
||||
|
||||
# Optional BAN expiration (30 seconds).
|
||||
curl -i -X BAN 'https://site.example/assets/' -H 'X-RustProxy-Ban-Ttl: 30'
|
||||
```
|
||||
|
||||
Use **Settings -> Cache -> HTTP BAN / PURGE IP allowlist** to grant the
|
||||
caller IP/CIDR. This HTTP interface does not accept admin cookies/tokens as an
|
||||
alternative to the allowlist. The domain, Host header, external scheme and
|
||||
listener must select the intended vhost. Behind a reverse proxy, forward the
|
||||
non-standard methods untouched and **replace**, rather than append untrusted,
|
||||
XFF/Forwarded headers at the edge. Never add the entire reverse-proxy subnet
|
||||
as a maintenance client merely because it is trusted for forwarding.
|
||||
|
||||
## Release security test plan (requires compiled RustProxy)
|
||||
|
||||
1. On the deployment host, run `cargo fmt --all -- --check`,
|
||||
`./dev.sh check`, `./dev.sh test`, and `./dev.sh release`. Address any
|
||||
compiler/regression errors before installation. `cargo audit` or an
|
||||
equivalent advisory scan requires an up-to-date advisory DB and was not run
|
||||
in this offline environment.
|
||||
2. Start a disposable instance with a populated cache under both HTTP and
|
||||
HTTPS for the same vhost/URL. Confirm `PURGE` returns 200 and drops the
|
||||
exact query variant only; `BAN` returns 202 and invalidates only **older**
|
||||
entries on the URL prefix. Confirm the *other* scheme and vhost stay hot.
|
||||
3. From a denied IP, expect 403 on `BAN` and `PURGE`. With an empty allowlist,
|
||||
all clients must be denied. Send duplicate TTL/confirmation/Content-Length,
|
||||
TE/body and `BAN /` without confirmation: expect 400. On a trusted-proxy
|
||||
hop with missing real client identity, expect 403. Test both IPv4 and IPv6.
|
||||
4. From an allowed IP, send 10 concurrent broad maintenance requests; confirm
|
||||
that only two workers run simultaneously and excess return 429. Verify
|
||||
server CPU/IO and audit entries while the cold tier is populated.
|
||||
5. Send GET requests without admin cookies to `/api/config`, `/api/status`,
|
||||
`/api/system-info`, `/api/events`, `/api/logs`, `/docs/openapi.json` and
|
||||
`/assets/app.js`; expect 401 and protective headers. With valid cookie but
|
||||
missing/bad Origin, marker or CSRF on PUT/POST/DELETE, expect 403. Test
|
||||
logout and password/session invalidation, live SSE reconnect/expiry.
|
||||
6. Test the Certificate API with no grant, a valid certificate-specific grant,
|
||||
revoked token, HTTP remote peer, and misuse against `/api/config`/logs.
|
||||
Check errors for token disclosure and audit coverage.
|
||||
7. Test actual browser startup, CSP console, output escaping and form save
|
||||
with malicious HTML strings; inspect reverse-proxy Host/Origin forwarding.
|
||||
Prefer HTTPS administration with a strict `allowed_hosts` list.
|
||||
|
||||
## Residual risk / non-claims
|
||||
|
||||
- **Not executed in this environment:** Cargo format/check/tests/release,
|
||||
actual Rust listener requests, HTTP/2/3, fuzzing, penetration tests,
|
||||
session revocation under load, proxy deployment configuration, dependency
|
||||
CVE scanning and long-running cold-cache resource measurements.
|
||||
- Proxy trust configuration is security-critical. A misconfigured edge that
|
||||
accepts forged forwarded client addresses can let external users impersonate
|
||||
an allowlisted IP. Restrict direct listener reachability and sanitize headers.
|
||||
- A public IP allowlist is not user authentication; anyone using an allowed
|
||||
IP/network can issue destructive maintenance commands. Use narrow CIDRs and
|
||||
optionally enforce additional controls on the edge (mTLS, firewall).
|
||||
- The two-worker concurrency bound mitigates bursts, not all denial-of-service
|
||||
scenarios: configured cache size and storage latency still determine scan
|
||||
cost. Per-IP quotas are not provided here.
|
||||
- Proxy protocol path, certificate verification, arbitrary upstream code,
|
||||
all application content and third-party packages are **not certified safe**
|
||||
by this audit. Production acceptance remains open until the runtime test
|
||||
plan is completed.
|
||||
+1
-1
@@ -350,7 +350,7 @@ The adjacent **info (`i`) popover** in Settings → Cache shows ready-to-copy
|
||||
`curl -X PURGE` and `curl -X BAN` examples in English and Polish, including
|
||||
the explicit `BAN /` confirmation. These public methods authorize by the
|
||||
source IP/CIDR only; they do not accept a browser admin session as a bypass.
|
||||
See [0.31.0 security audit](SECURITY_AUDIT_0.31.0.md) for reverse-proxy trust and tests.
|
||||
See [security audit](SECURITY_AUDIT.md) for reverse-proxy trust and tests.
|
||||
|
||||
## Static-response transformation
|
||||
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
# RustProxy 0.34.0 verification report
|
||||
|
||||
## Acceptance status
|
||||
|
||||
**Source candidate, not verified production release.** Rust (`cargo`, `rustc`) is
|
||||
not installed in this execution environment, and the runtime has no access to
|
||||
its dependency registry. Consequently the modified Rust source was **not
|
||||
compiled**, the new Rust tests were **not executed**, and no 0.34.0 RustProxy
|
||||
binary or live throughput figures are supplied. Source inspection and simulated
|
||||
UI/cache tests do not close the runtime acceptance gate.
|
||||
|
||||
## DEV 1 KiB counters follow-up (source remains uncompiled)
|
||||
|
||||
The newly supplied binary `rust-proxy(1)` identifies as 0.34.0 and has
|
||||
SHA-256 `1a84c58b20945bba9fdd1bc1a44d9e9d6ac7659d714343a46a1a8511a78331fa`.
|
||||
It completed **151 passing real HTTP tests, 0 failures, 1 optional zstd
|
||||
skip**. These tests were run on the supplied binary, not on the subsequently
|
||||
modified source. The latest source moves per-domain cache statistics outside
|
||||
HOT store mutexes and lazily allocates analytics samples only after a queue
|
||||
permit is available. Histogram completion now increments one exclusive
|
||||
latency bin instead of up to 11 cumulative bins; both Prometheus exporters
|
||||
convert them to the same cumulative format at scrape time. Static
|
||||
source/SQL/upgrade checks pass; **Rust compile,
|
||||
Rust unit tests and live new-source RPS remain OPEN**. See
|
||||
`docs/PROXY_1KB_COUNTERS_DEV_0.34.0.md` and the separate evidence archive.
|
||||
|
||||
## DEV 1 KiB reverse-proxy follow-up (October 2026; source only)
|
||||
|
||||
The supplied **old executable** passed loopback HTTP/1.1 1,040-byte content
|
||||
checks and ApacheBench 12,000-request samples. The configuration trial
|
||||
`max_idle_per_host=4 -> 64` increased short-run RPS, but shared-CPU noise
|
||||
precludes a stable nginx-parity percentage. The edited sources now use 64 by
|
||||
default for absent/new keepalive fields and reduce exclusive-Drop mutex/clone
|
||||
and absent-cookie overhead. See `docs/PROXY_1KB_HOTPATH_DEV_0.34.0.md`.
|
||||
**No newly modified binary was built or run.** Rust unit tests, runtime
|
||||
regressions, long soaks and post-edit code A/B are still required.
|
||||
|
||||
Current DEV source checks performed on the modified tree:
|
||||
|
||||
| Check | Result | Scope |
|
||||
|---|---|---|
|
||||
| `./dev.sh doctor` | PASS | Source preflight; reports `cargo` and `rustc` absent |
|
||||
| `scripts/tests/rps_hotpath_034_static_test.py` | PASS | Guardrails, defaults, retained routing/security and source-size limits only |
|
||||
| `scripts/tests/proxy_hotpath_034_static_test.py` | PASS | Routing and telemetry source guards only |
|
||||
| `scripts/tests/cache_scope_034_static_test.py`, `cache_hot_034_static_test.py`, `sendfile_034_static_test.py` | PASS | Cache and WWW source guards only |
|
||||
| `scripts/tests/dashboard_presets_sql_test.py` | PASS | SQLite migrations and preset constraints |
|
||||
| `scripts/tests/upgrade_performance_test.py` | PASS | Disposable upgrade/preservation/locking test |
|
||||
| `scripts/tests/sendfile_034_panel_browser_test.py` | PASS, 2 widths | Simulated Chromium admin API, not live Rust |
|
||||
| `scripts/tests/vhost_backend_popup_0337_browser_test.py` | PASS, 4 widths | Simulated Chromium admin API, not live Rust |
|
||||
| Node `--check` (vhost editor), Python `compileall`, JSON parser (OpenAPI) | PASS | Syntax only |
|
||||
| `./dev.sh check`, `./dev.sh test` | BLOCKED, exit 127 | Rust toolchain missing; no Rust unit/build acceptance |
|
||||
| Old supplied binary, `ab`/nginx configuration A/B | COMPLETED, DIAGNOSTIC | 1,040-byte H1 payload; shared-CPU and no new code exercised |
|
||||
|
||||
|
||||
## Later 0.34.0 reverse-proxy optimization revision
|
||||
|
||||
The refreshed source archive additionally reuses immutable route handles,
|
||||
limits lazy host wildcard allocation, fast-paths one-backend non-cookie LB,
|
||||
atomically caches healthy state and exchange scalar log fields, shares
|
||||
compiled per-member health policies across leases, and avoids
|
||||
unneeded fallback/retry header clones and empty-subscriber broadcast clones.
|
||||
The existing cache/sendfile work and all panel controls remain. Added Rust
|
||||
regression cases and the source-only hot-path guard are **not a substitute**
|
||||
for compilation and live HTTP, health, capacity, sticky, and H2 retry tests.
|
||||
No RPS improvement, corrected nginx gap, or full functional parity has been
|
||||
measured for this new revision. Details: `docs/PROXY_HOTPATH_0.34.0.md`.
|
||||
|
||||
## RPS-focused follow-up source revision (not yet compiled)
|
||||
|
||||
See `docs/PROXY_RPS_0.34.0.md` for detailed changes in request IDs, validated
|
||||
upstream URI construction, LB scheduling, probes, streaming byte accounting,
|
||||
unchanged pool generation lifetimes and log-history ownership. Source-only invariants pass.
|
||||
A live test of the PREVIOUS executable (not this source revision) completed
|
||||
110 passing cases, 0 failing cases and 1 skipped optional zstd decoder in the
|
||||
proxy/policy/cache/transform harness. The old executable also confirms query-
|
||||
string bypass is deliberate and reproduces the older Age/TTL bug. The new
|
||||
binary has NOT been built or tested and its RPS gain is unknown.
|
||||
|
||||
## HOT HIT follow-up source revision (not yet compiled)
|
||||
|
||||
See `docs/CACHE_HOTPATH_0.34.0.md`. Response header cloning has moved outside
|
||||
the global HOT lock, and redundant bounded stats key allocation, empty BAN
|
||||
checks and full HOT TTL sweeps were removed. A new Rust concurrency/regression
|
||||
suite and an ignored microbenchmark have been added. Existing COLD and Vary
|
||||
behavior, exact LRU and cache semantics remain. This source has not been built
|
||||
or run under Rust in this environment; no RPS gain is claimed.
|
||||
|
||||
## Changes and correctness boundaries
|
||||
|
||||
- Operational per-vhost saves (upstream keepalive/recycling, connection limits,
|
||||
load-balancing/health policy, timeouts, request gate and diagnostics) retain
|
||||
HOT and COLD entries. Backend origin, routing, content, cache and transform
|
||||
changes retain conservative invalidation. Vhost-specific changes purge only
|
||||
that vhost; global cache, transform, forwarding, headers and Web Service or
|
||||
PHP-FPM profile changes may conservatively purge all tiers.
|
||||
- Scope-specific cold-cache records persist an origin/content compatibility
|
||||
fingerprint. COLD startup rejects stale scope records and deletes their cache
|
||||
artifacts. Legacy COLD metadata defaults its fingerprint to zero; production
|
||||
vhosts have a nonzero SHA-derived fingerprint. A one-time cold refill can
|
||||
occur on the 0.33.8 -> 0.34.0 upgrade because the global identity changed.
|
||||
- A separate administrative configuration revision acts as a cache-ticket
|
||||
publication barrier: a request bound to a pre-save routing snapshot cannot
|
||||
create a new cache ticket after the save. The data revision stays stable for
|
||||
operational-only saves, so previously cached objects remain available.
|
||||
- Reverse-proxy response-body byte metering captures the backend lease once at
|
||||
wrapper creation instead of acquiring a backend-selection mutex per DATA
|
||||
frame. Request-body accounting, error paths and exchange ownership remain.
|
||||
- An **opt-in `sendfile: auto`** setting is wired through Web Service serde,
|
||||
compiled config, the Delivery UI and documented API. The Linux HTTP/1.1
|
||||
TCP transport adapter calls `libc::sendfile` for eligible static files;
|
||||
cache-enabled, ranged, compressed, transformed and TLS/H2/H3 paths retain
|
||||
existing streaming. It is **disabled by default** and must pass native
|
||||
compilation, correctness, transport and load tests before use. No persisted
|
||||
SQLite schema migration or LB UI change is required.
|
||||
|
||||
## Local verification
|
||||
|
||||
| Check | Outcome | Coverage / caveat |
|
||||
|---|---|---|
|
||||
| `./dev.sh doctor` | PASS | Project source layout and tool inventory |
|
||||
| SQL migration/preset test | PASS | All ten migrations re-applied, preset access and limits |
|
||||
| Upgrade test | PASS | Stop/lock guards, SQLite/.env/language preservation, symlink rejection |
|
||||
| Python compileall | PASS | Python syntax only |
|
||||
| Vhost LB/Cache Chromium test | PASS | Simulated browser quick-view, 320/375/768/1440px |
|
||||
| Other Chromium scripts | 15 PASS | Simulated API/CSS/JS, not a live Rust server |
|
||||
| Sidebar navigation Chromium test | INCOMPLETE | Execution time budget; not counted as a pass |
|
||||
| Three Node dashboard scripts | PASS | Simulated UI logic |
|
||||
| `./dev.sh check` and `./dev.sh test` | BLOCKED | `cargo`/`rustc` unavailable |
|
||||
| Rust unit/integration tests (including new scoped COLD regression) | NOT RUN | Requires Rust toolchain |
|
||||
| Live HTTP/protocol/LB regressions | NOT RUN | Requires built 0.34.0 binary |
|
||||
| 0.33.8 / 0.34.0 / nginx paired RPS benchmarks | NOT RUN | No verified 0.34.0 binary |
|
||||
| Zero-copy sendfile WWW setting | SOURCE IMPLEMENTED / NOT RUNTIME VERIFIED | Linux plain H1 static GET only, `off` by default |
|
||||
| Sendfile editor Chromium save/reload | PASS | 375/1440px, simulated admin API, off -> auto -> off |
|
||||
| Sendfile Rust adapter + fallback test code | ADDED / NOT RUN | TCP marker replacement, persistent socket, HEAD, range, conditional; compiler unavailable |
|
||||
| Amdahl sendfile/nginx sensitivity study | PASS MODEL | No runtime evidence for simulated improvements |
|
||||
|
||||
The 0.33.8 report in the supplied evidence archive recorded **706 passing
|
||||
Rust tests**, **152 HTTP assertions**, **111 transport assertions** and
|
||||
**17 LB assertions**. Those are *prior-version* results and are **not evidence
|
||||
that 0.34.0 passes**. This report never transfers previous pass counts.
|
||||
|
||||
## Synthetic cache-retention scenario (not an RPS measurement)
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
python3 scripts/performance/simulate_cache_034.py
|
||||
```
|
||||
|
||||
Assume 20 vhosts, 100 HOT + 100 COLD objects each, all public/cacheable with
|
||||
infinite TTL and adequate cache capacity; 1,000 configuration saves (800
|
||||
operational-only, 200 content changes to one vhost), followed by 500 uniformly
|
||||
random GETs per save. A miss immediately reloads an object into its original
|
||||
tier. No networking, filesystem, CPU timing, origin delay or concurrency is
|
||||
modeled. Results with seed 3400:
|
||||
|
||||
| Synthetic metric | Old global purge | 0.34.0 scoped retention |
|
||||
|---|---:|---:|
|
||||
| Object invalidations | 473,540 | 37,300 |
|
||||
| GET misses | 470,006 | 37,069 |
|
||||
| GET hits | 29,994 | 462,931 |
|
||||
| HIT ratio | 5.999% | 92.586% |
|
||||
|
||||
The modeled change yields **92.123% fewer invalidations**, **92.113% fewer
|
||||
misses**, and **+86.587 percentage points** of synthetic cache HIT ratio.
|
||||
**Measured 0.34.0 requests/s gain: unknown.** These values must not be
|
||||
presented as a real speedup or a projection against nginx.
|
||||
|
||||
## Previously measured comparison (0.33.8, not this candidate)
|
||||
|
||||
From the attached 0.33.8 evidence, five paired benchmark runs with cache OFF,
|
||||
median RPS:
|
||||
|
||||
| Mode / payload | 0.33.8 RPS | nginx RPS | 0.33.8 / nginx |
|
||||
|---|---:|---:|---:|
|
||||
| Reverse proxy 1,040 B | 29,334.12 | 170,610.16 | 17.19% |
|
||||
| Reverse proxy 64 KiB | 20,735.07 | 35,393.29 | 58.58% |
|
||||
| Reverse proxy 1 MiB | 4,461.19 | 2,730.23 | 163.40% |
|
||||
| WWW 1,040 B | 24,906.20 | 351,605.13 | 7.08% |
|
||||
| WWW 64 KiB | 20,856.67 | 203,709.91 | 10.24% |
|
||||
| WWW 1 MiB | 2,833.71 | 12,611.49 | 22.47% |
|
||||
|
||||
The 0.33.8 1 MiB proxy path was **2.77% slower than rebuilt 0.33.7**.
|
||||
Removing a per-response-frame mutex is a performance candidate but is **not**
|
||||
a measured correction of that regression. No throughput gains are attributed
|
||||
here to the new source changes.
|
||||
|
||||
## Hypothetical 0.34.0/nginx gap sensitivity (NOT MEASURED)
|
||||
|
||||
Run `python3 scripts/performance/simulate_nginx_gap_034.py`. Using the **real
|
||||
0.33.8 measured medians** above, assume zero-copy removes 50% of the
|
||||
read/copy part of per-request cost; that part hypothetically accounts for
|
||||
20/40/60% of *total* cost for eligible static files. Model **1/3/5%** total
|
||||
service-time savings from removed proxy-frame mutexes, independently.
|
||||
Amdahl: `RPS_sim = RPS_0.33.8 / (1 - hypothetical_fraction_saved)`. The
|
||||
result is a sensitivity calculation, **not a prediction or 0.34.0 benchmark**.
|
||||
|
||||
| Workload | Measured 0.33.8/nginx | Modeled low | Modeled middle | Modeled high |
|
||||
|---|---:|---:|---:|---:|
|
||||
| Proxy 1040 B | 17.19% | 17.37% | 17.73% | 18.10% |
|
||||
| Proxy 64 KiB | 58.58% | 59.18% | 60.40% | 61.67% |
|
||||
| Proxy 1 MiB | 163.40% | 165.05% | 168.45% | 172.00% |
|
||||
| WWW 1040 B | 7.08% | 7.08% | 7.08% | 7.08% |
|
||||
| WWW 64 KiB | 10.24% | 11.38% | 12.80% | 14.63% |
|
||||
| WWW 1 MiB | 22.47% | 24.97% | 28.09% | 32.10% |
|
||||
|
||||
These ratios apply only to eligible Linux plain HTTP/1.1 static workloads
|
||||
with cache OFF. TLS, HTTP/2, HTTP/3, ranged, compressed and object-cached
|
||||
responses are **not** accelerated by this bridge. Bandwidth, contention,
|
||||
queueing, realistic filesystem and actual CPU profiles are not modeled.
|
||||
Even the favorable modeled 1 MiB case remains ~3.1x slower than nginx WWW;
|
||||
small WWW remains ~14.1x slower. Proxy 1 MiB already exceeds nginx in the
|
||||
**prior baseline**, not as a demonstrated new 0.34.0 improvement.
|
||||
|
||||
## Required rollout gates
|
||||
|
||||
1. On a trusted Linux build worker with the pinned Rust toolchain, run
|
||||
`./dev.sh check`, `CARGO_INCREMENTAL=0 ./dev.sh test`, and a locked release
|
||||
build. Resolve any compiler/test issues before a deployment.
|
||||
2. Follow `scripts/performance/README.md` to run `regression.py`,
|
||||
`transport_regression.py`, `lb_regression.py`, and
|
||||
`concurrency_regression.py` against verified 0.33.8 and 0.34.0 binaries.
|
||||
3. Exercise administration saves during simultaneous requests: save keepalive
|
||||
values and confirm unchanged HOT/COLD entries and hashes; change one backend
|
||||
origin and confirm only its scope is purged, both before/after restart. Check
|
||||
that old snapshots cannot recreate invalidated objects.
|
||||
4. Run the five-repeat, 15-second paired benchmark against 0.33.8, 0.34.0 and
|
||||
nginx on the *same host and affinity*, including large proxy responses, CPU,
|
||||
RSS, resource limits and response correctness.
|
||||
5. Compile the source-level Linux `sendfile` bridge and validate on a running
|
||||
RustProxy process: plain HTTP/1 sendfile path, headers/body exactness,
|
||||
keepalive/pipelining, slow clients, cancellation, HEAD, range, cache,
|
||||
compression, TLS/H2/H3 fallback, symlink security and observability. Do
|
||||
**not** activate Auto on production before that gate passes.
|
||||
6. Complete a mixed-traffic soak and staged rollback rehearsal. Until then,
|
||||
do not describe 0.34.0 as production-accepted.
|
||||
@@ -1,19 +0,0 @@
|
||||
# RustProxy TODO
|
||||
|
||||
This file contains open work that remains after the current RustProxy 0.25 Web Server implementation.
|
||||
|
||||
## Web Server follow-up
|
||||
|
||||
- Multipart byte-range responses if required; current single-range/If-Range support remains the default scope.
|
||||
- Named locations only if they can reuse the existing vhost/location router rather than introduce a second router.
|
||||
- Optional PHP `PATH_INFO` script/path splitting after a confinement-safe rule is defined.
|
||||
- Additional FastCGI observability (bounded active/error/latency counters) without per-script labels.
|
||||
- A race-resistant non-Unix filesystem confinement implementation before enabling the Web Server on non-Unix targets.
|
||||
|
||||
## Release acceptance
|
||||
|
||||
- Run `./dev.sh fmt`, `./dev.sh check` and `./dev.sh test` on a host with the stable Rust toolchain.
|
||||
- Run live static-file acceptance over HTTP/1.1, HTTP/2 and HTTP/3, including ACL/auth/custom-error/header behavior.
|
||||
- Run a real PHP-FPM acceptance matrix over a Unix socket and loopback TCP, including `index.php`, front-controller `try_files`, POST bodies, timeouts and source-disclosure failure tests.
|
||||
|
||||
Completed release work belongs in `CHANGELOG.md`; planned version work belongs in `ROADMAP.md`; implementation/audit evidence belongs in the matching audit document.
|
||||
+1
-1
@@ -234,7 +234,7 @@ origin do not expose a selected backend or stale origin timing.
|
||||
| `pool_recycle_seconds` | 3600 | 0 (off) or 60..604800 |
|
||||
| `http1_max_buffer_bytes` | 131072 | 8192..1048576 |
|
||||
|
||||
The new default of 64 applies when this field is absent in a newly created or deserialized vhost; existing saved values (including 4) remain unchanged. Each idle upstream connection consumes a file descriptor and memory, so use a smaller value for resource-constrained deployments with many origins. See `docs/PROXY_1KB_HOTPATH_DEV_0.34.0.md` for the live configuration A/B evidence.
|
||||
The new default of 64 applies when this field is absent in a newly created or deserialized vhost; existing saved values (including 4) remain unchanged. Each idle upstream connection consumes a file descriptor and memory, so use a smaller value for resource-constrained deployments with many origins.
|
||||
|
||||
These configure HTTP idle reuse and TCP keepalive; HTTP/2 also uses pings for active
|
||||
connections, with a 10-second ping timeout and no pinging solely to keep idle
|
||||
|
||||
+1
-3
@@ -134,9 +134,7 @@ does not change the listener protocol configuration.
|
||||
tested against a running 0.34.0 RustProxy binary in the current environment.
|
||||
The included Rust unit/integration test and real-network test cases must pass
|
||||
on a Linux build worker before enabling in production. Sendfile cannot by
|
||||
itself close the small-response gap to nginx. See
|
||||
`docs/TEST_REPORT_0.34.0.md` and
|
||||
`scripts/performance/simulate_nginx_gap_034.py`.
|
||||
itself close the small-response gap to nginx. Live protocol and performance acceptance is still required.
|
||||
|
||||
### Intercept application HTTP errors (0.29.7)
|
||||
|
||||
|
||||
Reference in new issue
Block a user