v0.34.0_4
This commit is contained in:
1 parent
f20c8da718
commit
5910e6e901
21 files changed
+377
-51
No files matched your search
@@ -5,6 +5,20 @@
|
|||||||
|
|
||||||
## 0.34.0
|
## 0.34.0
|
||||||
|
|
||||||
|
- DEV 1 KiB throughput follow-up (OPT3): keep normalized safe routing
|
||||||
|
paths borrowed from an independently retained URI instead of allocating
|
||||||
|
during observation and forwarding; use the original strict percent/decode
|
||||||
|
validator for noncanonical paths. Build untrusted-peer Forwarded headers
|
||||||
|
without intermediate per-hop vectors, avoid rewriting an unchanged client
|
||||||
|
address into per-request log state, fast-path plain `Connection: keep-alive`,
|
||||||
|
encode request-ID hex in a single buffer and update the cache-disabled
|
||||||
|
reason counter directly. ACL, trusted-proxy validation, IPv6 formatting,
|
||||||
|
body streaming, analytics, metrics and defaults remain unchanged.
|
||||||
|
- Add regressions for path validator equivalence, multi-valued hop-by-hop
|
||||||
|
fields and untrusted IPv6 forwarding. User-supplied OPT2-FIX1 binary passed
|
||||||
|
89 targeted live checks. **This newest source has not been compiled or
|
||||||
|
RPS-tested**; see `docs/PROXY_1KB_OPT3_DEV_0.34.0.md`.
|
||||||
|
|
||||||
- DEV 1 KiB accounting follow-up: move per-domain HIT/MISS/BYPASS, reason,
|
- DEV 1 KiB accounting follow-up: move per-domain HIT/MISS/BYPASS, reason,
|
||||||
stored-entry and saved-body statistics outside the HOT cache's shared mutex.
|
stored-entry and saved-body statistics outside the HOT cache's shared mutex.
|
||||||
Publish bounded domain/reason indexes and update counters atomically while
|
Publish bounded domain/reason indexes and update counters atomically while
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
# RustProxy 0.34.0
|
# RustProxy 0.34.0
|
||||||
|
|
||||||
|
## DEV: small-response proxy hot path, OPT3 source candidate
|
||||||
|
|
||||||
|
Seven compatible optimizations reduce path copies and work constructing
|
||||||
|
forwarded headers, log IPs, hop-by-hop headers, request IDs and cache-off
|
||||||
|
reason totals. HTTP/1.1 proxy with a 1,040-byte response remains the first
|
||||||
|
benchmark priority; local Web Server is secondary. No version or policy changes.
|
||||||
|
The **new code is not compiled or RPS-verified** here. See
|
||||||
|
[`docs/PROXY_1KB_OPT3_DEV_0.34.0.md`](docs/PROXY_1KB_OPT3_DEV_0.34.0.md).
|
||||||
|
|
||||||
## DEV: cache counter contention and saturated analytics (source candidate)
|
## DEV: cache counter contention and saturated analytics (source candidate)
|
||||||
|
|
||||||
Per-domain cache outcome/reason accounting now uses bounded immutable indexes
|
Per-domain cache outcome/reason accounting now uses bounded immutable indexes
|
||||||
|
|||||||
+20
-18
@@ -2,11 +2,11 @@
|
|||||||
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
|
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
|
||||||
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
|
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
|
||||||
46bab1d2c2d6e0a80d57a6b232b1ba4c220884b95cd45ddccbafce2aa160cddb AGENTS.md
|
46bab1d2c2d6e0a80d57a6b232b1ba4c220884b95cd45ddccbafce2aa160cddb AGENTS.md
|
||||||
03d0a320df729cc98a119f4296e3b9805aece0c8591fa6b90003df2b58f8608c CHANGELOG.md
|
10f70748d2c070e2b9923b5aa6572347c7977a88f8bb5d4abe532827914bade7 CHANGELOG.md
|
||||||
ca201e3cd3f9842eae0006d0b40a0a72bcd3d4da6e4e5ecaf200d1b9a1390db2 Cargo.lock
|
ca201e3cd3f9842eae0006d0b40a0a72bcd3d4da6e4e5ecaf200d1b9a1390db2 Cargo.lock
|
||||||
fec5f1bae9a59ef1fabd27c5e45d31448dd8f811736eebfa8826b3806a7e3349 Cargo.toml
|
fec5f1bae9a59ef1fabd27c5e45d31448dd8f811736eebfa8826b3806a7e3349 Cargo.toml
|
||||||
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
|
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
|
||||||
5533499655d41f1faaf666dfa630375cf6770dff37f6cd7ef5a4e5bbd58cb258 README.md
|
09234b68d69eb10047396baab4bf5811b37e954ad3f5eaacf90247265d4fbd52 README.md
|
||||||
aee9bbb367e061aeb79916e38d121e7767f6f9d0c16a2dcc4b28f091586a33b1 ROADMAP.md
|
aee9bbb367e061aeb79916e38d121e7767f6f9d0c16a2dcc4b28f091586a33b1 ROADMAP.md
|
||||||
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
|
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
|
||||||
f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
|
f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
|
||||||
@@ -56,8 +56,9 @@ be53a58afbeddbd7deb4b5f6447f2dc74db0327839bd83407c20915df98dcfa0 docs/NOT_FOUND
|
|||||||
e68d5a5cf0991de9308f78afb0264e02979f7463a5f395eb7a6e9ec2bd3a3d54 docs/PHP_FPM.md
|
e68d5a5cf0991de9308f78afb0264e02979f7463a5f395eb7a6e9ec2bd3a3d54 docs/PHP_FPM.md
|
||||||
bc068a176056f5ea4d3b637071e644ed58a2140494c233ef22aa66e9700e3299 docs/PHP_FPM_ISOLATION.md
|
bc068a176056f5ea4d3b637071e644ed58a2140494c233ef22aa66e9700e3299 docs/PHP_FPM_ISOLATION.md
|
||||||
ad6e3f003b820a4f45e30e39577d120a69d167c70910bda4891971759458c3dd docs/PROMETHEUS.md
|
ad6e3f003b820a4f45e30e39577d120a69d167c70910bda4891971759458c3dd docs/PROMETHEUS.md
|
||||||
9a19e57e30b2d2818322fcd5b490380281014e2ed43279c2251f09acbfd23130 docs/PROXY_1KB_COUNTERS_DEV_0.34.0.md
|
401ab1bb2c7f3961742195d81f5daf5c9cf81609a4b175bea1d10d2a4b727082 docs/PROXY_1KB_COUNTERS_DEV_0.34.0.md
|
||||||
f13ee0005d57d2576ccc970745f17069096df4afa23d712ea0f748da85433f7d docs/PROXY_1KB_HOTPATH_DEV_0.34.0.md
|
f13ee0005d57d2576ccc970745f17069096df4afa23d712ea0f748da85433f7d docs/PROXY_1KB_HOTPATH_DEV_0.34.0.md
|
||||||
|
c9bbdd1cd8df02b2a19016b7bbeee1cf55917614ac672f1f94e0a05db7d309ef docs/PROXY_1KB_OPT3_DEV_0.34.0.md
|
||||||
deea89aed410c0619bdbdb12437edc7aff9dca2fec7fdc059e918c649fc36b51 docs/PROXY_HOTPATH_0.34.0.md
|
deea89aed410c0619bdbdb12437edc7aff9dca2fec7fdc059e918c649fc36b51 docs/PROXY_HOTPATH_0.34.0.md
|
||||||
a7d6189b0e8cb854918456e0063101ee77bfb2419a9ba0430eb0e3f8678c0f09 docs/PROXY_RPS_0.34.0.md
|
a7d6189b0e8cb854918456e0063101ee77bfb2419a9ba0430eb0e3f8678c0f09 docs/PROXY_RPS_0.34.0.md
|
||||||
887d1fdcc3b546b024afcd18b1716b8e3a74a3800012bf4bda7a122d2eabe233 docs/README.md
|
887d1fdcc3b546b024afcd18b1716b8e3a74a3800012bf4bda7a122d2eabe233 docs/README.md
|
||||||
@@ -133,7 +134,7 @@ a641bd35400c56f3b68b0a92e97b602d06090f03972ca8f01fa744e90dd97403 scripts/deploy
|
|||||||
2cc658faaa610b21b48942c59b955525539ad4503fd60f2a49c7e626a22faaff scripts/migration/migrate_npm.py
|
2cc658faaa610b21b48942c59b955525539ad4503fd60f2a49c7e626a22faaff scripts/migration/migrate_npm.py
|
||||||
a459d95bbb0076f33ade8b273b813308167f9d8d45242b309a2d18320ca0093c scripts/migration/migrate_npm_api.py
|
a459d95bbb0076f33ade8b273b813308167f9d8d45242b309a2d18320ca0093c scripts/migration/migrate_npm_api.py
|
||||||
31ccad3ec3c880119f3b7002f0c6652f8b43634edad8ab3763cb0e4497d0bb1f scripts/migration/migrate_npm_core.py
|
31ccad3ec3c880119f3b7002f0c6652f8b43634edad8ab3763cb0e4497d0bb1f scripts/migration/migrate_npm_core.py
|
||||||
2f5206ddea7a0816924986bfe663734f9fee7771fb25c04e0374642329b06c35 scripts/performance/README.md
|
bf934d59052988a528f3b7e7e3957b5a1ff1cf17db0d543e165cf76600e2137a scripts/performance/README.md
|
||||||
939287dadf7309689d29d2171757c2d80e91d898c3f9112454b943d08bd611fb scripts/performance/ab_1kb_dev.py
|
939287dadf7309689d29d2171757c2d80e91d898c3f9112454b943d08bd611fb scripts/performance/ab_1kb_dev.py
|
||||||
0380406a059ee9ec27473f8a46435f638f73a6dc729382ae1561c08dbf7ccb53 scripts/performance/benchmark.py
|
0380406a059ee9ec27473f8a46435f638f73a6dc729382ae1561c08dbf7ccb53 scripts/performance/benchmark.py
|
||||||
9ac220163256e08513597c397c016a48345b114d7876b439259cc310b36c83fc scripts/performance/checks_cache.py
|
9ac220163256e08513597c397c016a48345b114d7876b439259cc310b36c83fc scripts/performance/checks_cache.py
|
||||||
@@ -183,6 +184,7 @@ fa42ec0475e4398d647b3c094309b4332cd2b5649db223cae0b8e821d6b44a28 scripts/tests/
|
|||||||
72185f59f31bf04a4e88d53fe2fca52509f3663cb5f40b1d3bc319a5f945ef23 scripts/tests/panel_html.mjs
|
72185f59f31bf04a4e88d53fe2fca52509f3663cb5f40b1d3bc319a5f945ef23 scripts/tests/panel_html.mjs
|
||||||
b0136544af717b75df2cb107900ea65c3f945fdd0040369dbb93919008fb954c scripts/tests/panel_html.py
|
b0136544af717b75df2cb107900ea65c3f945fdd0040369dbb93919008fb954c scripts/tests/panel_html.py
|
||||||
8d1903e2b17b3d59cd0a5bec3e298e468b3d88ed2dda8b724239cc45d8353c59 scripts/tests/progressbar_0332_browser_test.py
|
8d1903e2b17b3d59cd0a5bec3e298e468b3d88ed2dda8b724239cc45d8353c59 scripts/tests/progressbar_0332_browser_test.py
|
||||||
|
e07652d8a9d170325779009e6d4a826a4d3cfc57515d73ada5f8717dd7a2aca6 scripts/tests/proxy_1kb_opt3_static_test.py
|
||||||
2a6e75c05f3860acf65ea8132f0083904478b690145115ec017c120d70f26cca scripts/tests/proxy_hotpath_034_static_test.py
|
2a6e75c05f3860acf65ea8132f0083904478b690145115ec017c120d70f26cca scripts/tests/proxy_hotpath_034_static_test.py
|
||||||
6fa2f22816996e989dad9dc7884adbf24f02f0ca6a6634b06e219eb08526c7fe scripts/tests/rps_hotpath_034_static_test.py
|
6fa2f22816996e989dad9dc7884adbf24f02f0ca6a6634b06e219eb08526c7fe scripts/tests/rps_hotpath_034_static_test.py
|
||||||
5be5cdc60324f4f0f6638640000b888a5babb0a9bbf29511c824a0ba38878c2a scripts/tests/sendfile_034_panel_browser_test.py
|
5be5cdc60324f4f0f6638640000b888a5babb0a9bbf29511c824a0ba38878c2a scripts/tests/sendfile_034_panel_browser_test.py
|
||||||
@@ -198,10 +200,10 @@ f0a2e3daa0a49aac54723cb5a281360e49a97c076281372c5cca2a85e096c02c scripts/tests/
|
|||||||
f55efa99245bbdbc98fe3202e52952d224ac3200612561580d52f6e251810049 scripts/tests/web_server_0330_ui_test.py
|
f55efa99245bbdbc98fe3202e52952d224ac3200612561580d52f6e251810049 scripts/tests/web_server_0330_ui_test.py
|
||||||
0dd1cef06a1c3e1546e2fb9b085bf369dfbed02107b9ab01b4fb4f3b894099c0 scripts/tests/web_server_0334_browser_test.py
|
0dd1cef06a1c3e1546e2fb9b085bf369dfbed02107b9ab01b4fb4f3b894099c0 scripts/tests/web_server_0334_browser_test.py
|
||||||
22e880e98c7b9d700c7a4c02b9b1d543e5dbd1f4ecdca42813a1083165b32a19 scripts/version.py
|
22e880e98c7b9d700c7a4c02b9b1d543e5dbd1f4ecdca42813a1083165b32a19 scripts/version.py
|
||||||
55485a513e8c4da2a58daa7abcb15b32ed408a5fc062cf23599fb83d16b90cfe src/acl.rs
|
9bf4e4a61824f82b773b4e8ffa0909b9cf0a42bfc14d695bd64da240cc634a61 src/acl.rs
|
||||||
63efab1e95d41134fc30cc57320c32810bbae9f81a640bdb24b1be2059e63d8a src/acl/dynamic.rs
|
63efab1e95d41134fc30cc57320c32810bbae9f81a640bdb24b1be2059e63d8a src/acl/dynamic.rs
|
||||||
4803e44de39b872a5b3b55f9ca9e81c066ba52cb8f6adb879fd57c5f94b93e89 src/acl/dynamic/tests.rs
|
4803e44de39b872a5b3b55f9ca9e81c066ba52cb8f6adb879fd57c5f94b93e89 src/acl/dynamic/tests.rs
|
||||||
a22e5ea6381a1eacafe650be0f5cd6e653773e31f6491a562fe0459a309fb7cc src/acl/tests.rs
|
5e77c7e8d9781f6a8ff3d3f09da4dbcd5277b241901ff17c6c8c2703f2abbcef src/acl/tests.rs
|
||||||
6f5d3137bf24451da05c1db9f0f9e2a451305db17b6bc2590815ad54a6511829 src/acme.rs
|
6f5d3137bf24451da05c1db9f0f9e2a451305db17b6bc2590815ad54a6511829 src/acme.rs
|
||||||
587097620b2f25ce7c6f59f907b47660184b095e3040c7615e74509a82e7cdd8 src/acme/jobs.rs
|
587097620b2f25ce7c6f59f907b47660184b095e3040c7615e74509a82e7cdd8 src/acme/jobs.rs
|
||||||
3f158f46e80b403b658b4e9b1e3710e38c70e47b2d476b8223d9c394ffeeba95 src/acme/model.rs
|
3f158f46e80b403b658b4e9b1e3710e38c70e47b2d476b8223d9c394ffeeba95 src/acme/model.rs
|
||||||
@@ -336,7 +338,7 @@ a9eeb93519aae2de7a1282296ced91d2eb8307cc966f8fbf6085af0fd21b0ad2 src/cache/cold
|
|||||||
2eaba3261da2a547e9af91330319a3abd3a8ad8b5b44a337a84040720209017d src/cache/cold/startup.rs
|
2eaba3261da2a547e9af91330319a3abd3a8ad8b5b44a337a84040720209017d src/cache/cold/startup.rs
|
||||||
e7c7fd71727d542c2df3e03a4a1cba9d839c516963f66bbfde1c29ec81053500 src/cache/cold/worker.rs
|
e7c7fd71727d542c2df3e03a4a1cba9d839c516963f66bbfde1c29ec81053500 src/cache/cold/worker.rs
|
||||||
d51fa4f320be56c572f767617428c3f3838543cb0eeb7d1f8ae791e1a1ce9a71 src/cache/collapse.rs
|
d51fa4f320be56c572f767617428c3f3838543cb0eeb7d1f8ae791e1a1ce9a71 src/cache/collapse.rs
|
||||||
36febf4d27a2f5bbd72dab7f45d8b8dd64fb4f7b95060b9e0ebd5709d19749fa src/cache/dashboard.rs
|
563919c53d38e9b83505a5715469833569efb2a09e91a2b9a89c335187368dee src/cache/dashboard.rs
|
||||||
7eaf3947cfc89cc6ef55a64fef0b3fedf1d9a1c33322aba2b5170c0ea34a5f2c src/cache/fill.rs
|
7eaf3947cfc89cc6ef55a64fef0b3fedf1d9a1c33322aba2b5170c0ea34a5f2c src/cache/fill.rs
|
||||||
675d18afb53f25c56cba49bbb431fa39ecd9d7bdd192c7b39b0698225de7a2a2 src/cache/invalidation.rs
|
675d18afb53f25c56cba49bbb431fa39ecd9d7bdd192c7b39b0698225de7a2a2 src/cache/invalidation.rs
|
||||||
c1357a13658e1ed5158084d3a41ba9ea906512774a5863ce9c56cb508d3fa6c8 src/cache/invalidation/ban.rs
|
c1357a13658e1ed5158084d3a41ba9ea906512774a5863ce9c56cb508d3fa6c8 src/cache/invalidation/ban.rs
|
||||||
@@ -359,7 +361,7 @@ e26dce02df9c043329ca164fed215d271781bce8fb03240f528331bf3df6b9d7 src/cache/poli
|
|||||||
989df11e246123f760ef31689370f3500e0913fcc6b70d7ac0c2c95e2f412fb0 src/cache/policy/request.rs
|
989df11e246123f760ef31689370f3500e0913fcc6b70d7ac0c2c95e2f412fb0 src/cache/policy/request.rs
|
||||||
d6051d55168fe1db97980a54c22c708f4f325ba72bfc36f35a1851eb5dcc051d src/cache/policy/response.rs
|
d6051d55168fe1db97980a54c22c708f4f325ba72bfc36f35a1851eb5dcc051d src/cache/policy/response.rs
|
||||||
7e2bcfbb2b57732cdc704d9df7a9298fb80e735e37984431bc936630e41e7f8a src/cache/range.rs
|
7e2bcfbb2b57732cdc704d9df7a9298fb80e735e37984431bc936630e41e7f8a src/cache/range.rs
|
||||||
6d7a4c22a7776a6538fb22d78806bec72efbe11aa5a26fb3ecf575ffd266ada0 src/cache/stats.rs
|
cf2513043813421e10260cef63645fdb8c5249114f16d91eae9f919b0214242a src/cache/stats.rs
|
||||||
93bd3e9e5c1a045c784cee0e1fdb9e1c5d862ed70d5d91c54c5a769bca8fd949 src/cache/tests.rs
|
93bd3e9e5c1a045c784cee0e1fdb9e1c5d862ed70d5d91c54c5a769bca8fd949 src/cache/tests.rs
|
||||||
9183fa8e6086e92fb148fc278fd866d44e3dd988c848a071e32066e44a1cbf0a src/cache/tests/cold_tier_tests.rs
|
9183fa8e6086e92fb148fc278fd866d44e3dd988c848a071e32066e44a1cbf0a src/cache/tests/cold_tier_tests.rs
|
||||||
25ed7635bfd5e34de55b319bf163db9a0af26642664ce7685ebe1241bab52cac src/cache/tests/dashboard_tests.rs
|
25ed7635bfd5e34de55b319bf163db9a0af26642664ce7685ebe1241bab52cac src/cache/tests/dashboard_tests.rs
|
||||||
@@ -391,8 +393,8 @@ b45d6040810c93ae2acb04ef6b0a59c189751edf9008cd81a4521c9e04e7f07d src/docker.rs
|
|||||||
4c2c988191a97f81568a8df64af772463d010844f1abf327871901cdb32d4336 src/docker/model.rs
|
4c2c988191a97f81568a8df64af772463d010844f1abf327871901cdb32d4336 src/docker/model.rs
|
||||||
5f4610c59b2d4ff6a205a2d9ae11bc84be689b2a68302459f546b3f43e53593c src/docker/reconcile.rs
|
5f4610c59b2d4ff6a205a2d9ae11bc84be689b2a68302459f546b3f43e53593c src/docker/reconcile.rs
|
||||||
a12c47df4b90b6fc36c978a42477e6ddffbbbf69aed7252101c2fd829709bd6e src/docker/tests.rs
|
a12c47df4b90b6fc36c978a42477e6ddffbbbf69aed7252101c2fd829709bd6e src/docker/tests.rs
|
||||||
2a02ea2e010cde1b1fe99b2e6baa80ea344ea6b332dbb3c4be795b71541f64a7 src/forwarding.rs
|
ed17a905bc249b4c36dfd07711a87bea9f385a01d16264f798e1bae932dc51a0 src/forwarding.rs
|
||||||
057e652f216a1478638cbb53b1c64ac88c7f413a65825caacbf893c5af004a63 src/forwarding/tests.rs
|
b25fbb00de0e37ad5fa1a049839265e5f7e9f83b6214c40a95f57ea4807685bc src/forwarding/tests.rs
|
||||||
af7a4e91c32ffbe4701c8c06e48ba3f8bbbe55e8b9e877aecb30ccdf858e9e8f src/geoip.rs
|
af7a4e91c32ffbe4701c8c06e48ba3f8bbbe55e8b9e877aecb30ccdf858e9e8f src/geoip.rs
|
||||||
80850f7acd1ed4c0048b065e9e81740869a8efbb0d9b5d2e83d8ec7fbc494703 src/geoip/mmdb.rs
|
80850f7acd1ed4c0048b065e9e81740869a8efbb0d9b5d2e83d8ec7fbc494703 src/geoip/mmdb.rs
|
||||||
06aa056d789742cc11c8826c338d985e6a6fb22da6c3530937eec38e35478034 src/geoip/mmdb_tests.rs
|
06aa056d789742cc11c8826c338d985e6a6fb22da6c3530937eec38e35478034 src/geoip/mmdb_tests.rs
|
||||||
@@ -440,25 +442,25 @@ e03f0d6b3f35dae6b98c394837aa0d17e4d73a8306767b4c607868bc00c30286 src/prometheus
|
|||||||
fb2d8d45cd2e947f783eb981b37fa418c39fcb327a3af17a648fadea7f37f888 src/prometheus/web_php.rs
|
fb2d8d45cd2e947f783eb981b37fa418c39fcb327a3af17a648fadea7f37f888 src/prometheus/web_php.rs
|
||||||
2c97367686b54989573ce7fb81b2c6dde72adb254df9522bc05ded74f1fbffb6 src/proxy/body.rs
|
2c97367686b54989573ce7fb81b2c6dde72adb254df9522bc05ded74f1fbffb6 src/proxy/body.rs
|
||||||
c9b76abd91f0dfe81e07073e89d35faa033c19e11fa88b223f075428542284f1 src/proxy/body/tests.rs
|
c9b76abd91f0dfe81e07073e89d35faa033c19e11fa88b223f075428542284f1 src/proxy/body/tests.rs
|
||||||
b88d1d7a6837a2a470e5863f1f3247878daee62791fc3d8e72baeb66469bdfbf src/proxy/cache_helpers.rs
|
818dcfefa1c40d6a722dacf53c7f45c53141240f1c1913cfa651e19dd2718526 src/proxy/cache_helpers.rs
|
||||||
aa49c9928bb91e3d5bae0d340d88396a0417b9edec6192d41e1403533319a897 src/proxy/cache_http_invalidation.rs
|
aa49c9928bb91e3d5bae0d340d88396a0417b9edec6192d41e1403533319a897 src/proxy/cache_http_invalidation.rs
|
||||||
676c1b325fd8e55bcdd86451c7d1d8b8d347ca00e8da6b3e5b148a58c376a9b4 src/proxy/cache_plan.rs
|
676c1b325fd8e55bcdd86451c7d1d8b8d347ca00e8da6b3e5b148a58c376a9b4 src/proxy/cache_plan.rs
|
||||||
3404b03096862de1050088f62cdd811ea1ab61d191f902501173107f81f23b9b src/proxy/cache_plan/tests.rs
|
3404b03096862de1050088f62cdd811ea1ab61d191f902501173107f81f23b9b src/proxy/cache_plan/tests.rs
|
||||||
a8848cbdf85368548b003af2b43823c453866c472af4c7d2ff0ec7da14c44c8d src/proxy/error_fallback.rs
|
a8848cbdf85368548b003af2b43823c453866c472af4c7d2ff0ec7da14c44c8d src/proxy/error_fallback.rs
|
||||||
bf2e6bbd64fb45fceed54af0bff1c1e00da22b79c64dd8f5c23d2af0dbab0b7d src/proxy/forward/cache_lookup.rs
|
5670cd3557d862660596d8f9163a510aab8651206b5e61ae0f735a1a8a3c4313 src/proxy/forward/cache_lookup.rs
|
||||||
cfa941b60b5d167ec9d8e2566ea3342b588d1a9078a6ca711ad6062ddf00fec9 src/proxy/forward/response.rs
|
cfa941b60b5d167ec9d8e2566ea3342b588d1a9078a6ca711ad6062ddf00fec9 src/proxy/forward/response.rs
|
||||||
7a5f43fe65e5a746eb84881da0bf784215081ead66d18faa7780533ad5df9c8c src/proxy/forward/route_cache.rs
|
7a5f43fe65e5a746eb84881da0bf784215081ead66d18faa7780533ad5df9c8c src/proxy/forward/route_cache.rs
|
||||||
96c97928986ca89c2d6d415d5b107b9cc7228410bae93a2c08c24974324f4d8d src/proxy/forward/success_finish.rs
|
8b7d9802616fde19a5be3ae2d2e4703e066f203e6a60a6bd781d3a2c15388348 src/proxy/forward/success_finish.rs
|
||||||
0a921474ef2b93eabdaf3888651cf29130eace5ccb1e0f5d6bc25e6aa69280be src/proxy/forward/success_prepare.rs
|
0a921474ef2b93eabdaf3888651cf29130eace5ccb1e0f5d6bc25e6aa69280be src/proxy/forward/success_prepare.rs
|
||||||
bb24da9c9ab986a9c4d4144d4d1be0f661561d4afc755f298b58bc679b182850 src/proxy/forward/upstream.rs
|
bb24da9c9ab986a9c4d4144d4d1be0f661561d4afc755f298b58bc679b182850 src/proxy/forward/upstream.rs
|
||||||
11dccc239ac8a8e9ea06e884c2f2658f86b7807d14a59b8e052bc7dd75e86d63 src/proxy/forward/web_source.rs
|
bb8c9117e05b0cb4bbfe029ef4d1a031fc755cdce255b15dad072afd1c845737 src/proxy/forward/web_source.rs
|
||||||
facde401ba5bcbafdcea5524f236cfae37eec11144ceeea879e83314c84bc97e src/proxy/forward_entry.rs
|
2b6ae70b84fd1eba0dba6d8d7e2db62227193dc5b77e4a6111ceab1b219800c2 src/proxy/forward_entry.rs
|
||||||
9b25f5e3ea4443a63ee6f45d12e1957dd316fe8e85325fc00ea416faaab1025a src/proxy/h2_graceful.rs
|
9b25f5e3ea4443a63ee6f45d12e1957dd316fe8e85325fc00ea416faaab1025a src/proxy/h2_graceful.rs
|
||||||
3e856a2e3e2236caebf4a7e7e8aba229a529cc552c1fb73e4b81c38784ecad26 src/proxy/headers.rs
|
31e925a408ba6273deb0793df5f5f82eeabbcaffb365cc0457ed9c67d9200f34 src/proxy/headers.rs
|
||||||
02542edb25302ab4778d5f84f900b546ed126be56e4bc9d3580703edea6f492c src/proxy/headers/tests.rs
|
013f0495127e351cf535a928526b56bd3a63202d8abd0fa59da5dfb8dc783f27 src/proxy/headers/tests.rs
|
||||||
3f25429418941eb72c3847125340db74684ad316eb278ecf658953026fd41f4f src/proxy/mod.rs
|
3f25429418941eb72c3847125340db74684ad316eb278ecf658953026fd41f4f src/proxy/mod.rs
|
||||||
55133ceace2e5262d9c54fa41bea099a27b817d1e09c1cd0e2493fd9cee4da38 src/proxy/redirect_helpers.rs
|
55133ceace2e5262d9c54fa41bea099a27b817d1e09c1cd0e2493fd9cee4da38 src/proxy/redirect_helpers.rs
|
||||||
96da894a86c05f8f467bf83cc23e56627ed3f9f490ffc936872d70126c8ecb41 src/proxy/request_id.rs
|
01b3027cfe92512f71be056a1ae26749b3ad3417d43388f1c05dcf5884d83966 src/proxy/request_id.rs
|
||||||
d2df55b6ccfc7ceb8a3d2c49f664acd183448e3d932d5399d6692e87f79a094b src/proxy/request_id/tests.rs
|
d2df55b6ccfc7ceb8a3d2c49f664acd183448e3d932d5399d6692e87f79a094b src/proxy/request_id/tests.rs
|
||||||
e56c65c85e42ee4fe6adf3655f3b2d176798e37c648db6bfb8e1602300b723eb src/proxy/tests/authority_tests.rs
|
e56c65c85e42ee4fe6adf3655f3b2d176798e37c648db6bfb8e1602300b723eb src/proxy/tests/authority_tests.rs
|
||||||
1aa9bbbfb2659facdaeaf185e11221b1c8cd5fe6e23c7c1dacd3aa55e3db4fcb src/proxy/tests/cache_fast_path_tests.rs
|
1aa9bbbfb2659facdaeaf185e11221b1c8cd5fe6e23c7c1dacd3aa55e3db4fcb src/proxy/tests/cache_fast_path_tests.rs
|
||||||
|
|||||||
@@ -130,3 +130,18 @@ backend connects and all log/traffic drop counters. Test concurrent global
|
|||||||
purges and per-vhost purges during traffic and verify the stats reset behavior
|
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
|
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**.
|
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.
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# 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,5 +1,12 @@
|
|||||||
# Live performance and regression harness
|
# Live performance and regression harness
|
||||||
|
|
||||||
|
## DEV OPT3 / 1 KiB response: uncompiled source
|
||||||
|
|
||||||
|
`docs/PROXY_1KB_OPT3_DEV_0.34.0.md` describes 7 optimized hot-path sites
|
||||||
|
and how to benchmark their **newly rebuilt** executable. Baseline
|
||||||
|
`rust-proxy(2)` passed 89 real proxy/policy/cache checks. This runtime result
|
||||||
|
is not evidence that uncompiled OPT3 changes work, nor a measured OPT3 RPS gain.
|
||||||
|
|
||||||
These scripts exercise actual RustProxy executables through loopback sockets.
|
These scripts exercise actual RustProxy executables through loopback sockets.
|
||||||
They do not simulate Rust request handling. The Python server in `peer.py` is a
|
They do not simulate Rust request handling. The Python server in `peer.py` is a
|
||||||
controllable test **upstream**, used to verify streaming, non-replayable writes,
|
controllable test **upstream**, used to verify streaming, non-replayable writes,
|
||||||
|
|||||||
Executable
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Source-only regression guards for the OPT3 patch (NOT a Rust compile)."""
|
||||||
|
from pathlib import Path
|
||||||
|
root = Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
|
def read(name):
|
||||||
|
return (root / name).read_text(encoding='utf-8')
|
||||||
|
|
||||||
|
assert 'version = "0.34.0"' in read('Cargo.toml')
|
||||||
|
acl = read('src/acl.rs')
|
||||||
|
assert "pub fn routing_path_view(raw: &str)" in acl
|
||||||
|
assert "return routing_path(raw).map(std::borrow::Cow::Owned)" in acl
|
||||||
|
assert 'raw.split' in acl and 'raw.len() <= 8192' in acl
|
||||||
|
forward = read('src/proxy/forward_entry.rs')
|
||||||
|
assert 'let routing_uri = request.uri().clone();' in forward
|
||||||
|
assert 'crate::acl::routing_path_view(routing_uri.path())' in forward
|
||||||
|
assert 'if context.client_ip != peer.0.ip()' in forward
|
||||||
|
route = read('src/proxy/cache_helpers.rs')
|
||||||
|
assert 'crate::acl::routing_path_view(request.uri().path())' in route
|
||||||
|
for name in ('src/proxy/forward/cache_lookup.rs', 'src/proxy/forward/success_finish.rs', 'src/proxy/forward/web_source.rs'):
|
||||||
|
assert 'path.clone()' not in read(name), name
|
||||||
|
assert 'if !trusted_peer' in read('src/forwarding.rs')
|
||||||
|
assert 'let mut ips = chain(incoming).unwrap_or_default();' in read('src/forwarding.rs')
|
||||||
|
headers = read('src/proxy/headers.rs')
|
||||||
|
assert 'let only_keep_alive' in headers
|
||||||
|
assert 'let nominated: Vec<HeaderName>' in headers
|
||||||
|
assert 'headers.remove(name)' in headers
|
||||||
|
assert 'String::from_utf8(encoded)' in read('src/proxy/request_id.rs')
|
||||||
|
assert 'if reason == "cache_disabled"' in read('src/cache/stats.rs')
|
||||||
|
assert not read('src/cache/stats.rs').startswith('//!') # previous E0753 build issue
|
||||||
|
for name in ('src/acl/tests.rs', 'src/proxy/headers/tests.rs', 'src/forwarding/tests.rs'):
|
||||||
|
assert (root / name).exists()
|
||||||
|
for source in (root / 'src').rglob('*.rs'):
|
||||||
|
if '/tests/' in source.as_posix() or source.name == 'tests.rs':
|
||||||
|
continue
|
||||||
|
assert len(source.read_text(encoding='utf-8').splitlines()) <= 500, source
|
||||||
|
print('PASS OPT3 source guards (STATIC ONLY; not cargo check, live HTTP or throughput)')
|
||||||
+26
@@ -166,6 +166,32 @@ pub fn validate_decoded_routing_path(path: &str) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validate a routing path without allocating for the common already-normalized
|
||||||
|
/// form. The slow path delegates to `routing_path` to preserve percent decode,
|
||||||
|
/// UTF-8 validation, slash folding and ambiguity rejection exactly as before.
|
||||||
|
/// Callers must not use this value as the upstream URL: the raw URI is forwarded.
|
||||||
|
pub fn routing_path_view(raw: &str) -> Result<std::borrow::Cow<'_, str>> {
|
||||||
|
ensure!(
|
||||||
|
raw.starts_with('/') && raw.len() <= 8192,
|
||||||
|
"invalid path length or form"
|
||||||
|
);
|
||||||
|
let bytes = raw.as_bytes();
|
||||||
|
for (index, &byte) in bytes.iter().enumerate() {
|
||||||
|
if byte == b'%' || (byte == b'/' && index > 0 && bytes[index - 1] == b'/') {
|
||||||
|
return routing_path(raw).map(std::borrow::Cow::Owned);
|
||||||
|
}
|
||||||
|
ensure!(
|
||||||
|
byte > 32 && byte != 127 && !matches!(byte, b'\\' | b'?' | b'#' | b';'),
|
||||||
|
"invalid path character"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ensure!(
|
||||||
|
!raw.split('/').any(|segment| segment == "." || segment == ".."),
|
||||||
|
"ambiguous path segments"
|
||||||
|
);
|
||||||
|
Ok(std::borrow::Cow::Borrowed(raw))
|
||||||
|
}
|
||||||
|
|
||||||
/// Routing/ACL use one decoded representation; upstream still receives the original path.
|
/// Routing/ACL use one decoded representation; upstream still receives the original path.
|
||||||
/// Reject encoded separators, percent (double decoding), controls, ambiguous dot segments,
|
/// Reject encoded separators, percent (double decoding), controls, ambiguous dot segments,
|
||||||
/// matrix parameters and malformed UTF-8. Repeated literal separators are
|
/// matrix parameters and malformed UTF-8. Repeated literal separators are
|
||||||
|
|||||||
@@ -255,4 +255,21 @@ mod tests {
|
|||||||
assert!(routing_path(path).is_err(), "{path}");
|
assert!(routing_path(path).is_err(), "{path}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn borrowed_route_path_matches_original_validator_for_safe_and_unsafe_targets() {
|
||||||
|
use std::borrow::Cow;
|
||||||
|
for value in ["/", "/a/b", "/static/app.js", "/café", "/admin/%61", "/a//b", "/%2e%2e/a", "/..", "/a;secret", "/x%2Fy", "/%00", "/a b", "/bad%", "/a\\b", "not-absolute"] {
|
||||||
|
let original = routing_path(value);
|
||||||
|
let borrowed = routing_path_view(value);
|
||||||
|
assert_eq!(original.is_ok(), borrowed.is_ok(), "{value}");
|
||||||
|
if let (Ok(normalized), Ok(view)) = (original, borrowed) {
|
||||||
|
assert_eq!(normalized, view.as_ref(), "{value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(matches!(routing_path_view("/foo/bar").unwrap(), Cow::Borrowed(_)));
|
||||||
|
assert!(matches!(routing_path_view("/foo//bar").unwrap(), Cow::Owned(_)));
|
||||||
|
assert!(matches!(routing_path_view("/%61").unwrap(), Cow::Owned(_)));
|
||||||
|
assert!(routing_path_view(&format!("/{}", "x".repeat(8192))).is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Vendored
+3
-3
@@ -5,7 +5,7 @@ impl Cache {
|
|||||||
.lock()
|
.lock()
|
||||||
.unwrap_or_else(|e| e.into_inner())
|
.unwrap_or_else(|e| e.into_inner())
|
||||||
.clone();
|
.clone();
|
||||||
let mut store = self.store.lock().unwrap_or_else(|e| e.into_inner());
|
let store = self.store.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
let n = |value: &AtomicU64| value.load(Ordering::Relaxed);
|
let n = |value: &AtomicU64| value.load(Ordering::Relaxed);
|
||||||
let hits = n(&self.counters.hits);
|
let hits = n(&self.counters.hits);
|
||||||
let misses = n(&self.counters.misses);
|
let misses = n(&self.counters.misses);
|
||||||
@@ -243,7 +243,7 @@ impl Cache {
|
|||||||
count
|
count
|
||||||
}
|
}
|
||||||
pub fn scope_view(&self, scope: &str) -> ScopeView {
|
pub fn scope_view(&self, scope: &str) -> ScopeView {
|
||||||
let mut store = self.store.lock().unwrap_or_else(|e| e.into_inner());
|
let store = self.store.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
let mut view = ScopeView {
|
let mut view = ScopeView {
|
||||||
entries: 0,
|
entries: 0,
|
||||||
resident_bytes: 0,
|
resident_bytes: 0,
|
||||||
@@ -269,7 +269,7 @@ impl Cache {
|
|||||||
self.domain_stats.record_reason(scope, domain, reason);
|
self.domain_stats.record_reason(scope, domain, reason);
|
||||||
}
|
}
|
||||||
pub fn dashboard(&self, scope: Option<&str>, offset: usize, limit: usize) -> Dashboard {
|
pub fn dashboard(&self, scope: Option<&str>, offset: usize, limit: usize) -> Dashboard {
|
||||||
let mut store = self.store.lock().unwrap_or_else(|e| e.into_inner());
|
let store = self.store.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
let revision = store.revision;
|
let revision = store.revision;
|
||||||
let mut groups: BTreeMap<(String, String), DomainView> = self
|
let mut groups: BTreeMap<(String, String), DomainView> = self
|
||||||
.domain_stats
|
.domain_stats
|
||||||
|
|||||||
Vendored
+4
@@ -58,6 +58,10 @@ impl DomainStats {
|
|||||||
fn record_reason(&self, reason: &'static str) {
|
fn record_reason(&self, reason: &'static str) {
|
||||||
// Cache OFF is the highest-volume case; avoid reason map lookup and
|
// Cache OFF is the highest-volume case; avoid reason map lookup and
|
||||||
// any shared lock/allocator activity for its dashboard reason.
|
// any shared lock/allocator activity for its dashboard reason.
|
||||||
|
if reason == "cache_disabled" {
|
||||||
|
self.fast_reasons[0].fetch_add(1, Ordering::Relaxed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if let Some(index) = FAST_REASONS.iter().position(|label| *label == reason) {
|
if let Some(index) = FAST_REASONS.iter().position(|label| *label == reason) {
|
||||||
self.fast_reasons[index].fetch_add(1, Ordering::Relaxed);
|
self.fast_reasons[index].fetch_add(1, Ordering::Relaxed);
|
||||||
return;
|
return;
|
||||||
|
|||||||
+28
-5
@@ -217,11 +217,34 @@ impl Compiled {
|
|||||||
) -> Result<Context> {
|
) -> Result<Context> {
|
||||||
let peer_ip = canonical(peer.ip());
|
let peer_ip = canonical(peer.ip());
|
||||||
let trusted_peer = self.trusted(peer_ip);
|
let trusted_peer = self.trusted(peer_ip);
|
||||||
let mut ips = if trusted_peer {
|
if !trusted_peer {
|
||||||
chain(incoming).unwrap_or_default()
|
let scheme = if tls { "https" } else { "http" };
|
||||||
} else {
|
let authority_text = authority.to_str()?;
|
||||||
Vec::new()
|
crate::config::request_host(authority_text)?;
|
||||||
};
|
let target = uri.path_and_query().map(|v| v.as_str()).unwrap_or("/");
|
||||||
|
let original_url = format!("{scheme}://{authority_text}{target}");
|
||||||
|
let peer_text = peer_ip.to_string();
|
||||||
|
let peer_header = HeaderValue::from_str(&peer_text)?;
|
||||||
|
let forwarded = format!(
|
||||||
|
"for={};by={};proto={scheme};host=\"{authority_text}\"",
|
||||||
|
node(peer_ip), node(canonical(local.ip()))
|
||||||
|
);
|
||||||
|
let mut values = HeaderMap::with_capacity(7);
|
||||||
|
values.insert("forwarded", forwarded.parse()?);
|
||||||
|
values.insert("x-forwarded-for", peer_header.clone());
|
||||||
|
values.insert("x-real-ip", peer_header);
|
||||||
|
values.insert("x-forwarded-host", authority.clone());
|
||||||
|
values.insert("x-forwarded-proto", HeaderValue::from_static(scheme));
|
||||||
|
values.insert("x-forwarded-port", local.port().to_string().parse()?);
|
||||||
|
values.insert("x-forwarded-uri", target.parse()?);
|
||||||
|
return Ok(Context {
|
||||||
|
client_ip: peer_ip,
|
||||||
|
scheme,
|
||||||
|
original_url,
|
||||||
|
values,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let mut ips = chain(incoming).unwrap_or_default();
|
||||||
ips.push(peer_ip);
|
ips.push(peer_ip);
|
||||||
// Discard everything left of the first untrusted hop walking from us.
|
// Discard everything left of the first untrusted hop walking from us.
|
||||||
let first = ips.iter().rposition(|ip| !self.trusted(*ip)).unwrap_or(0);
|
let first = ips.iter().rposition(|ip| !self.trusted(*ip)).unwrap_or(0);
|
||||||
|
|||||||
@@ -200,4 +200,33 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(c.values["x-forwarded-for"], "203.0.113.8, 104.16.1.2");
|
assert_eq!(c.values["x-forwarded-for"], "203.0.113.8, 104.16.1.2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn untrusted_fast_path_preserves_ipv6_and_forwarding_metadata() {
|
||||||
|
let source = Settings {
|
||||||
|
trusted_proxies: vec!["10.0.0.0/8".into()],
|
||||||
|
trust_forwarded_proto: true,
|
||||||
|
}.compile().unwrap();
|
||||||
|
let mut supplied = HeaderMap::new();
|
||||||
|
supplied.insert("forwarded", "for=203.0.113.8".parse().unwrap());
|
||||||
|
supplied.insert("x-forwarded-proto", "http".parse().unwrap());
|
||||||
|
let context = source.context(
|
||||||
|
&supplied,
|
||||||
|
"[2001:db8::5]:5050".parse().unwrap(),
|
||||||
|
"[2001:db8::10]:8080".parse().unwrap(),
|
||||||
|
true,
|
||||||
|
&"app.example:443".parse().unwrap(),
|
||||||
|
&"/s?q=1".parse().unwrap(),
|
||||||
|
).unwrap();
|
||||||
|
assert_eq!(context.client_ip, "2001:db8::5".parse::<IpAddr>().unwrap());
|
||||||
|
assert_eq!(context.scheme, "https");
|
||||||
|
assert_eq!(context.original_url, "https://app.example:443/s?q=1");
|
||||||
|
assert_eq!(context.values["forwarded"],
|
||||||
|
"for=\"[2001:db8::5]\";by=\"[2001:db8::10]\";proto=https;host=\"app.example:443\"");
|
||||||
|
assert_eq!(context.values["x-forwarded-for"], "2001:db8::5");
|
||||||
|
assert_eq!(context.values["x-real-ip"], "2001:db8::5");
|
||||||
|
assert_eq!(context.values["x-forwarded-proto"], "https");
|
||||||
|
assert_eq!(context.values["x-forwarded-port"], "8080");
|
||||||
|
assert_eq!(context.values["x-forwarded-uri"], "/s?q=1");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -172,9 +172,9 @@ fn response_context(
|
|||||||
let authority = effective_host(request, tls).ok()?;
|
let authority = effective_host(request, tls).ok()?;
|
||||||
let host = request_host(authority.to_str().ok()?).ok()?;
|
let host = request_host(authority.to_str().ok()?).ok()?;
|
||||||
let frontend = snapshot.frontend(&host, local, tls).cloned();
|
let frontend = snapshot.frontend(&host, local, tls).cloned();
|
||||||
let route = crate::acl::routing_path(request.uri().path())
|
let route = crate::acl::routing_path_view(request.uri().path())
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|path| snapshot.select_on_arc(&host, &path, local, tls).cloned());
|
.and_then(|path| snapshot.select_on_arc(&host, path.as_ref(), local, tls).cloned());
|
||||||
Some((frontend, route))
|
Some((frontend, route))
|
||||||
})();
|
})();
|
||||||
context.unwrap_or((None, None))
|
context.unwrap_or((None, None))
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ if let Some(ticket) = &cache_ticket {
|
|||||||
uri: uri.clone(),
|
uri: uri.clone(),
|
||||||
public_suffix: public_suffix.clone(),
|
public_suffix: public_suffix.clone(),
|
||||||
host: host.clone(),
|
host: host.clone(),
|
||||||
path: path.clone(),
|
path: path.as_ref().to_owned(),
|
||||||
scope: scope.to_owned(),
|
scope: scope.to_owned(),
|
||||||
timeout: frontend
|
timeout: frontend
|
||||||
.and_then(|frontend| frontend.proxy_timeouts.response_header_timeout())
|
.and_then(|frontend| frontend.proxy_timeouts.response_header_timeout())
|
||||||
@@ -79,7 +79,7 @@ if let Some(ticket) = &cache_ticket {
|
|||||||
uri: uri.clone(),
|
uri: uri.clone(),
|
||||||
public_suffix: public_suffix.clone(),
|
public_suffix: public_suffix.clone(),
|
||||||
host: host.clone(),
|
host: host.clone(),
|
||||||
path: path.clone(),
|
path: path.as_ref().to_owned(),
|
||||||
scope: scope.to_owned(),
|
scope: scope.to_owned(),
|
||||||
timeout: frontend
|
timeout: frontend
|
||||||
.and_then(|frontend| frontend.proxy_timeouts.response_header_timeout())
|
.and_then(|frontend| frontend.proxy_timeouts.response_header_timeout())
|
||||||
@@ -240,7 +240,7 @@ if handshake.is_none()
|
|||||||
uri: uri.clone(),
|
uri: uri.clone(),
|
||||||
public_suffix: public_suffix.clone(),
|
public_suffix: public_suffix.clone(),
|
||||||
host: host.clone(),
|
host: host.clone(),
|
||||||
path: path.clone(),
|
path: path.as_ref().to_owned(),
|
||||||
scope: scope.to_owned(),
|
scope: scope.to_owned(),
|
||||||
timeout: frontend
|
timeout: frontend
|
||||||
.and_then(|frontend| frontend.proxy_timeouts.response_header_timeout())
|
.and_then(|frontend| frontend.proxy_timeouts.response_header_timeout())
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ if let (Some(ticket), Some(plan), Some(transformed)) =
|
|||||||
uri: active_uri.clone(),
|
uri: active_uri.clone(),
|
||||||
public_suffix: public_suffix.clone(),
|
public_suffix: public_suffix.clone(),
|
||||||
host: host.clone(),
|
host: host.clone(),
|
||||||
path: path.clone(),
|
path: path.as_ref().to_owned(),
|
||||||
scope: scope.to_owned(),
|
scope: scope.to_owned(),
|
||||||
timeout: frontend
|
timeout: frontend
|
||||||
.and_then(|frontend| frontend.proxy_timeouts.response_header_timeout())
|
.and_then(|frontend| frontend.proxy_timeouts.response_header_timeout())
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
}
|
}
|
||||||
let service_path = match route.rewritten_path(&path) {
|
let service_path = match route.rewritten_path(&path) {
|
||||||
Ok(Some(value)) => value,
|
Ok(Some(value)) => value,
|
||||||
Ok(None) => path.clone(),
|
Ok(None) => path.as_ref().to_owned(),
|
||||||
Err(_) => return error(StatusCode::BAD_REQUEST,
|
Err(_) => return error(StatusCode::BAD_REQUEST,
|
||||||
"RustProxy: invalid Web Server rewrite path\n"),
|
"RustProxy: invalid Web Server rewrite path\n"),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -101,7 +101,12 @@ async fn forward_request(
|
|||||||
.extensions()
|
.extensions()
|
||||||
.get::<InternalFallbackRoute>()
|
.get::<InternalFallbackRoute>()
|
||||||
.cloned();
|
.cloned();
|
||||||
let Ok(path) = crate::acl::routing_path(request.uri().path()) else {
|
// Retain the original URI's shared backing bytes independently of `request`.
|
||||||
|
// A normalized, already-safe path can then be borrowed for the entire
|
||||||
|
// forwarding operation, even after the request URI/headers are rewritten.
|
||||||
|
// Encoded/slash-folded paths still allocate via the strict decoder.
|
||||||
|
let routing_uri = request.uri().clone();
|
||||||
|
let Ok(path) = crate::acl::routing_path_view(routing_uri.path()) else {
|
||||||
return error(
|
return error(
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
"RustProxy: ambiguous request path\n",
|
"RustProxy: ambiguous request path\n",
|
||||||
@@ -216,9 +221,15 @@ async fn forward_request(
|
|||||||
cloudflare_country = crate::forwarding::cloudflare_country(request.headers(), peer.0);
|
cloudflare_country = crate::forwarding::cloudflare_country(request.headers(), peer.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(exchange) = request.extensions().get::<Arc<Exchange>>() {
|
// `observe` already stored the actual transport peer. Most requests
|
||||||
*exchange.client_ip.lock().unwrap_or_else(|e| e.into_inner()) =
|
// don't use a trusted forwarding hop, so avoid formatting the same IP
|
||||||
Some(context.client_ip.to_string());
|
// and taking the per-request mutex a second time. IPv4-mapped peers
|
||||||
|
// still take this path so their canonical form is correctly logged.
|
||||||
|
if context.client_ip != peer.0.ip() {
|
||||||
|
if let Some(exchange) = request.extensions().get::<Arc<Exchange>>() {
|
||||||
|
*exchange.client_ip.lock().unwrap_or_else(|e| e.into_inner()) =
|
||||||
|
Some(context.client_ip.to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// GeoIP stays off the normal request path. Resolve lazily for any vhost or
|
// GeoIP stays off the normal request path. Resolve lazily for any vhost or
|
||||||
// location policy that actually needs a country code, or for log capture.
|
// location policy that actually needs a country code, or for log capture.
|
||||||
|
|||||||
+21
-9
@@ -4,15 +4,27 @@ use http::{
|
|||||||
};
|
};
|
||||||
/// Remove both standard hop-by-hop fields and fields named by Connection.
|
/// Remove both standard hop-by-hop fields and fields named by Connection.
|
||||||
pub fn strip_hop_by_hop(headers: &mut HeaderMap) {
|
pub fn strip_hop_by_hop(headers: &mut HeaderMap) {
|
||||||
let nominated: Vec<HeaderName> = headers
|
// The common HTTP/1.1 "Connection: keep-alive" case has no dynamic
|
||||||
.get_all(CONNECTION)
|
// nominated headers to collect. Keep the general multi-value/token
|
||||||
.iter()
|
// parser for all other requests (including maliciously repeated fields).
|
||||||
.filter_map(|v| v.to_str().ok())
|
let only_keep_alive = {
|
||||||
.flat_map(|v| v.split(','))
|
let mut values = headers.get_all(CONNECTION).iter();
|
||||||
.filter_map(|v| v.trim().parse().ok())
|
values.next().is_some_and(|value| {
|
||||||
.collect();
|
value.as_bytes().eq_ignore_ascii_case(b"keep-alive")
|
||||||
for name in nominated {
|
&& values.next().is_none()
|
||||||
headers.remove(name);
|
})
|
||||||
|
};
|
||||||
|
if !only_keep_alive {
|
||||||
|
let nominated: Vec<HeaderName> = headers
|
||||||
|
.get_all(CONNECTION)
|
||||||
|
.iter()
|
||||||
|
.filter_map(|v| v.to_str().ok())
|
||||||
|
.flat_map(|v| v.split(','))
|
||||||
|
.filter_map(|v| v.trim().parse().ok())
|
||||||
|
.collect();
|
||||||
|
for name in nominated {
|
||||||
|
headers.remove(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for name in [
|
for name in [
|
||||||
"connection",
|
"connection",
|
||||||
|
|||||||
@@ -52,4 +52,34 @@ mod tests {
|
|||||||
restore_admin_cookie(&mut h, &admin);
|
restore_admin_cookie(&mut h, &admin);
|
||||||
assert_eq!(h.get("cookie").unwrap(), "a=1; b=2; rustproxy_session=secret");
|
assert_eq!(h.get("cookie").unwrap(), "a=1; b=2; rustproxy_session=secret");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn common_keep_alive_does_not_strip_unrelated_headers() {
|
||||||
|
for value in ["keep-alive", "KEEP-ALIVE", "Keep-Alive"] {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert("connection", value.parse().unwrap());
|
||||||
|
headers.insert("keep-alive", "timeout=2".parse().unwrap());
|
||||||
|
headers.insert("x-tenant", "alpha".parse().unwrap());
|
||||||
|
headers.insert("authorization", "Bearer allowed".parse().unwrap());
|
||||||
|
strip_hop_by_hop(&mut headers);
|
||||||
|
assert!(!headers.contains_key("connection"));
|
||||||
|
assert!(!headers.contains_key("keep-alive"));
|
||||||
|
assert_eq!(headers["x-tenant"], "alpha");
|
||||||
|
assert_eq!(headers["authorization"], "Bearer allowed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn multiple_connection_fields_still_strip_all_nominated_headers() {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.append("connection", "keep-alive".parse().unwrap());
|
||||||
|
headers.append("connection", "x-hidden, upgrade".parse().unwrap());
|
||||||
|
headers.insert("x-hidden", "sensitive".parse().unwrap());
|
||||||
|
headers.insert("x-public", "allowed".parse().unwrap());
|
||||||
|
headers.insert("upgrade", "websocket".parse().unwrap());
|
||||||
|
strip_hop_by_hop(&mut headers);
|
||||||
|
assert!(!headers.contains_key("x-hidden"));
|
||||||
|
assert!(!headers.contains_key("connection"));
|
||||||
|
assert!(!headers.contains_key("upgrade"));
|
||||||
|
assert_eq!(headers["x-public"], "allowed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,12 +13,14 @@ fn request_id_hash(sequence: u64) -> String {
|
|||||||
|
|
||||||
fn request_id_hex(digest: &[u8]) -> String {
|
fn request_id_hex(digest: &[u8]) -> String {
|
||||||
const HEX: &[u8; 16] = b"0123456789abcdef";
|
const HEX: &[u8; 16] = b"0123456789abcdef";
|
||||||
let mut encoded = String::with_capacity(digest.len() * 2);
|
// Encode two ASCII bytes per digest byte in one allocation; avoid 64
|
||||||
for &byte in digest {
|
// separate String::push bounds checks for the 32-byte SHA-256 digest.
|
||||||
encoded.push(HEX[(byte >> 4) as usize] as char);
|
let mut encoded = vec![0u8; digest.len() * 2];
|
||||||
encoded.push(HEX[(byte & 0x0f) as usize] as char);
|
for (i, &byte) in digest.iter().enumerate() {
|
||||||
|
encoded[2 * i] = HEX[(byte >> 4) as usize];
|
||||||
|
encoded[2 * i + 1] = HEX[(byte & 0x0f) as usize];
|
||||||
}
|
}
|
||||||
encoded
|
String::from_utf8(encoded).expect("hex digits are always valid UTF-8")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
Reference in new issue
Block a user