diff --git a/Cargo.lock b/Cargo.lock index eb48d24..347a262 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,24 +2,82 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + [[package]] name = "bitflags" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -37,6 +95,58 @@ dependencies = [ "rand_core", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + [[package]] name = "cpufeatures" version = "0.3.0" @@ -46,24 +156,126 @@ dependencies = [ "libc", ] +[[package]] +name = "criterion" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + [[package]] name = "getrandom" version = "0.4.2" @@ -78,6 +290,17 @@ dependencies = [ "wasip3", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -117,12 +340,32 @@ dependencies = [ "serde_core", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + [[package]] name = "leb128fmt" version = "0.1.0" @@ -147,6 +390,71 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "pretty_assertions" version = "1.4.1" @@ -181,6 +489,7 @@ name = "proxy-protocol" version = "0.5.3" dependencies = [ "bytes", + "criterion", "pretty_assertions", "rand", "snafu", @@ -218,6 +527,70 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "semver" version = "1.0.28" @@ -231,6 +604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -266,6 +640,18 @@ dependencies = [ "zmij", ] +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "snafu" version = "0.9.0" @@ -298,6 +684,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "unicode-ident" version = "1.0.24" @@ -310,6 +706,16 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasip2" version = "1.0.3+wasi-0.2.9" @@ -328,6 +734,51 @@ dependencies = [ "wit-bindgen 0.51.0", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + [[package]] name = "wasm-encoder" version = "0.244.0" @@ -362,6 +813,62 @@ dependencies = [ "semver", ] +[[package]] +name = "web-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "wit-bindgen" version = "0.51.0" @@ -462,6 +969,26 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/Cargo.toml b/Cargo.toml index 8b0aede..25f2844 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ snafu = "~0.9" bytes = "~1" [dev-dependencies] +criterion = { version = "0.8.2", features = ["html_reports"] } pretty_assertions = "^1.4" rand = "~0.10" @@ -29,3 +30,7 @@ rand = "~0.10" default = [] always_exhaustive = [] + +[[bench]] +name = "proxy_header" +harness = false diff --git a/benches/proxy_header.rs b/benches/proxy_header.rs new file mode 100644 index 0000000..9d9c712 --- /dev/null +++ b/benches/proxy_header.rs @@ -0,0 +1,192 @@ +//! Hot-path benchmarks for the PROXY protocol codec. +//! +//! Every accepted connection on the proxy front-end pays for exactly one +//! `parse`, and every upstream connection pays for exactly one `encode`, so +//! these run per-connection and are worth tracking. + +use bytes::BytesMut; +use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; +use proxy_protocol::{ProxyHeader, encode, parse, version1, version2}; +use std::hint::black_box; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6}; + +const V2_SIG: [u8; 12] = [ + 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A, +]; + +fn v2_header(family_proto: u8, payload: &[u8]) -> Vec { + let mut buf = Vec::with_capacity(16 + payload.len()); + buf.extend_from_slice(&V2_SIG); + buf.push(0x21); // version 2, PROXY command + buf.push(family_proto); + buf.extend_from_slice(&(payload.len() as u16).to_be_bytes()); + buf.extend_from_slice(payload); + buf +} + +fn v2_inet() -> Vec { + let mut p = Vec::new(); + p.extend_from_slice(&Ipv4Addr::new(192, 168, 0, 1).octets()); + p.extend_from_slice(&Ipv4Addr::new(10, 0, 0, 7).octets()); + p.extend_from_slice(&443u16.to_be_bytes()); + p.extend_from_slice(&51234u16.to_be_bytes()); + v2_header(0x11, &p) // AF_INET + STREAM +} + +fn v2_inet6() -> Vec { + let mut p = Vec::new(); + p.extend_from_slice(&Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1).octets()); + p.extend_from_slice(&Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2).octets()); + p.extend_from_slice(&443u16.to_be_bytes()); + p.extend_from_slice(&51234u16.to_be_bytes()); + v2_header(0x21, &p) // AF_INET6 + STREAM +} + +fn tlv(type_id: u8, value: &[u8]) -> Vec { + let mut t = Vec::with_capacity(3 + value.len()); + t.push(type_id); + t.extend_from_slice(&(value.len() as u16).to_be_bytes()); + t.extend_from_slice(value); + t +} + +/// AF_INET header carrying the TLV set a real edge sends: ALPN, authority +/// (SNI), unique-id and a CRC32C. +fn v2_inet_with_tlvs() -> Vec { + let mut p = Vec::new(); + p.extend_from_slice(&Ipv4Addr::new(192, 168, 0, 1).octets()); + p.extend_from_slice(&Ipv4Addr::new(10, 0, 0, 7).octets()); + p.extend_from_slice(&443u16.to_be_bytes()); + p.extend_from_slice(&51234u16.to_be_bytes()); + p.extend_from_slice(&tlv(0x01, b"h2")); + p.extend_from_slice(&tlv(0x02, b"api.example.com")); + p.extend_from_slice(&tlv(0x05, b"0123456789abcdef")); + p.extend_from_slice(&tlv(0x03, &0xdead_beefu32.to_be_bytes())); + v2_header(0x11, &p) +} + +fn parse_bench(c: &mut Criterion) { + let cases: Vec<(&str, Vec)> = vec![ + ( + "v1/tcp4", + b"PROXY TCP4 192.168.0.1 10.0.0.7 51234 443\r\n".to_vec(), + ), + ( + "v1/tcp6", + b"PROXY TCP6 2001:db8::1 2001:db8::2 51234 443\r\n".to_vec(), + ), + ("v1/unknown", b"PROXY UNKNOWN\r\n".to_vec()), + ("v2/inet", v2_inet()), + ("v2/inet6", v2_inet6()), + ("v2/inet+tlvs", v2_inet_with_tlvs()), + ]; + + let mut group = c.benchmark_group("parse"); + for (name, bytes) in &cases { + group.throughput(Throughput::Bytes(bytes.len() as u64)); + group.bench_with_input(BenchmarkId::from_parameter(name), bytes, |b, bytes| { + b.iter(|| { + let mut slice = &bytes[..]; + black_box(parse(black_box(&mut slice)).unwrap()) + }) + }); + } + group.finish(); +} + +/// The rejection path matters too: a non-PROXY client hitting a +/// proxy-protocol listener must be cheap to reject. +fn parse_reject_bench(c: &mut Criterion) { + let junk = b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".to_vec(); + c.bench_function("parse/reject_non_proxy", |b| { + b.iter(|| { + let mut slice = &junk[..]; + black_box(parse(black_box(&mut slice)).unwrap_err()) + }) + }); +} + +fn encode_bench(c: &mut Criterion) { + let v1_v4 = ProxyHeader::Version1 { + addresses: version1::ProxyAddresses::Ipv4 { + source: SocketAddrV4::new(Ipv4Addr::new(192, 168, 0, 1), 51234), + destination: SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 7), 443), + }, + }; + let v1_v6 = ProxyHeader::Version1 { + addresses: version1::ProxyAddresses::Ipv6 { + source: SocketAddrV6::new(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 51234, 0, 0), + destination: SocketAddrV6::new( + Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2), + 443, + 0, + 0, + ), + }, + }; + let v2_v4 = ProxyHeader::Version2 { + command: version2::ProxyCommand::Proxy, + transport_protocol: version2::ProxyTransportProtocol::Stream, + addresses: version2::ProxyAddresses::Ipv4 { + source: SocketAddrV4::new(Ipv4Addr::new(192, 168, 0, 1), 51234), + destination: SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 7), 443), + }, + extensions: vec![], + }; + let v2_v4_tlvs = ProxyHeader::Version2 { + command: version2::ProxyCommand::Proxy, + transport_protocol: version2::ProxyTransportProtocol::Stream, + addresses: version2::ProxyAddresses::Ipv4 { + source: SocketAddrV4::new(Ipv4Addr::new(192, 168, 0, 1), 51234), + destination: SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 7), 443), + }, + extensions: vec![ + version2::ExtensionTlv::Alpn(b"h2".to_vec()), + version2::ExtensionTlv::Authority("api.example.com".to_owned()), + version2::ExtensionTlv::UniqueId(b"0123456789abcdef".to_vec()), + version2::ExtensionTlv::Crc32c(0xdead_beef), + ], + }; + + let mut group = c.benchmark_group("encode"); + for (name, header) in [ + ("v1/tcp4", &v1_v4), + ("v1/tcp6", &v1_v6), + ("v2/inet", &v2_v4), + ("v2/inet+tlvs", &v2_v4_tlvs), + ] { + group.bench_with_input(BenchmarkId::from_parameter(name), header, |b, header| { + b.iter(|| black_box(encode(black_box(header.clone())).unwrap())) + }); + } + group.finish(); +} + +/// Round-trip: what a connection actually costs end to end. +fn roundtrip_bench(c: &mut Criterion) { + let header = ProxyHeader::Version2 { + command: version2::ProxyCommand::Proxy, + transport_protocol: version2::ProxyTransportProtocol::Stream, + addresses: version2::ProxyAddresses::Ipv4 { + source: SocketAddrV4::new(Ipv4Addr::new(192, 168, 0, 1), 51234), + destination: SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 7), 443), + }, + extensions: vec![], + }; + c.bench_function("roundtrip/v2_inet", |b| { + b.iter(|| { + let buf: BytesMut = encode(black_box(header.clone())).unwrap(); + let mut slice = &buf[..]; + black_box(parse(&mut slice).unwrap()) + }) + }); +} + +criterion_group!( + benches, + parse_bench, + parse_reject_bench, + encode_bench, + roundtrip_bench +); +criterion_main!(benches); diff --git a/src/lib.rs b/src/lib.rs index 4c2b856..581c16d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,7 +85,7 @@ fn parse_version(buf: &mut impl Buf) -> Result { // 0x59), and we can therefore decide version based on that. // // We use ::chunk to not advance any bytes unnecessarily. - if buf.chunk()[..6] == [b'P', b'R', b'O', b'X', b'Y', b' '] { + if buf.chunk()[..6] == *b"PROXY " { buf.advance(6); return Ok(1); } @@ -693,10 +693,7 @@ mod parse_tests { #[test] fn test_version_parsing_correct() { - assert_eq!( - parse_version(&mut &[b'P', b'R', b'O', b'X', b'Y', b' '][..]), - Ok(1), - ); + assert_eq!(parse_version(&mut &b"PROXY "[..]), Ok(1),); assert_eq!( parse_version( &mut &[ diff --git a/src/version2.rs b/src/version2.rs index e8d8f5e..8f8aad9 100644 --- a/src/version2.rs +++ b/src/version2.rs @@ -508,25 +508,15 @@ pub(crate) fn parse(buf: &mut impl Buf) -> Result 108 * 2, ProxyAddressFamily::Unspec => 0, }; - if address_family == ProxyAddressFamily::Unix { - ensure!( - length >= 108 * 2, - InsufficientLengthSpecifiedSnafu { - given: length, - needs: 108usize * 2, - } - ); - ensure!(buf.remaining() >= length, UnexpectedEofSnafu); - let mut source = [0u8; 108]; - let mut destination = [0u8; 108]; - buf.copy_to_slice(&mut source[..]); - buf.copy_to_slice(&mut destination[..]); - // TODO(Mariell Hoversholm): Support TLVs - if length > 108 * 2 { - buf.advance(length - (108 * 2)); - } - } - + // AF_UNIX takes the same path as the other families from here: the + // address bytes are read by the `match` below and anything past them is + // TLVs, handled by the extension loop. An earlier revision read the 216 + // address bytes *here as well*, so the read below could never be + // satisfied and every AF_UNIX header failed with `UnexpectedEof`. + // + // The `length >= 216` check that block also carried is not lost: the + // `checked_sub` below fails with the same `InsufficientLengthSpecified` + // (given: length, needs: 216). let mut ext_len = length .checked_sub(address_len) @@ -1251,6 +1241,89 @@ mod parse_tests { assert_eq!(ExtensionTlv::parse(&mut chunk).unwrap(), tlv); } } + /// Pads a socket path to the 108-byte field AF_UNIX addresses use. + fn unix_path(path: &str) -> [u8; 108] { + let mut out = [0u8; 108]; + out[..path.len()].copy_from_slice(path.as_bytes()); + out + } + + /// Body of an AF_UNIX + STREAM header (everything after the signature), + /// with `tlvs` appended after the address block. + fn unix_header(source: &[u8; 108], destination: &[u8; 108], tlvs: &[u8]) -> Vec { + let mut buf = vec![ + // Proxy command + 1u8, + // Unix << 4 | Stream + (3 << 4) | 1, + ]; + let len = (108 * 2 + tlvs.len()) as u16; + buf.extend_from_slice(&len.to_be_bytes()); + buf.extend_from_slice(&source[..]); + buf.extend_from_slice(&destination[..]); + buf.extend_from_slice(tlvs); + buf + } + + /// An AF_UNIX header parses at all. The address block used to be consumed + /// twice — once by a dedicated pre-read, once by the address `match` — so + /// the second read could never be satisfied and every AF_UNIX header came + /// back as `UnexpectedEof`. + #[test] + fn test_unix() { + let source = unix_path("/var/run/haproxy/src.sock"); + let destination = unix_path("/var/run/haproxy/dst.sock"); + assert_eq!( + parse_fully(&mut &unix_header(&source, &destination, &[])[..]), + Ok(ProxyHeader::Version2 { + command: ProxyCommand::Proxy, + transport_protocol: ProxyTransportProtocol::Stream, + addresses: ProxyAddresses::Unix { + source, + destination, + }, + extensions: Vec::new(), + }), + ); + } + + /// TLVs following an AF_UNIX address are parsed like any other family's. + /// The removed pre-read skipped them wholesale. + #[test] + fn test_unix_with_tlv() { + let source = unix_path("/tmp/a.sock"); + let destination = unix_path("/tmp/b.sock"); + // ALPN "h2". + let tlvs = [PP2_TYPE_ALPN, 0, 2, 0x68, 0x32]; + assert_eq!( + parse_fully(&mut &unix_header(&source, &destination, &tlvs)[..]), + Ok(ProxyHeader::Version2 { + command: ProxyCommand::Proxy, + transport_protocol: ProxyTransportProtocol::Stream, + addresses: ProxyAddresses::Unix { + source, + destination, + }, + extensions: vec![ExtensionTlv::Alpn(b"h2".to_vec())], + }), + ); + } + + /// A declared length too small for the 216-byte address block is refused + /// rather than read past. + #[test] + fn test_unix_length_too_short() { + let mut header = unix_header(&unix_path("/tmp/a.sock"), &unix_path("/tmp/b.sock"), &[]); + // Claim 100 bytes of payload; the address block needs 216. + header[2..4].copy_from_slice(&100u16.to_be_bytes()); + assert_eq!( + parse(&mut &header[..]), + Err(ParseError::InsufficientLengthSpecified { + given: 100, + needs: 216, + }), + ); + } } #[cfg(test)] @@ -1528,4 +1601,36 @@ mod encode_tests { )), ); } + + /// What we encode for AF_UNIX, we can read back. The encoder was always + /// correct here; the parser could not consume its output. + #[test] + fn test_unix_roundtrip() { + let mut source = [0u8; 108]; + source[..11].copy_from_slice(b"/tmp/a.sock"); + let mut destination = [0u8; 108]; + destination[..11].copy_from_slice(b"/tmp/b.sock"); + + let addresses = ProxyAddresses::Unix { + source, + destination, + }; + let encoded = encode( + ProxyCommand::Proxy, + ProxyTransportProtocol::Stream, + addresses, + &[ExtensionTlv::Alpn(b"h2".to_vec())], + ) + .expect("encode"); + + assert_eq!( + crate::parse(&mut &encoded[..]), + Ok(crate::ProxyHeader::Version2 { + command: ProxyCommand::Proxy, + transport_protocol: ProxyTransportProtocol::Stream, + addresses, + extensions: vec![ExtensionTlv::Alpn(b"h2".to_vec())], + }), + ); + } }