From 72166865c511dcef8b12aaabf1c534b250e0704a Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Thu, 28 Aug 2025 21:45:17 +0800 Subject: [PATCH 01/33] chore(ktls): sort and update deps --- Cargo.lock | 375 +++++++++++++++++++++++++++++++----------------- ktls/Cargo.toml | 22 +-- 2 files changed, 253 insertions(+), 144 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f34db36..5dd156d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" @@ -28,15 +28,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.13.0" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878" +checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" dependencies = [ "aws-lc-sys", "zeroize", @@ -44,9 +44,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.28.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f7720b74ed28ca77f90769a71fd8c637a0137f6fae4ae947e1050229cff57f" +checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" dependencies = [ "bindgen", "cc", @@ -57,9 +57,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -67,7 +67,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -101,9 +101,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" [[package]] name = "bytes" @@ -113,9 +113,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.18" +version = "1.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" dependencies = [ "jobserver", "libc", @@ -133,9 +133,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" @@ -192,12 +192,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -245,20 +245,20 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", @@ -274,15 +274,15 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" [[package]] name = "home" @@ -295,14 +295,25 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" dependencies = [ "equivalent", "hashbrown", ] +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "itertools" version = "0.12.1" @@ -314,11 +325,11 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "libc", ] @@ -338,7 +349,7 @@ dependencies = [ "rcgen", "rustls", "smallvec", - "socket2", + "socket2 0.5.10", "test-case", "thiserror", "tokio", @@ -371,18 +382,18 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets", + "windows-targets 0.53.3", ] [[package]] @@ -393,9 +404,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -418,9 +429,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memoffset" @@ -439,29 +450,29 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.7" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] name = "nix" -version = "0.29.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags", "cfg-if", @@ -498,18 +509,19 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -546,9 +558,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -556,15 +568,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -597,9 +609,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "prettyplease" -version = "0.2.32" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", "syn", @@ -616,9 +628,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -634,9 +646,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rcgen" @@ -653,23 +665,23 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata 0.4.10", + "regex-syntax 0.8.6", ] [[package]] @@ -683,13 +695,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax 0.8.6", ] [[package]] @@ -700,9 +712,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "ring" @@ -712,7 +724,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -720,9 +732,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -745,9 +757,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.25" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "aws-lc-rs", "once_cell", @@ -760,15 +772,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-webpki" -version = "0.103.1" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "aws-lc-rs", "ring", @@ -776,6 +791,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "scopeguard" version = "1.2.0" @@ -819,38 +840,45 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] [[package]] name = "slab" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "subtle" version = "2.6.1" @@ -859,9 +887,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.100" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -903,18 +931,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", @@ -923,12 +951,11 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -952,20 +979,22 @@ checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "tokio" -version = "1.44.2" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "slab", + "socket2 0.6.0", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -991,15 +1020,15 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "toml_datetime", @@ -1019,9 +1048,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -1030,9 +1059,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -1087,9 +1116,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -1134,13 +1163,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1149,7 +1184,16 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", ] [[package]] @@ -1158,14 +1202,31 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -1174,53 +1235,101 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" -version = "0.7.6" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] diff --git a/ktls/Cargo.toml b/ktls/Cargo.toml index d0656fa..ea770f4 100644 --- a/ktls/Cargo.toml +++ b/ktls/Cargo.toml @@ -13,19 +13,19 @@ Configures kTLS for tokio-rustls client and server connections. rust-version = "1.75" [dependencies] -libc = { version = "0.2.155", features = ["const-extern-fn"] } -thiserror = "2" -tracing = "0.1.40" -tokio-rustls = { default-features = false, version = "0.26.0" } -rustls = { version = "0.23.12", default-features = false } -smallvec = "1.13.2" +futures-util = "0.3.31" +ktls-sys = "1.0" +libc = { version = "0.2.175", features = ["const-extern-fn"] } memoffset = "0.9.1" -pin-project-lite = "0.2.14" +num_enum = "0.7.4" +nix = { version = "0.30.1", features = ["socket", "uio", "net"] } +pin-project-lite = "0.2.16" +rustls = { version = "0.23.27", default-features = false } +smallvec = "1.15" +thiserror = "2.0" tokio = { version = "1.39.2", features = ["net", "macros", "io-util"] } -ktls-sys = "1.0.1" -num_enum = "0.7.3" -futures-util = "0.3.30" -nix = { version = "0.29.0", features = ["socket", "uio", "net"] } +tokio-rustls = { version = "0.26.0", default-features = false } +tracing = "0.1.41" [dev-dependencies] lazy_static = "1.5.0" From 197ac8a318b8a63338bd288a49b7a13e02fc789f Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 10:08:25 +0800 Subject: [PATCH 02/33] PR 62: partial: optimize workspace p --- Cargo.toml | 11 +++++++++++ ktls-sys/Cargo.toml | 16 +++++++++------- ktls/Cargo.toml | 16 +++++++++------- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a2572f3..2b61f3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,14 @@ [workspace] members = ["ktls", "ktls-sys"] resolver = "2" + +[workspace.package] +edition = "2021" +rust-version = "1.75.0" +# === Publication info === +authors = ["Amos Wenger "] +categories = ["network-programming"] +keywords = ["ktls", "linux", "tls", "rustls"] +license = "MIT OR Apache-2.0" +readme = "README.md" +repository = "https://github.com/rustls/ktls" diff --git a/ktls-sys/Cargo.toml b/ktls-sys/Cargo.toml index b94ea41..bd6223c 100644 --- a/ktls-sys/Cargo.toml +++ b/ktls-sys/Cargo.toml @@ -1,15 +1,17 @@ [package] name = "ktls-sys" version = "1.0.2" -edition = "2021" -license = "MIT OR Apache-2.0" -repository = "https://github.com/rustls/ktls-sys" -documentation = "https://docs.rs/ktls-sys" -authors = ["Amos Wenger "] -readme = "README.md" +edition.workspace = true +rust-version = "1.75.0" +# === Publication info === +authors.workspace = true +categories.workspace = true description = """ FFI bindings for `linux/tls.h` """ -rust-version = "1.75" +keywords.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true [dependencies] diff --git a/ktls/Cargo.toml b/ktls/Cargo.toml index ea770f4..74de2ba 100644 --- a/ktls/Cargo.toml +++ b/ktls/Cargo.toml @@ -1,16 +1,18 @@ [package] name = "ktls" version = "6.0.2" -edition = "2021" -license = "MIT OR Apache-2.0" -repository = "https://github.com/rustls/ktls" -documentation = "https://docs.rs/ktls" -authors = ["Amos Wenger "] -readme = "README.md" +edition.workspace = true +rust-version.workspace = true +# === Publication info === +authors.workspace = true +categories.workspace = true description = """ Configures kTLS for tokio-rustls client and server connections. """ -rust-version = "1.75" +keywords.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true [dependencies] futures-util = "0.3.31" From fed94a723b614489f00be5ed2e18e1e2e39d0d37 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 10:10:14 +0800 Subject: [PATCH 03/33] PR 62: partial: add rustfmt --- ktls-sys/src/lib.rs | 1 + rustfmt.toml | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 rustfmt.toml diff --git a/ktls-sys/src/lib.rs b/ktls-sys/src/lib.rs index 742fd1d..d8c16e3 100644 --- a/ktls-sys/src/lib.rs +++ b/ktls-sys/src/lib.rs @@ -1,4 +1,5 @@ #![allow(non_camel_case_types)] #![allow(non_snake_case)] +#[rustfmt::skip] pub mod bindings; diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..cc7a638 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,21 @@ +edition = "2021" +tab_spaces = 4 +unstable_features = true + +# imports +group_imports = "StdExternalCrate" +imports_granularity = "Module" +reorder_imports = true + +# comments +format_code_in_doc_comments = true +normalize_comments = true +wrap_comments = true + +# others +condense_wildcard_suffixes = true +format_strings = true +format_macro_matchers = true +merge_derives = false +reorder_impl_items = true +use_field_init_shorthand = true From f19c87dc1d00488428c830bbf74fd4023dd4c202 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 10:11:00 +0800 Subject: [PATCH 04/33] PR 62: partial: add lints like rustls --- ktls/src/lib.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/ktls/src/lib.rs b/ktls/src/lib.rs index c428fe5..df6825a 100644 --- a/ktls/src/lib.rs +++ b/ktls/src/lib.rs @@ -1,3 +1,26 @@ +#![doc = include_str!("../README.md")] +#![warn( + unsafe_code, + unused_must_use, + clippy::alloc_instead_of_core, + clippy::exhaustive_enums, + clippy::exhaustive_structs, + clippy::manual_let_else, + clippy::use_self, + clippy::upper_case_acronyms, + elided_lifetimes_in_paths, + missing_docs, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unused_import_braces, + unused_extern_crates, + unused_qualifications +)] + +#[cfg(not(target_os = "linux"))] +compile_error!("This crate only supports Linux"); + use ffi::{setup_tls_info, setup_ulp, KtlsCompatibilityError}; use futures_util::future::try_join_all; use ktls_sys::bindings as sys; From 9ea2dcec0e58e45e4d4206dfb46a3c1bf89b87a4 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Thu, 28 Aug 2025 22:16:10 +0800 Subject: [PATCH 05/33] refactor(setup): setup ULP and TLS params [NOT COMPILED] --- ktls/Cargo.toml | 3 + ktls/src/ffi.rs | 229 +---------------------------- ktls/src/lib.rs | 19 +-- ktls/src/setup.rs | 31 ++++ ktls/src/setup/tls.rs | 332 ++++++++++++++++++++++++++++++++++++++++++ ktls/src/setup/ulp.rs | 52 +++++++ 6 files changed, 425 insertions(+), 241 deletions(-) create mode 100644 ktls/src/setup.rs create mode 100644 ktls/src/setup/tls.rs create mode 100644 ktls/src/setup/ulp.rs diff --git a/ktls/Cargo.toml b/ktls/Cargo.toml index 74de2ba..6674e51 100644 --- a/ktls/Cargo.toml +++ b/ktls/Cargo.toml @@ -44,3 +44,6 @@ aws_lc_rs = ["rustls/aws_lc_rs", "tokio-rustls/aws_lc_rs"] aws-lc-rs = ["aws_lc_rs"] # Alias because Cargo features commonly use `-` ring = ["rustls/ring", "tokio-rustls/ring"] tls12 = ["rustls/tls12", "tokio-rustls/tls12"] + +# Expose some low-level APIs +raw-api = [] diff --git a/ktls/src/ffi.rs b/ktls/src/ffi.rs index 77af19b..34144f0 100644 --- a/ktls/src/ffi.rs +++ b/ktls/src/ffi.rs @@ -3,7 +3,7 @@ use std::os::unix::prelude::RawFd; use ktls_sys::bindings as ktls; use rustls::{ internal::msgs::{enums::AlertLevel, message::Message}, - AlertDescription, ConnectionTrafficSecrets, SupportedCipherSuite, + AlertDescription }; pub(crate) const TLS_1_2_VERSION_NUMBER: u16 = (((ktls::TLS_1_2_VERSION_MAJOR & 0xFF) as u16) << 8) @@ -12,233 +12,6 @@ pub(crate) const TLS_1_2_VERSION_NUMBER: u16 = (((ktls::TLS_1_2_VERSION_MAJOR & pub(crate) const TLS_1_3_VERSION_NUMBER: u16 = (((ktls::TLS_1_3_VERSION_MAJOR & 0xFF) as u16) << 8) | ((ktls::TLS_1_3_VERSION_MINOR & 0xFF) as u16); -/// `setsockopt` level constant: TCP -const SOL_TCP: libc::c_int = 6; - -/// `setsockopt` SOL_TCP name constant: "upper level protocol" -const TCP_ULP: libc::c_int = 31; - -/// `setsockopt` level constant: TLS -const SOL_TLS: libc::c_int = 282; - -/// `setsockopt` SOL_TLS level constant: transmit (write) -const TLS_TX: libc::c_int = 1; - -/// `setsockopt` SOL_TLS level constant: receive (read) -const TLX_RX: libc::c_int = 2; - -pub fn setup_ulp(fd: RawFd) -> std::io::Result<()> { - unsafe { - if libc::setsockopt( - fd, - SOL_TCP, - TCP_ULP, - "tls".as_ptr() as *const libc::c_void, - 3, - ) < 0 - { - return Err(std::io::Error::last_os_error()); - } - } - - Ok(()) -} - -#[derive(Clone, Copy, Debug)] -pub enum Direction { - // Transmit - Tx, - // Receive - Rx, -} - -impl From for libc::c_int { - fn from(val: Direction) -> Self { - match val { - Direction::Tx => TLS_TX, - Direction::Rx => TLX_RX, - } - } -} - -#[allow(dead_code)] -pub enum CryptoInfo { - AesGcm128(ktls::tls12_crypto_info_aes_gcm_128), - AesGcm256(ktls::tls12_crypto_info_aes_gcm_256), - AesCcm128(ktls::tls12_crypto_info_aes_ccm_128), - Chacha20Poly1305(ktls::tls12_crypto_info_chacha20_poly1305), - Sm4Gcm(ktls::tls12_crypto_info_sm4_gcm), - Sm4Ccm(ktls::tls12_crypto_info_sm4_ccm), -} - -impl CryptoInfo { - /// Return the system struct as a pointer. - pub fn as_ptr(&self) -> *const libc::c_void { - match self { - CryptoInfo::AesGcm128(info) => info as *const _ as *const libc::c_void, - CryptoInfo::AesGcm256(info) => info as *const _ as *const libc::c_void, - CryptoInfo::AesCcm128(info) => info as *const _ as *const libc::c_void, - CryptoInfo::Chacha20Poly1305(info) => info as *const _ as *const libc::c_void, - CryptoInfo::Sm4Gcm(info) => info as *const _ as *const libc::c_void, - CryptoInfo::Sm4Ccm(info) => info as *const _ as *const libc::c_void, - } - } - - /// Return the system struct size. - pub fn size(&self) -> usize { - match self { - CryptoInfo::AesGcm128(_) => std::mem::size_of::(), - CryptoInfo::AesGcm256(_) => std::mem::size_of::(), - CryptoInfo::AesCcm128(_) => std::mem::size_of::(), - CryptoInfo::Chacha20Poly1305(_) => { - std::mem::size_of::() - } - CryptoInfo::Sm4Gcm(_) => std::mem::size_of::(), - CryptoInfo::Sm4Ccm(_) => std::mem::size_of::(), - } - } -} - -#[derive(thiserror::Error, Debug)] -pub enum KtlsCompatibilityError { - #[error("cipher suite not supported with kTLS: {0:?}")] - UnsupportedCipherSuite(SupportedCipherSuite), - - #[error("wrong size key")] - WrongSizeKey, - - #[error("wrong size iv")] - WrongSizeIv, -} - -impl CryptoInfo { - /// Try to convert rustls cipher suite and secrets into a `CryptoInfo`. - pub fn from_rustls( - cipher_suite: SupportedCipherSuite, - (seq, secrets): (u64, ConnectionTrafficSecrets), - ) -> Result { - let version = match cipher_suite { - SupportedCipherSuite::Tls12(..) => TLS_1_2_VERSION_NUMBER, - SupportedCipherSuite::Tls13(..) => TLS_1_3_VERSION_NUMBER, - }; - - Ok(match secrets { - ConnectionTrafficSecrets::Aes128Gcm { key, iv } => { - // see https://github.com/rustls/rustls/issues/1833, between - // rustls 0.21 and 0.22, the extract_keys codepath was changed, - // so, for TLS 1.2, both GCM-128 and GCM-256 return the - // Aes128Gcm variant. - - match key.as_ref().len() { - 16 => CryptoInfo::AesGcm128(ktls::tls12_crypto_info_aes_gcm_128 { - info: ktls::tls_crypto_info { - version, - cipher_type: ktls::TLS_CIPHER_AES_GCM_128 as _, - }, - iv: iv - .as_ref() - .get(4..) - .expect("AES-GCM-128 iv is 8 bytes") - .try_into() - .expect("AES-GCM-128 iv is 8 bytes"), - key: key - .as_ref() - .try_into() - .expect("AES-GCM-128 key is 16 bytes"), - salt: iv - .as_ref() - .get(..4) - .expect("AES-GCM-128 salt is 4 bytes") - .try_into() - .expect("AES-GCM-128 salt is 4 bytes"), - rec_seq: seq.to_be_bytes(), - }), - 32 => CryptoInfo::AesGcm256(ktls::tls12_crypto_info_aes_gcm_256 { - info: ktls::tls_crypto_info { - version, - cipher_type: ktls::TLS_CIPHER_AES_GCM_256 as _, - }, - iv: iv - .as_ref() - .get(4..) - .expect("AES-GCM-256 iv is 8 bytes") - .try_into() - .expect("AES-GCM-256 iv is 8 bytes"), - key: key - .as_ref() - .try_into() - .expect("AES-GCM-256 key is 32 bytes"), - salt: iv - .as_ref() - .get(..4) - .expect("AES-GCM-256 salt is 4 bytes") - .try_into() - .expect("AES-GCM-256 salt is 4 bytes"), - rec_seq: seq.to_be_bytes(), - }), - _ => unreachable!("GCM key length is not 16 or 32"), - } - } - ConnectionTrafficSecrets::Aes256Gcm { key, iv } => { - CryptoInfo::AesGcm256(ktls::tls12_crypto_info_aes_gcm_256 { - info: ktls::tls_crypto_info { - version, - cipher_type: ktls::TLS_CIPHER_AES_GCM_256 as _, - }, - iv: iv - .as_ref() - .get(4..) - .expect("AES-GCM-256 iv is 8 bytes") - .try_into() - .expect("AES-GCM-256 iv is 8 bytes"), - key: key - .as_ref() - .try_into() - .expect("AES-GCM-256 key is 32 bytes"), - salt: iv - .as_ref() - .get(..4) - .expect("AES-GCM-256 salt is 4 bytes") - .try_into() - .expect("AES-GCM-256 salt is 4 bytes"), - rec_seq: seq.to_be_bytes(), - }) - } - ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv } => { - CryptoInfo::Chacha20Poly1305(ktls::tls12_crypto_info_chacha20_poly1305 { - info: ktls::tls_crypto_info { - version, - cipher_type: ktls::TLS_CIPHER_CHACHA20_POLY1305 as _, - }, - iv: iv - .as_ref() - .try_into() - .expect("Chacha20-Poly1305 iv is 12 bytes"), - key: key - .as_ref() - .try_into() - .expect("Chacha20-Poly1305 key is 32 bytes"), - salt: ktls::__IncompleteArrayField::new(), - rec_seq: seq.to_be_bytes(), - }) - } - _ => { - return Err(KtlsCompatibilityError::UnsupportedCipherSuite(cipher_suite)); - } - }) - } -} - -pub fn setup_tls_info(fd: RawFd, dir: Direction, info: CryptoInfo) -> Result<(), crate::Error> { - let ret = unsafe { libc::setsockopt(fd, SOL_TLS, dir.into(), info.as_ptr(), info.size() as _) }; - if ret < 0 { - return Err(crate::Error::TlsCryptoInfoError( - std::io::Error::last_os_error(), - )); - } - Ok(()) -} - const TLS_SET_RECORD_TYPE: libc::c_int = 1; const ALERT: u8 = 0x15; diff --git a/ktls/src/lib.rs b/ktls/src/lib.rs index df6825a..29de74e 100644 --- a/ktls/src/lib.rs +++ b/ktls/src/lib.rs @@ -21,7 +21,8 @@ #[cfg(not(target_os = "linux"))] compile_error!("This crate only supports Linux"); -use ffi::{setup_tls_info, setup_ulp, KtlsCompatibilityError}; +pub mod setup; + use futures_util::future::try_join_all; use ktls_sys::bindings as sys; use rustls::{Connection, SupportedCipherSuite, SupportedProtocolVersion}; @@ -48,7 +49,6 @@ use tokio::{ }; mod ffi; -pub use crate::ffi::CryptoInfo; mod async_read_ready; pub use async_read_ready::AsyncReadReady; @@ -245,9 +245,6 @@ pub enum Error { #[error("failed to enable TLS ULP (upper level protocol): {0}")] UlpError(#[source] std::io::Error), - #[error("kTLS compatibility error: {0}")] - KtlsCompatibility(#[from] KtlsCompatibilityError), - #[error("failed to export secrets")] ExportSecrets(#[source] rustls::Error), @@ -300,7 +297,7 @@ where let (io, conn) = stream.into_inner(); let io = io.io; - setup_inner(io.as_raw_fd(), Connection::Client(conn))?; + setup_inner(io, Connection::Client(conn))?; Ok(KtlsStream::new(io, drained)) } @@ -343,7 +340,7 @@ async fn drain(stream: &mut (impl AsyncRead + Unpin)) -> std::io::Result Result<(), Error> { +fn setup_inner(socket: &Fd, conn: Connection) -> Result<(), Error> { let cipher_suite = match conn.negotiated_cipher_suite() { Some(cipher_suite) => cipher_suite, None => { @@ -356,13 +353,9 @@ fn setup_inner(fd: RawFd, conn: Connection) -> Result<(), Error> { Err(err) => return Err(Error::ExportSecrets(err)), }; - ffi::setup_ulp(fd).map_err(Error::UlpError)?; - - let tx = CryptoInfo::from_rustls(cipher_suite, secrets.tx)?; - setup_tls_info(fd, ffi::Direction::Tx, tx)?; + setup::setup_ulp(socket).map_err(Error::UlpError)?; - let rx = CryptoInfo::from_rustls(cipher_suite, secrets.rx)?; - setup_tls_info(fd, ffi::Direction::Rx, rx)?; + setup::setup_tls_params(socket, cipher_suite, secrets)?; Ok(()) } diff --git a/ktls/src/setup.rs b/ktls/src/setup.rs new file mode 100644 index 0000000..9ca743a --- /dev/null +++ b/ktls/src/setup.rs @@ -0,0 +1,31 @@ +//! Transport Layer Security (TLS) is a Upper Layer Protocol (ULP) that runs +//! over TCP. TLS provides end-to-end data integrity and confidentiality. +//! +//! Once the TCP connection is established, sets the TLS ULP, which allows us to +//! set/get TLS socket options. +//! +//! This module provides the [`setup_ulp`] function, which sets the ULP (Upper +//! Layer Protocol) to TLS for a TCP socket. The user can also determine whether +//! the kernel supports kTLS with [`setup_ulp`]. +//! +//! After the TLS handshake is completed, we have all the parameters required to +//! move the data-path to the kernel. There is a separate socket option for +//! moving the transmit and the receive into the kernel. +//! +//! This module provides the low-level [`setup_tls_params`] function (when +//! feature `raw-api` is enabled), which sets the Kernel TLS parameters on the +//! TCP socket, allowing the kernel to handle encryption and decryption of the +//! TLS data. + +#![allow(clippy::module_name_repetitions)] + +mod tls; +mod ulp; + +#[cfg(not(feature = "raw-api"))] +pub(crate) use tls::{setup_tls_params, setup_tls_params_rx, setup_tls_params_tx}; +#[cfg(feature = "raw-api")] +pub use tls::{ + setup_tls_params, setup_tls_params_rx, setup_tls_params_tx, TlsCryptoInfoRx, TlsCryptoInfoTx, +}; +pub use ulp::{setup_ulp, SetupError}; diff --git a/ktls/src/setup/tls.rs b/ktls/src/setup/tls.rs new file mode 100644 index 0000000..495263d --- /dev/null +++ b/ktls/src/setup/tls.rs @@ -0,0 +1,332 @@ +//! See the [module-level documentation](crate::setup) for more details. + +#![allow(rustdoc::private_intra_doc_links)] + +use std::os::fd::{AsFd, AsRawFd}; +use std::{io, mem}; + +use libc::c_int; +use nix::errno::Errno; +use nix::sys::socket::{setsockopt, SetSockOpt}; +use rustls::crypto::cipher::NONCE_LEN; +use rustls::{ConnectionTrafficSecrets, ExtractedSecrets, SupportedCipherSuite}; + +#[cfg_attr(not(feature = "raw-api"), allow(unreachable_pub))] +/// Sets the kTLS parameters on the socket after the TLS handshake is completed. +/// +/// ## Errors +/// +/// * Invalid crypto materials. +/// * Syscall error. +pub fn setup_tls_params( + socket: &S, + cipher_suite: SupportedCipherSuite, + secrets: ExtractedSecrets, +) -> io::Result<()> { + let (tx, rx) = TlsCryptoInfo::extract_from(cipher_suite, secrets)?; + + setsockopt(socket, TcpTlsTx {}, &tx) + .and_then(|()| setsockopt(socket, TcpTlsRx {}, &rx)) + .map_err(io::Error::from) +} + +#[cfg_attr(not(feature = "raw-api"), allow(unreachable_pub))] +/// Like [`setup_tls_params`], but only sets up the transmit direction. +/// +/// This is useful when performing key update. +/// +/// ## Errors +/// +/// See [`setup_tls_params`]. +pub fn setup_tls_params_tx( + socket: &S, + cipher_suite: SupportedCipherSuite, + (seq, secrets): (u64, ConnectionTrafficSecrets), +) -> io::Result<()> { + let tx = TlsCryptoInfoTx::extract_tx_from(cipher_suite, (seq, secrets))?; + + setsockopt(socket, TcpTlsTx {}, &tx) + .map_err(io::Error::from) + +} + +#[cfg_attr(not(feature = "raw-api"), allow(unreachable_pub))] +/// Like [`setup_tls_params`], but only sets up the receive direction. +/// +/// This is useful when performing key update. +/// +/// ## Errors +/// +/// See [`setup_tls_params`]. +pub fn setup_tls_params_rx( + socket: &S, + cipher_suite: SupportedCipherSuite, + (seq, secrets): (u64, ConnectionTrafficSecrets), +) -> io::Result<()> { + let rx = TlsCryptoInfoRx::extract_rx_from(cipher_suite, (seq, secrets))?; + + setsockopt(socket, TcpTlsRx {}, &rx) + .map_err(io::Error::from) + +} + +#[derive(Debug, Clone, Copy)] +/// Sets the Kernel TLS read/write parameters on the TCP socket. +struct TcpTls {} + +/// See [`TcpTls`]. +type TcpTlsTx = TcpTls<{ libc::TLS_TX }>; + +/// See [`TcpTls`]. +type TcpTlsRx = TcpTls<{ libc::TLS_RX }>; + +impl SetSockOpt for TcpTls { + type Val = TlsCryptoInfo; + + fn set(&self, fd: &F, val: &Self::Val) -> nix::Result<()> { + let (ffi_ptr, ffi_len) = val.0.as_ffi_value(); + + #[allow(unsafe_code)] + // SAFETY: syscall + unsafe { + let res = libc::setsockopt( + fd.as_fd().as_raw_fd(), + libc::SOL_TLS, + DIRECTION, + ffi_ptr, + ffi_len, + ); + Errno::result(res)?; + } + + Ok(()) + } +} + +#[cfg_attr(not(feature = "raw-api"), allow(unreachable_pub))] +#[repr(transparent)] +/// Sets the Kernel TLS read/write parameters on the TCP socket. +pub struct TlsCryptoInfo(TlsCryptoInfoImpl); + +#[cfg_attr(not(feature = "raw-api"), allow(unreachable_pub))] +/// See [`TlsCryptoInfo`]. +pub type TlsCryptoInfoTx = TlsCryptoInfo<{ libc::TLS_TX }>; + +#[cfg_attr(not(feature = "raw-api"), allow(unreachable_pub))] +/// See [`TlsCryptoInfo`]. +pub type TlsCryptoInfoRx = TlsCryptoInfo<{ libc::TLS_RX }>; + +#[cfg(feature = "raw-api")] +impl TlsCryptoInfo { + /// Create a custom [`TlsCryptoInfo`] from the given + /// [`libc::tls12_crypto_info_aes_gcm_128`]. + /// + /// This is for advanced usage only. + pub const fn custom_aes_128_gcm(inner: libc::tls12_crypto_info_aes_gcm_128) -> Self { + Self(TlsCryptoInfoImpl::AesGcm128(inner)) + } + + /// Create a custom [`TlsCryptoInfo`] from the given + /// [`libc::tls12_crypto_info_aes_gcm_256`]. + pub const fn custom_aes_256_gcm(inner: libc::tls12_crypto_info_aes_gcm_256) -> Self { + Self(TlsCryptoInfoImpl::AesGcm256(inner)) + } + + /// Create a custom [`TlsCryptoInfo`] from the given + /// [`libc::tls12_crypto_info_chacha20_poly1305`]. + pub const fn custom_chacha20_poly1305( + inner: libc::tls12_crypto_info_chacha20_poly1305, + ) -> Self { + Self(TlsCryptoInfoImpl::Chacha20Poly1305(inner)) + } + + /// Sets the kTLS parameters on the given file descriptor. + pub fn set(&self, fd: &Fd) -> io::Result<()> { + setsockopt(fd, TcpTls {}, self).map_err(io::Error::from) + } +} + +impl TlsCryptoInfo { + /// Extract the bidirectional `TlsCryptoInfo` from the given + /// `SupportedCipherSuite` and `ExtractedSecrets`. + /// + /// ## Errors + /// + /// * Invalid crypto materials + fn extract_from( + cipher_suite: SupportedCipherSuite, + secrets: ExtractedSecrets, + ) -> Result<(TlsCryptoInfoTx, TlsCryptoInfoRx), InvalidCryptoInfo> { + Ok(( + TlsCryptoInfo(TlsCryptoInfoImpl::extract_from(cipher_suite, secrets.tx)?), + TlsCryptoInfo(TlsCryptoInfoImpl::extract_from(cipher_suite, secrets.rx)?), + )) + } +} + +impl TlsCryptoInfoTx { + fn extract_tx_from( + cipher_suite: SupportedCipherSuite, + (seq, secrets): (u64, ConnectionTrafficSecrets), + ) -> Result { + TlsCryptoInfoImpl::extract_from(cipher_suite, (seq, secrets)).map(TlsCryptoInfo) + } +} + +impl TlsCryptoInfoRx { + fn extract_rx_from( + cipher_suite: SupportedCipherSuite, + (seq, secrets): (u64, ConnectionTrafficSecrets), + ) -> Result { + TlsCryptoInfoImpl::extract_from(cipher_suite, (seq, secrets)).map(TlsCryptoInfo) + } +} + +#[repr(C)] +#[allow(unused)] +/// A wrapper around the system `tls12_crypto_info_*` structs, use with setting +/// up the kTLS r/w parameters on the TCP socket. +/// +/// This is originated from the `nix` crate, which currently does not support +/// `AES-128-CCM` and `SM4`, so we implement our own version here. +enum TlsCryptoInfoImpl { + AesGcm128(libc::tls12_crypto_info_aes_gcm_128), + AesGcm256(libc::tls12_crypto_info_aes_gcm_256), + Chacha20Poly1305(libc::tls12_crypto_info_chacha20_poly1305), + AesCcm128(libc::tls12_crypto_info_aes_ccm_128), + Sm4Gcm(libc::tls12_crypto_info_sm4_gcm), + Sm4Ccm(libc::tls12_crypto_info_sm4_ccm), +} + +impl TlsCryptoInfoImpl { + #[allow(unused_qualifications)] + #[allow(clippy::cast_possible_truncation)] // Since Rust 2021 doesn't have `size_of_val` included in prelude. + #[inline] + fn as_ffi_value(&self) -> (*const libc::c_void, libc::socklen_t) { + match self { + Self::AesGcm128(crypto_info) => ( + <*const _>::cast(crypto_info), + mem::size_of_val(crypto_info) as libc::socklen_t, + ), + Self::AesGcm256(crypto_info) => ( + <*const _>::cast(crypto_info), + mem::size_of_val(crypto_info) as libc::socklen_t, + ), + Self::AesCcm128(crypto_info) => ( + <*const _>::cast(crypto_info), + mem::size_of_val(crypto_info) as libc::socklen_t, + ), + Self::Chacha20Poly1305(crypto_info) => ( + <*const _>::cast(crypto_info), + mem::size_of_val(crypto_info) as libc::socklen_t, + ), + Self::Sm4Gcm(crypto_info) => ( + <*const _>::cast(crypto_info), + mem::size_of_val(crypto_info) as libc::socklen_t, + ), + Self::Sm4Ccm(crypto_info) => ( + <*const _>::cast(crypto_info), + mem::size_of_val(crypto_info) as libc::socklen_t, + ), + } + } + + /// Extract the `TlsCryptoInfoImpl` from the given + /// `SupportedCipherSuite` and `ConnectionTrafficSecrets`. + fn extract_from( + cipher_suite: SupportedCipherSuite, + (seq, secrets): (u64, ConnectionTrafficSecrets), + ) -> Result { + let version = match cipher_suite { + #[cfg(feature = "tls12")] + SupportedCipherSuite::Tls12(..) => libc::TLS_1_2_VERSION, + SupportedCipherSuite::Tls13(..) => libc::TLS_1_3_VERSION, + }; + + Ok(match secrets { + ConnectionTrafficSecrets::Aes128Gcm { key, iv } => { + // see https://github.com/rustls/rustls/issues/1833, between + // rustls 0.21 and 0.22, the extract_keys codepath was changed, + // so, for TLS 1.2, both GCM-128 and GCM-256 return the + // Aes128Gcm variant. + // + // This issue is fixed since rustls 0.23. + + let iv_and_salt: &[u8; NONCE_LEN] = iv.as_ref().try_into().unwrap(); + + Self::AesGcm128(libc::tls12_crypto_info_aes_gcm_128 { + info: libc::tls_crypto_info { + version, + cipher_type: libc::TLS_CIPHER_AES_GCM_128, + }, + iv: iv_and_salt[4..].try_into().unwrap(), + key: key + .as_ref() + .try_into() + .map_err(|_| InvalidCryptoInfo::WrongSizeKey)?, + salt: iv_and_salt[..4].try_into().unwrap(), + rec_seq: seq.to_be_bytes(), + }) + } + ConnectionTrafficSecrets::Aes256Gcm { key, iv } => { + let iv_and_salt: &[u8; NONCE_LEN] = iv.as_ref().try_into().unwrap(); + + Self::AesGcm256(libc::tls12_crypto_info_aes_gcm_256 { + info: libc::tls_crypto_info { + version, + cipher_type: libc::TLS_CIPHER_AES_GCM_256, + }, + iv: iv_and_salt[4..].try_into().unwrap(), + key: key + .as_ref() + .try_into() + .map_err(|_| InvalidCryptoInfo::WrongSizeKey)?, + salt: iv_and_salt[..4].try_into().unwrap(), + rec_seq: seq.to_be_bytes(), + }) + } + ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv } => { + Self::Chacha20Poly1305(libc::tls12_crypto_info_chacha20_poly1305 { + info: libc::tls_crypto_info { + version, + cipher_type: libc::TLS_CIPHER_CHACHA20_POLY1305, + }, + iv: iv.as_ref().try_into().unwrap(), + key: key + .as_ref() + .try_into() + .map_err(|_| InvalidCryptoInfo::WrongSizeKey)?, + salt: [], + rec_seq: seq.to_be_bytes(), + }) + } + _ => { + return Err(InvalidCryptoInfo::UnsupportedCipherSuite(cipher_suite)); + } + }) + } +} + +#[derive(Debug)] +#[derive(thiserror::Error)] +/// Crypto material is invalid, e.g., wrong size key or IV. +#[non_exhaustive] +enum InvalidCryptoInfo { + #[error("Wrong size key")] + /// The provided key has an incorrect size (unlikely). + WrongSizeKey, + + #[error("Wrong size iv")] + /// The provided IV has an incorrect size (unlikely). + WrongSizeIv, + + #[error("the negotiated cipher suite [{0:?}] is not supported")] + /// The negotiated cipher suite is not supported by this crate. + UnsupportedCipherSuite(SupportedCipherSuite), +} + +impl From for io::Error { + fn from(err: InvalidCryptoInfo) -> Self { + io::Error::new(io::ErrorKind::InvalidInput, err) + } +} \ No newline at end of file diff --git a/ktls/src/setup/ulp.rs b/ktls/src/setup/ulp.rs new file mode 100644 index 0000000..26786b2 --- /dev/null +++ b/ktls/src/setup/ulp.rs @@ -0,0 +1,52 @@ +//! See the [module-level documentation](crate::setup) for more details. + +use std::os::fd::AsFd; +use std::{fmt, io}; + +use nix::errno::Errno; +use nix::sys::socket::{setsockopt, sockopt}; + +/// Sets the TLS Upper Layer Protocol (ULP). +/// +/// This should be called before performing any I/O operations on the +/// socket. +/// +/// # Errors +/// +/// [`SetupError`]. +/// +/// If the error is caused by the system not supporting kTLS, such as kernel +/// module `tls` not being enabled or the kernel version being too old, will +/// have the original socket returned, see [`SetupError::socket`]. +pub fn setup_ulp(socket: S) -> Result> { + match setsockopt(&socket, sockopt::TcpUlp::default(), b"tls") { + Ok(()) => Ok(socket), + Err(err) if err == Errno::ENOENT => Err(SetupError { + error: io::Error::from(err), + socket: Some(socket), + }), + Err(err) => Err(SetupError { + error: io::Error::from(err), + socket: None, + }), + } +} + +#[allow(clippy::exhaustive_structs)] +#[derive(thiserror::Error)] +#[error("{error}")] +/// An error that occurred while configuring the ULP. +pub struct SetupError { + #[source] + /// The I/O error that occurred while configuring the ULP. + pub error: io::Error, + + /// The original I/O socket. + pub socket: Option, +} + +impl fmt::Debug for SetupError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.error.fmt(f) + } +} From 91995c98826f495bf983b946fca9fe24b4199656 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 10:16:05 +0800 Subject: [PATCH 06/33] PR 62: partial: make logging optional [NOT COMPILED] --- Cargo.lock | 1 + ktls/Cargo.toml | 9 +++++++- ktls/src/lib.rs | 1 + ktls/src/log.rs | 61 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 ktls/src/log.rs diff --git a/Cargo.lock b/Cargo.lock index 5dd156d..7393612 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -341,6 +341,7 @@ dependencies = [ "ktls-sys 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static", "libc", + "log", "memoffset", "nix", "num_enum", diff --git a/ktls/Cargo.toml b/ktls/Cargo.toml index 6674e51..4dd3b30 100644 --- a/ktls/Cargo.toml +++ b/ktls/Cargo.toml @@ -18,6 +18,7 @@ repository.workspace = true futures-util = "0.3.31" ktls-sys = "1.0" libc = { version = "0.2.175", features = ["const-extern-fn"] } +log = { version = "0.4.27", optional = true } memoffset = "0.9.1" num_enum = "0.7.4" nix = { version = "0.30.1", features = ["socket", "uio", "net"] } @@ -27,7 +28,7 @@ smallvec = "1.15" thiserror = "2.0" tokio = { version = "1.39.2", features = ["net", "macros", "io-util"] } tokio-rustls = { version = "0.26.0", default-features = false } -tracing = "0.1.41" +tracing = { version = "0.1.41", optional = true } [dev-dependencies] lazy_static = "1.5.0" @@ -47,3 +48,9 @@ tls12 = ["rustls/tls12", "tokio-rustls/tls12"] # Expose some low-level APIs raw-api = [] + +# Logging support with `log` crate +log = ["dep:log"] + +# Logging support with `tracing` crate +tracing = ["dep:tracing"] diff --git a/ktls/src/lib.rs b/ktls/src/lib.rs index 29de74e..dd5f5a6 100644 --- a/ktls/src/lib.rs +++ b/ktls/src/lib.rs @@ -49,6 +49,7 @@ use tokio::{ }; mod ffi; +pub mod log; mod async_read_ready; pub use async_read_ready::AsyncReadReady; diff --git a/ktls/src/log.rs b/ktls/src/log.rs new file mode 100644 index 0000000..e52aa97 --- /dev/null +++ b/ktls/src/log.rs @@ -0,0 +1,61 @@ +//! Logger macros. + +#[macro_export] +#[doc(hidden)] +macro_rules! trace { + ($($tt:tt)*) => { + #[cfg(feature = "tracing")] + tracing::trace!($($tt)*); + + #[cfg(all(feature = "log", not(feature = "tracing")))] + log::trace!($($tt)*); + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! debug { + ($($tt:tt)*) => { + #[cfg(feature = "tracing")] + tracing::debug!($($tt)*); + + #[cfg(all(feature = "log", not(feature = "tracing")))] + log::debug!($($tt)*); + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! info { + ($($tt:tt)*) => { + #[cfg(feature = "tracing")] + tracing::info!($($tt)*); + + #[cfg(all(feature = "log", not(feature = "tracing")))] + log::info!($($tt)*); + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! warn { + ($($tt:tt)*) => { + #[cfg(feature = "tracing")] + tracing::warn!($($tt)*); + + #[cfg(all(feature = "log", not(feature = "tracing")))] + log::warn!($($tt)*); + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! error { + ($($tt:tt)*) => { + #[cfg(feature = "tracing")] + tracing::error!($($tt)*); + + #[cfg(all(feature = "log", not(feature = "tracing")))] + log::error!($($tt)*); + }; +} From 4545957720fb16c5f9db57f7447addd1da2b608e Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 10:21:22 +0800 Subject: [PATCH 07/33] PR 62: partial: implement enum KeyUpdateRequest since rustls doesn't export it [NOT COMPILED] --- ktls/src/lib.rs | 1 + ktls/src/protocol.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 ktls/src/protocol.rs diff --git a/ktls/src/lib.rs b/ktls/src/lib.rs index dd5f5a6..417452f 100644 --- a/ktls/src/lib.rs +++ b/ktls/src/lib.rs @@ -50,6 +50,7 @@ use tokio::{ mod ffi; pub mod log; +mod protocol; mod async_read_ready; pub use async_read_ready::AsyncReadReady; diff --git a/ktls/src/protocol.rs b/ktls/src/protocol.rs new file mode 100644 index 0000000..fc5385a --- /dev/null +++ b/ktls/src/protocol.rs @@ -0,0 +1,80 @@ +//! TLS protocol enums that are not publically exposed by rustls. + +#![allow(non_upper_case_globals)] + +use std::fmt; + +macro_rules! c_enum { + { + $( #[$attr:meta] )* + $vis:vis enum $name:ident: $repr:ty { + $( + $( #[$vattr:meta] )* + $variant:ident = $value:expr + ),* $(,)? + } + } => { + $( #[$attr] )* + #[repr(transparent)] + $vis struct $name($vis $repr); + + impl $name { + $( + $( #[$vattr] )* + $vis const $variant: Self = Self($value); + )* + } + + impl fmt::Debug for $name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + $( const $variant: $repr = $name::$variant.0; )* + + let text = match self.0 { + $( $variant => concat!(stringify!($name), "::", stringify!($variant)), )* + _ => return f.debug_tuple(stringify!($name)).field(&self.0).finish() + }; + + f.write_str(text) + } + } + + impl fmt::Display for $name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + $( const $variant: $repr = $name::$variant.0; )* + + let text = match self.0 { + $( $variant => stringify!($variant), )* + _ => return <$repr as fmt::Display>::fmt(&self.0, f) + }; + + f.write_str(text) + } + } + + impl From<$repr> for $name { + fn from(value: $repr) -> Self { + Self(value) + } + } + + impl From<$name> for $repr { + fn from(value: $name) -> Self { + value.0 + } + } + } +} + +/// `KeyUpdate`, not requested +pub(crate) const KEY_UPDATE_NOT_REQUESTED: u8 = 0; + +/// `KeyUpdate`, requested +pub(crate) const KEY_UPDATE_REQUESTED: u8 = 1; + +c_enum! { + #[derive(Copy, Clone, Eq, PartialEq)] + pub(crate) enum KeyUpdateRequest: u8 { + UpdateNotRequested = KEY_UPDATE_NOT_REQUESTED, + UpdateRequested = KEY_UPDATE_REQUESTED + } +} From 77424082df624bdbd5cd7a97a9a576dc41e27636 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Thu, 28 Aug 2025 22:25:47 +0800 Subject: [PATCH 08/33] refactor(ktls): unified error type [NOT COMPILED] --- ktls/src/error.rs | 45 +++++++++++++++++++++++++++++++++++++++++++ ktls/src/lib.rs | 19 +----------------- ktls/src/setup/tls.rs | 39 +++++++++---------------------------- 3 files changed, 55 insertions(+), 48 deletions(-) create mode 100644 ktls/src/error.rs diff --git a/ktls/src/error.rs b/ktls/src/error.rs new file mode 100644 index 0000000..8a8ff64 --- /dev/null +++ b/ktls/src/error.rs @@ -0,0 +1,45 @@ +//! Error types for the `ktls` crate + +use std::io; + +use rustls::SupportedCipherSuite; + +#[non_exhaustive] +#[derive(Debug, thiserror::Error)] +/// Unified error type for this crate +pub enum Error { + #[error(transparent)] + /// Invalid crypto material, e.g., wrong size key or IV. + InvalidCryptoInfo(#[from] InvalidCryptoInfo), + + #[error(transparent)] + /// General IO error. + IO(#[from] io::Error), +} + +impl From for io::Error { + fn from(error: Error) -> Self { + match error { + Error::IO(error) => error, + _ => Self::other(error), + } + } +} + +#[derive(Debug)] +#[derive(thiserror::Error)] +/// Crypto material is invalid, e.g., wrong size key or IV. +#[non_exhaustive] +pub enum InvalidCryptoInfo { + #[error("Wrong size key")] + /// The provided key has an incorrect size (unlikely). + WrongSizeKey, + + #[error("Wrong size iv")] + /// The provided IV has an incorrect size (unlikely). + WrongSizeIv, + + #[error("the negotiated cipher suite [{0:?}] is not supported")] + /// The negotiated cipher suite is not supported by this crate. + UnsupportedCipherSuite(SupportedCipherSuite), +} \ No newline at end of file diff --git a/ktls/src/lib.rs b/ktls/src/lib.rs index 417452f..5833c6d 100644 --- a/ktls/src/lib.rs +++ b/ktls/src/lib.rs @@ -51,6 +51,7 @@ use tokio::{ mod ffi; pub mod log; mod protocol; +pub mod error; mod async_read_ready; pub use async_read_ready::AsyncReadReady; @@ -242,24 +243,6 @@ fn sample_cipher_setup(sock: &TcpStream, cipher_suite: SupportedCipherSuite) -> Ok(()) } -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("failed to enable TLS ULP (upper level protocol): {0}")] - UlpError(#[source] std::io::Error), - - #[error("failed to export secrets")] - ExportSecrets(#[source] rustls::Error), - - #[error("failed to configure tx/rx (unsupported cipher?): {0}")] - TlsCryptoInfoError(#[source] std::io::Error), - - #[error("an I/O occured while draining the rustls stream: {0}")] - DrainError(#[source] std::io::Error), - - #[error("no negotiated cipher suite: call config_ktls_* only /after/ the handshake")] - NoNegotiatedCipherSuite, -} - /// Configure kTLS for this socket. If this call succeeds, data can be written /// and read from this socket, and the kernel takes care of encryption /// transparently. I'm not clear how rekeying is handled (probably via control diff --git a/ktls/src/setup/tls.rs b/ktls/src/setup/tls.rs index 495263d..fab8ed5 100644 --- a/ktls/src/setup/tls.rs +++ b/ktls/src/setup/tls.rs @@ -11,6 +11,8 @@ use nix::sys::socket::{setsockopt, SetSockOpt}; use rustls::crypto::cipher::NONCE_LEN; use rustls::{ConnectionTrafficSecrets, ExtractedSecrets, SupportedCipherSuite}; +use crate::error::{Error, InvalidCryptoInfo}; + #[cfg_attr(not(feature = "raw-api"), allow(unreachable_pub))] /// Sets the kTLS parameters on the socket after the TLS handshake is completed. /// @@ -22,12 +24,13 @@ pub fn setup_tls_params( socket: &S, cipher_suite: SupportedCipherSuite, secrets: ExtractedSecrets, -) -> io::Result<()> { +) -> Result<(), Error> { let (tx, rx) = TlsCryptoInfo::extract_from(cipher_suite, secrets)?; setsockopt(socket, TcpTlsTx {}, &tx) .and_then(|()| setsockopt(socket, TcpTlsRx {}, &rx)) .map_err(io::Error::from) + .map_err(Error::IO) } #[cfg_attr(not(feature = "raw-api"), allow(unreachable_pub))] @@ -42,12 +45,12 @@ pub fn setup_tls_params_tx( socket: &S, cipher_suite: SupportedCipherSuite, (seq, secrets): (u64, ConnectionTrafficSecrets), -) -> io::Result<()> { +) -> Result<(), Error> { let tx = TlsCryptoInfoTx::extract_tx_from(cipher_suite, (seq, secrets))?; setsockopt(socket, TcpTlsTx {}, &tx) .map_err(io::Error::from) - + .map_err(Error::IO) } #[cfg_attr(not(feature = "raw-api"), allow(unreachable_pub))] @@ -62,12 +65,12 @@ pub fn setup_tls_params_rx( socket: &S, cipher_suite: SupportedCipherSuite, (seq, secrets): (u64, ConnectionTrafficSecrets), -) -> io::Result<()> { +) -> Result<(), Error> { let rx = TlsCryptoInfoRx::extract_rx_from(cipher_suite, (seq, secrets))?; setsockopt(socket, TcpTlsRx {}, &rx) .map_err(io::Error::from) - + .map_err(Error::IO) } #[derive(Debug, Clone, Copy)] @@ -141,7 +144,7 @@ impl TlsCryptoInfo { } /// Sets the kTLS parameters on the given file descriptor. - pub fn set(&self, fd: &Fd) -> io::Result<()> { + pub fn set(&self, fd: &Fd) -> Result<(), Error> { setsockopt(fd, TcpTls {}, self).map_err(io::Error::from) } } @@ -306,27 +309,3 @@ impl TlsCryptoInfoImpl { }) } } - -#[derive(Debug)] -#[derive(thiserror::Error)] -/// Crypto material is invalid, e.g., wrong size key or IV. -#[non_exhaustive] -enum InvalidCryptoInfo { - #[error("Wrong size key")] - /// The provided key has an incorrect size (unlikely). - WrongSizeKey, - - #[error("Wrong size iv")] - /// The provided IV has an incorrect size (unlikely). - WrongSizeIv, - - #[error("the negotiated cipher suite [{0:?}] is not supported")] - /// The negotiated cipher suite is not supported by this crate. - UnsupportedCipherSuite(SupportedCipherSuite), -} - -impl From for io::Error { - fn from(err: InvalidCryptoInfo) -> Self { - io::Error::new(io::ErrorKind::InvalidInput, err) - } -} \ No newline at end of file From f95b5da2936c99bb14c3652f278216eead6a4302 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 10:25:04 +0800 Subject: [PATCH 09/33] PR 62: partial: implement KtlsStream [NOT COMPILED] --- Cargo.lock | 161 +----- ktls/Cargo.toml | 9 +- ktls/src/async_read_ready.rs | 12 - ktls/src/cork_stream.rs | 214 -------- ktls/src/error.rs | 7 +- ktls/src/ffi.rs | 105 ++-- ktls/src/ktls_stream.rs | 308 ----------- ktls/src/lib.rs | 134 +---- ktls/src/stream.rs | 186 +++++++ ktls/src/stream/context.rs | 956 +++++++++++++++++++++++++++++++++++ ktls/src/stream/error.rs | 57 +++ 11 files changed, 1285 insertions(+), 864 deletions(-) delete mode 100644 ktls/src/async_read_ready.rs delete mode 100644 ktls/src/cork_stream.rs delete mode 100644 ktls/src/ktls_stream.rs create mode 100644 ktls/src/stream.rs create mode 100644 ktls/src/stream/context.rs create mode 100644 ktls/src/stream/error.rs diff --git a/Cargo.lock b/Cargo.lock index 7393612..aeeaac7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -184,12 +184,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - [[package]] name = "errno" version = "0.3.13" @@ -206,43 +200,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-macro", - "futures-task", - "pin-project-lite", - "pin-utils", - "slab", -] - [[package]] name = "getrandom" version = "0.2.16" @@ -278,12 +235,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" - [[package]] name = "home" version = "0.5.11" @@ -293,16 +244,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "indexmap" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" -dependencies = [ - "equivalent", - "hashbrown", -] - [[package]] name = "io-uring" version = "0.7.10" @@ -337,19 +278,16 @@ dependencies = [ name = "ktls" version = "6.0.2" dependencies = [ - "futures-util", - "ktls-sys 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", "lazy_static", "libc", "log", "memoffset", "nix", - "num_enum", "oorandom", - "pin-project-lite", + "pin-project", "rcgen", "rustls", - "smallvec", "socket2 0.5.10", "test-case", "thiserror", @@ -363,12 +301,6 @@ dependencies = [ name = "ktls-sys" version = "1.0.2" -[[package]] -name = "ktls-sys" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ed84c81d133bc00e291085cff51c15cb07d3cede0aade1f2d82dd33df82d7e7" - [[package]] name = "lazy_static" version = "1.5.0" @@ -508,28 +440,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" -[[package]] -name = "num_enum" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "object" version = "0.36.7" @@ -591,16 +501,30 @@ dependencies = [ ] [[package]] -name = "pin-project-lite" -version = "0.2.16" +name = "pin-project" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "powerfmt" @@ -618,15 +542,6 @@ dependencies = [ "syn", ] -[[package]] -name = "proc-macro-crate" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" -dependencies = [ - "toml_edit", -] - [[package]] name = "proc-macro2" version = "1.0.101" @@ -792,12 +707,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - [[package]] name = "scopeguard" version = "1.2.0" @@ -1019,23 +928,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow", -] - [[package]] name = "tracing" version = "0.1.41" @@ -1326,15 +1218,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" -[[package]] -name = "winnow" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" -dependencies = [ - "memchr", -] - [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/ktls/Cargo.toml b/ktls/Cargo.toml index 4dd3b30..67437e1 100644 --- a/ktls/Cargo.toml +++ b/ktls/Cargo.toml @@ -15,16 +15,13 @@ readme.workspace = true repository.workspace = true [dependencies] -futures-util = "0.3.31" -ktls-sys = "1.0" +bitflags = "2.9" libc = { version = "0.2.175", features = ["const-extern-fn"] } log = { version = "0.4.27", optional = true } memoffset = "0.9.1" -num_enum = "0.7.4" nix = { version = "0.30.1", features = ["socket", "uio", "net"] } -pin-project-lite = "0.2.16" -rustls = { version = "0.23.27", default-features = false } -smallvec = "1.15" +pin-project = "1.1" +rustls = { version = "0.23.31", default-features = false, features = ["std"] } thiserror = "2.0" tokio = { version = "1.39.2", features = ["net", "macros", "io-util"] } tokio-rustls = { version = "0.26.0", default-features = false } diff --git a/ktls/src/async_read_ready.rs b/ktls/src/async_read_ready.rs deleted file mode 100644 index b8d9a21..0000000 --- a/ktls/src/async_read_ready.rs +++ /dev/null @@ -1,12 +0,0 @@ -use std::{io, task}; - -pub trait AsyncReadReady { - /// cf. https://docs.rs/tokio/latest/tokio/net/struct.TcpStream.html#method.poll_read_ready - fn poll_read_ready(&self, cx: &mut task::Context<'_>) -> task::Poll>; -} - -impl AsyncReadReady for tokio::net::TcpStream { - fn poll_read_ready(&self, cx: &mut task::Context<'_>) -> task::Poll> { - tokio::net::TcpStream::poll_read_ready(self, cx) - } -} diff --git a/ktls/src/cork_stream.rs b/ktls/src/cork_stream.rs deleted file mode 100644 index 9d8271e..0000000 --- a/ktls/src/cork_stream.rs +++ /dev/null @@ -1,214 +0,0 @@ -use std::{io, pin::Pin, task}; - -use rustls::internal::msgs::codec::Codec; -use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; - -use crate::AsyncReadReady; - -enum State { - ReadHeader { header_buf: [u8; 5], offset: usize }, - ReadPayload { msg_size: usize, offset: usize }, - // we encountered EOF while reading, or saw an invalid header and we're just - // passing reads through without doing any sort of processing now. - Passthrough, -} - -/// This is a wrapper that reads TLS message headers so it knows when to start -/// doing empty reads at the message boundary when "draining" a rustls -/// connection before setting up kTLS for it. -/// -/// The short explanation is: rustls might have buffered one or more -/// ApplicationData messages (the last one might even be partial) by the time -/// "connect" / "accept" returns. -/// -/// We not only need to pop messages rustls has already deframed (that's done in -/// a drain function elsewhere), but also let rustls finish reading and -/// deframing any partial message it may have already buffered. -/// -/// Because this wrapper is trying very hard not to do any error handling, if it -/// encounters anything that doesn't look like a TLS header (unknown type, -/// nonsensical size, unexpected EOF), it'll quite easily fall back to a -/// "passthrough" mode with no internal buffering, letting rustls take care -/// reporting any errors. -pub struct CorkStream { - pub io: IO, - // if true, causes empty reads at the message boudnary - pub corked: bool, - state: State, -} - -impl CorkStream { - pub fn new(io: IO) -> Self { - Self { - io, - corked: false, - state: State::ReadHeader { - header_buf: Default::default(), - offset: 0, - }, - } - } -} - -impl AsyncRead for CorkStream -where - IO: AsyncRead, -{ - #[inline] - fn poll_read( - self: Pin<&mut Self>, - cx: &mut task::Context<'_>, - buf: &mut ReadBuf<'_>, - ) -> task::Poll> { - let this = unsafe { self.get_unchecked_mut() }; - let mut io = unsafe { Pin::new_unchecked(&mut this.io) }; - - let state = &mut this.state; - - loop { - match state { - State::ReadHeader { header_buf, offset } => { - if *offset == 0 && this.corked { - tracing::trace!( - "corked, returning empty read (but waking to prevent stalls)" - ); - cx.waker().wake_by_ref(); - return task::Poll::Ready(Ok(())); - } - - let left = header_buf.len() - *offset; - tracing::trace!("reading header, {left}/{} bytes left", header_buf.len()); - - { - let mut rest = ReadBuf::new(&mut header_buf[*offset..]); - tracing::trace!("reading header: doing i/o"); - futures_util::ready!(io.as_mut().poll_read(cx, &mut rest)?); - tracing::trace!("reading header: io was ready"); - *offset += rest.filled().len(); - if rest.filled().is_empty() { - // that's an unexpected EOF for sure, but let's have - // rustls deal with the error reporting shall we? - tracing::trace!( - "unexpected EOF: header cut short after {} bytes", - *offset - ); - buf.put_slice(&header_buf[..*offset]); - *state = State::Passthrough; - - return task::Poll::Ready(Ok(())); - } - tracing::trace!("read {} bytes off of header", rest.filled().len()); - } - - if *offset == 5 { - // TODO: handle cases where buffer has less than 5 bytes - // remaining. I (fasterthanlime) bet this never happens in - // practice since the rustls deframer uses `copy_within` to - // get rid of the part of the buffer it's already decoded. - assert!(buf.remaining() >= 5, "you found an edge case in ktls!"); - buf.put_slice(&header_buf[..]); - - match decode_header(*header_buf) { - Some((typ, version, len)) => { - tracing::trace!( - "read header: typ={typ:?}, version={version:?}, len={len}" - ); - *state = State::ReadPayload { - msg_size: len as usize, - offset: 0, - }; - } - None => { - // we encountered an invalid header, let's bail out - tracing::warn!("encountered invalid header, bailing out"); - *state = State::Passthrough; - } - } - - return task::Poll::Ready(Ok(())); - } else { - // keep trying - } - } - State::ReadPayload { msg_size, offset } => { - let rest = *msg_size - *offset; - - let just_read = { - let mut rest = buf.take(rest); - futures_util::ready!(io.as_mut().poll_read(cx, &mut rest)?); - - tracing::trace!("read {} bytes off of payload", rest.filled().len()); - *offset += rest.filled().len(); - - if *offset == *msg_size { - tracing::trace!("read full payload (all {} bytes)", *offset); - *state = State::ReadHeader { - header_buf: Default::default(), - offset: 0, - }; - } - - rest.filled().len() - }; - - let new_filled = buf.filled().len() + just_read; - buf.set_filled(new_filled); - - return task::Poll::Ready(Ok(())); - } - State::Passthrough => { - // we encountered EOF while reading, or saw an invalid header and we're just - // passing reads through without doing any sort of processing now. - return io.poll_read(cx, buf); - } - } - } - } -} - -impl AsyncReadReady for CorkStream -where - IO: AsyncReadReady, -{ - fn poll_read_ready(&self, cx: &mut task::Context<'_>) -> task::Poll> { - self.io.poll_read_ready(cx) - } -} - -impl AsyncWrite for CorkStream -where - IO: AsyncWrite, -{ - #[inline] - fn poll_write( - self: Pin<&mut Self>, - cx: &mut task::Context<'_>, - buf: &[u8], - ) -> task::Poll> { - let io = unsafe { self.map_unchecked_mut(|s| &mut s.io) }; - io.poll_write(cx, buf) - } - - #[inline] - fn poll_flush(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll> { - let io = unsafe { self.map_unchecked_mut(|s| &mut s.io) }; - io.poll_flush(cx) - } - - #[inline] - fn poll_shutdown( - self: Pin<&mut Self>, - cx: &mut task::Context<'_>, - ) -> task::Poll> { - let io = unsafe { self.map_unchecked_mut(|s| &mut s.io) }; - io.poll_shutdown(cx) - } -} - -fn decode_header(b: [u8; 5]) -> Option<(rustls::ContentType, rustls::ProtocolVersion, u16)> { - let typ = rustls::ContentType::read_bytes(&b[0..1]).ok()?; - let version = rustls::ProtocolVersion::read_bytes(&b[1..3]).ok()?; - // this is dumb but it looks less scary than `.try_into().unwrap()`: - let len: u16 = u16::from_be_bytes([b[3], b[4]]); - Some((typ, version, len)) -} diff --git a/ktls/src/error.rs b/ktls/src/error.rs index 8a8ff64..a35002f 100644 --- a/ktls/src/error.rs +++ b/ktls/src/error.rs @@ -12,6 +12,11 @@ pub enum Error { /// Invalid crypto material, e.g., wrong size key or IV. InvalidCryptoInfo(#[from] InvalidCryptoInfo), + #[error("failed to extract connection secrets from rustls connection: {0}")] + /// Failed to extract connection secrets from rustls connection, e.g., not + /// have `config.enable_secret_extraction` set to true + ExtractSecrets(#[source] rustls::Error), + #[error(transparent)] /// General IO error. IO(#[from] io::Error), @@ -42,4 +47,4 @@ pub enum InvalidCryptoInfo { #[error("the negotiated cipher suite [{0:?}] is not supported")] /// The negotiated cipher suite is not supported by this crate. UnsupportedCipherSuite(SupportedCipherSuite), -} \ No newline at end of file +} diff --git a/ktls/src/ffi.rs b/ktls/src/ffi.rs index 34144f0..762f4ca 100644 --- a/ktls/src/ffi.rs +++ b/ktls/src/ffi.rs @@ -1,67 +1,62 @@ -use std::os::unix::prelude::RawFd; +//! Raw FFI wrappers. -use ktls_sys::bindings as ktls; -use rustls::{ - internal::msgs::{enums::AlertLevel, message::Message}, - AlertDescription -}; +// Since Rust 2021 doesn't have `size_of_val` included in prelude. +#![allow(unused_qualifications)] -pub(crate) const TLS_1_2_VERSION_NUMBER: u16 = (((ktls::TLS_1_2_VERSION_MAJOR & 0xFF) as u16) << 8) - | ((ktls::TLS_1_2_VERSION_MINOR & 0xFF) as u16); +use std::os::fd::RawFd; +use std::{io, mem, ptr}; -pub(crate) const TLS_1_3_VERSION_NUMBER: u16 = (((ktls::TLS_1_3_VERSION_MAJOR & 0xFF) as u16) << 8) - | ((ktls::TLS_1_3_VERSION_MINOR & 0xFF) as u16); - -const TLS_SET_RECORD_TYPE: libc::c_int = 1; -const ALERT: u8 = 0x15; - -// Yes, really. cmsg components are aligned to [libc::c_long] -#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))] -#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))] -struct Cmsg { - hdr: libc::cmsghdr, +#[repr(C)] +pub(crate) struct Cmsg { + _hdr: libc::cmsghdr, data: [u8; N], } impl Cmsg { - fn new(level: i32, typ: i32, data: [u8; N]) -> Self { - Self { - hdr: libc::cmsghdr { - // on Linux this is a usize, on macOS this is a u32 - #[allow(clippy::unnecessary_cast)] - cmsg_len: (memoffset::offset_of!(Self, data) + N) as _, - cmsg_level: level, - cmsg_type: typ, - }, - data, - } + #[allow(trivial_numeric_casts)] + #[allow(clippy::cast_possible_truncation)] + #[allow(clippy::cast_possible_wrap)] + pub(crate) fn new(level: i32, typ: i32, data: [u8; N]) -> Self { + #[allow(unsafe_code)] + // SAFETY: zeroed is fine for cmsghdr as we will set all the fields we use. + let mut hdr = unsafe { mem::zeroed::() }; + + hdr.cmsg_level = level; + hdr.cmsg_type = typ; + // For MUSL target, this is u32. + hdr.cmsg_len = (memoffset::offset_of!(Self, data) + N) as _; + + Self { _hdr: hdr, data } } } -pub fn send_close_notify(fd: RawFd) -> std::io::Result<()> { - let mut data = vec![]; - Message::build_alert(AlertLevel::Warning, AlertDescription::CloseNotify) - .payload - .encode(&mut data); - - let mut cmsg = Cmsg::new(SOL_TLS, TLS_SET_RECORD_TYPE, [ALERT]); - - let msg = libc::msghdr { - msg_name: std::ptr::null_mut(), - msg_namelen: 0, - msg_iov: &mut libc::iovec { - iov_base: data.as_mut_ptr() as _, - iov_len: data.len(), - }, - msg_iovlen: 1, - msg_control: &mut cmsg as *mut _ as *mut _, - msg_controllen: cmsg.hdr.cmsg_len, - msg_flags: 0, - }; - - let ret = unsafe { libc::sendmsg(fd, &msg, 0) }; - if ret < 0 { - return Err(std::io::Error::last_os_error()); +#[allow(trivial_numeric_casts)] +#[allow(clippy::cast_possible_truncation)] +#[allow(clippy::cast_possible_wrap)] +/// A wrapper around [`libc::sendmsg`]. +pub(crate) fn sendmsg( + fd: RawFd, + data: &mut [io::IoSlice<'_>], + cmsg: &mut Cmsg, + flags: i32, +) -> io::Result { + #[allow(unsafe_code)] + // SAFETY: zeroed is fine for msghdr as we will set all the fields we use. + let mut msghdr: libc::msghdr = unsafe { mem::zeroed() }; + + msghdr.msg_control = ptr::from_mut(cmsg).cast(); + msghdr.msg_controllen = mem::size_of_val(cmsg) as _; + msghdr.msg_iov = ptr::from_mut(data).cast(); + msghdr.msg_iovlen = data.len() as _; + + #[allow(unsafe_code)] + // SAFETY: syscall + let ret = unsafe { libc::sendmsg(fd, &msghdr, flags) }; + + if ret >= 0 { + #[allow(clippy::cast_sign_loss)] + Ok(ret as usize) + } else { + Err(io::Error::last_os_error()) } - Ok(()) } diff --git a/ktls/src/ktls_stream.rs b/ktls/src/ktls_stream.rs deleted file mode 100644 index 2d6b615..0000000 --- a/ktls/src/ktls_stream.rs +++ /dev/null @@ -1,308 +0,0 @@ -use nix::{ - errno::Errno, - sys::socket::{recvmsg, ControlMessageOwned, MsgFlags, SockaddrIn, TlsGetRecordType}, -}; -use num_enum::FromPrimitive; -use std::{ - io::{self, IoSliceMut}, - os::unix::prelude::AsRawFd, - pin::Pin, - task, -}; - -use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; - -use crate::AsyncReadReady; - -// A wrapper around `IO` that sends a `close_notify` when shut down or dropped. -pin_project_lite::pin_project! { - pub struct KtlsStream - where - IO: AsRawFd - { - #[pin] - inner: IO, - write_closed: bool, - read_closed: bool, - drained: Option<(usize, Vec)>, - } -} - -impl KtlsStream -where - IO: AsRawFd, -{ - pub fn new(inner: IO, drained: Option>) -> Self { - Self { - inner, - write_closed: false, - read_closed: false, - drained: drained.map(|drained| (0, drained)), - } - } - - /// Return the drained data + the original I/O - pub fn into_raw(self) -> (Option>, IO) { - (self.drained.map(|(_, drained)| drained), self.inner) - } - - /// Returns a reference to the original I/O - pub fn get_ref(&self) -> &IO { - &self.inner - } - - /// Returns a mut reference to the original I/O - pub fn get_mut(&mut self) -> &mut IO { - &mut self.inner - } -} - -#[derive(Debug, PartialEq, Clone, Copy, num_enum::FromPrimitive)] -#[repr(u8)] -enum TlsAlertLevel { - Warning = 1, - Fatal = 2, - #[num_enum(catch_all)] - Other(u8), -} - -#[derive(Debug, PartialEq, Clone, Copy, num_enum::FromPrimitive)] -#[repr(u8)] -enum TlsAlertDescription { - CloseNotify = 0, - #[num_enum(catch_all)] - Other(u8), -} - -impl AsyncRead for KtlsStream -where - IO: AsRawFd + AsyncRead + AsyncReadReady, -{ - fn poll_read( - self: Pin<&mut Self>, - cx: &mut task::Context<'_>, - buf: &mut ReadBuf<'_>, - ) -> task::Poll> { - tracing::trace!(buf.remaining = %buf.remaining(), "KtlsStream::poll_read"); - - if self.read_closed { - return task::Poll::Ready(Ok(())); - } - - if buf.remaining() == 0 { - return task::Poll::Ready(Ok(())); - } - - let mut this = self.project(); - - if let Some((drain_index, drained)) = this.drained.as_mut() { - let drained = &drained[*drain_index..]; - let len = std::cmp::min(buf.remaining(), drained.len()); - - tracing::trace!(%len, "KtlsStream::poll_read, can take from drain"); - buf.put_slice(&drained[..len]); - - *drain_index += len; - if *drain_index >= drained.len() { - tracing::trace!("KtlsStream::poll_read, done draining"); - *this.drained = None; - } - cx.waker().wake_by_ref(); - - tracing::trace!("KtlsStream::poll_read, returning after drain"); - return task::Poll::Ready(Ok(())); - } - - let read_res = this.inner.as_mut().poll_read(cx, buf); - if let task::Poll::Ready(Err(e)) = &read_res { - // 5 is a generic "input/output error", it happens when - // using poll_read on a kTLS socket that just received - // a control message - if let Some(5) = e.raw_os_error() { - // could be a control message, let's check - let fd = this.inner.as_raw_fd(); - - // XXX: recvmsg wants a `&mut Vec` so it's able to resize it - // I guess? Or so there's a clear separation between uninitialized - // and initialized? We could probably get read of that heap alloc, idk. - - // let mut cmsgspace = - // [0u8; unsafe { libc::CMSG_SPACE(std::mem::size_of::() as _) as _ }]; - let mut cmsgspace = Vec::with_capacity(unsafe { - libc::CMSG_SPACE(std::mem::size_of::() as _) as _ - }); - - let mut iov = [IoSliceMut::new(buf.initialize_unfilled())]; - let flags = MsgFlags::empty(); - - let r = recvmsg::(fd, &mut iov, Some(&mut cmsgspace), flags); - let r = match r { - Ok(r) => r, - Err(Errno::EAGAIN) => { - unreachable!("expected a control message, got EAGAIN") - } - Err(e) => { - // ok I guess it really failed then - tracing::trace!(?e, "recvmsg failed"); - return Err(e.into()).into(); - } - }; - let cmsg = r - .cmsgs()? - .next() - .expect("we should've received exactly one control message"); - - let record_type = match cmsg { - ControlMessageOwned::TlsGetRecordType(t) => t, - _ => panic!("unexpected cmsg type: {cmsg:#?}"), - }; - - match record_type { - TlsGetRecordType::ChangeCipherSpec => { - panic!("change_cipher_spec isn't supported by the ktls crate") - } - TlsGetRecordType::Alert => { - // the alert level and description are in iovs - let iov = r.iovs().next().expect("expected data in iovs"); - - let (level, description) = match iov { - [] => { - // we have an early return case for that - unreachable!(); - } - &[level] => { - // https://github.com/facebookincubator/fizz/blob/fff6d9d49d3c554ab66b58822d1e1fe93e8d80f2/fizz/experimental/ktls/AsyncKTLSSocket.cpp#L144 - // - // Since all alerts (even warning-level alerts) - // signal the abort of a TLS session, we do not - // need to worry about additional application - // data. - // - // If we only have half the alert (because the - // user passed a buffer of size 1), just assume - // it's a close_notify - ( - TlsAlertLevel::from_primitive(level), - TlsAlertDescription::CloseNotify, - ) - } - &[level, description] => ( - TlsAlertLevel::from_primitive(level), - TlsAlertDescription::from_primitive(description), - ), - _ => { - unreachable!( - "TLS alerts are exactly 2 bytes, your kTLS is misbehaving" - ); - } - }; - - match (level, description) { - // https://datatracker.ietf.org/doc/html/rfc5246#section-7.2 - // alerts we should handle are ones with fatal level or a - // close_notify - (_, TlsAlertDescription::CloseNotify) | (TlsAlertLevel::Fatal, _) => { - tracing::trace!(?level, ?description, "got TLS alert"); - *this.read_closed = true; - *this.write_closed = true; - if let Err(e) = - crate::ffi::send_close_notify(this.inner.as_raw_fd()) - { - return Err(e).into(); - } - // the file descriptor will be closed when the stream is dropped, - // we already protect against writes-after-close_notify through - // the write_closed flag - return task::Poll::Ready(Ok(())); - } - _ => { - // we got something we probably can't handle - } - } - return task::Poll::Ready(Ok(())); - } - TlsGetRecordType::Handshake => { - // TODO: this is where we receive TLS 1.3 resumption tickets, - // should those be stored anywhere? I'm not even sure what - // format they have at this point - tracing::trace!( - "ignoring handshake message (probably a resumption ticket)" - ); - } - TlsGetRecordType::ApplicationData => { - unreachable!("received TLS application in recvmsg, this is supposed to happen in the poll_read codepath") - } - TlsGetRecordType::Unknown(t) => { - // just ignore the record? - tracing::trace!("received record_type {t:#?}"); - } - _ => { - tracing::trace!("received unsupported record type"); - } - }; - - // FIXME: this is hacky, but can we do better? - // after we handled (..ignored) the control message, we don't - // know whether the socket is still ready to be read or not. - // - // we could try looping (tricky code structure), but we can't, - // for example, just call `poll_read`, which might fail not - // with EAGAIN/EWOULDBLOCK, but because _another_ control - // message is available. - cx.waker().wake_by_ref(); - return task::Poll::Pending; - } - } - - read_res - } -} - -impl AsyncWrite for KtlsStream -where - IO: AsRawFd + AsyncWrite, -{ - fn poll_write( - self: Pin<&mut Self>, - cx: &mut task::Context<'_>, - buf: &[u8], - ) -> task::Poll> { - if self.write_closed { - return task::Poll::Ready(Ok(0)); - } - - self.project().inner.poll_write(cx, buf) - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll> { - self.project().inner.poll_flush(cx) - } - - fn poll_shutdown( - self: Pin<&mut Self>, - cx: &mut task::Context<'_>, - ) -> task::Poll> { - let this = self.project(); - - if !*this.write_closed { - // they didn't hang up on us, we're nicely being asked to shut down, - // let's send a close_notify (and not wait for them to send it back) - *this.write_closed = true; - if let Err(e) = crate::ffi::send_close_notify(this.inner.as_raw_fd()) { - return Err(e).into(); - } - } - - // this ends up closing the inner file descriptor no matter what - this.inner.poll_shutdown(cx) - } -} - -impl AsRawFd for KtlsStream -where - IO: AsRawFd, -{ - fn as_raw_fd(&self) -> std::os::unix::prelude::RawFd { - self.inner.as_raw_fd() - } -} diff --git a/ktls/src/lib.rs b/ktls/src/lib.rs index 5833c6d..bc5073f 100644 --- a/ktls/src/lib.rs +++ b/ktls/src/lib.rs @@ -21,11 +21,7 @@ #[cfg(not(target_os = "linux"))] compile_error!("This crate only supports Linux"); -pub mod setup; - -use futures_util::future::try_join_all; -use ktls_sys::bindings as sys; -use rustls::{Connection, SupportedCipherSuite, SupportedProtocolVersion}; +use rustls::{SupportedCipherSuite, SupportedProtocolVersion}; #[cfg(all(not(feature = "ring"), not(feature = "aws_lc_rs")))] compile_error!("This crate needs wither the 'ring' or 'aws_lc_rs' feature enabled"); @@ -35,32 +31,14 @@ compile_error!("The 'ring' and 'aws_lc_rs' features are mutually exclusive"); use rustls::crypto::aws_lc_rs::cipher_suite; #[cfg(feature = "ring")] use rustls::crypto::ring::cipher_suite; +use tokio::net::{TcpListener, TcpStream}; -use smallvec::SmallVec; -use std::{ - future::Future, - io, - net::SocketAddr, - os::unix::prelude::{AsRawFd, RawFd}, -}; -use tokio::{ - io::{AsyncRead, AsyncReadExt, AsyncWrite}, - net::{TcpListener, TcpStream}, -}; - +pub mod error; mod ffi; pub mod log; mod protocol; -pub mod error; - -mod async_read_ready; -pub use async_read_ready::AsyncReadReady; - -mod ktls_stream; -pub use ktls_stream::KtlsStream; - -mod cork_stream; -pub use cork_stream::CorkStream; +pub mod setup; +pub mod stream; #[derive(Debug, Default)] pub struct CompatibleCiphers { @@ -243,108 +221,6 @@ fn sample_cipher_setup(sock: &TcpStream, cipher_suite: SupportedCipherSuite) -> Ok(()) } -/// Configure kTLS for this socket. If this call succeeds, data can be written -/// and read from this socket, and the kernel takes care of encryption -/// transparently. I'm not clear how rekeying is handled (probably via control -/// messages, but can't find a code sample for it). -/// -/// The inner IO type must be wrapped in [CorkStream] since it's the only way -/// to drain a rustls stream cleanly. See its documentation for details. -pub async fn config_ktls_server( - mut stream: tokio_rustls::server::TlsStream>, -) -> Result, Error> -where - IO: AsRawFd + AsyncRead + AsyncReadReady + AsyncWrite + Unpin, -{ - stream.get_mut().0.corked = true; - let drained = drain(&mut stream).await.map_err(Error::DrainError)?; - let (io, conn) = stream.into_inner(); - let io = io.io; - - setup_inner(io.as_raw_fd(), Connection::Server(conn))?; - Ok(KtlsStream::new(io, drained)) -} - -/// Configure kTLS for this socket. If this call succeeds, data can be -/// written and read from this socket, and the kernel takes care of encryption -/// (and key updates, etc.) transparently. -/// -/// The inner IO type must be wrapped in [CorkStream] since it's the only way -/// to drain a rustls stream cleanly. See its documentation for details. -pub async fn config_ktls_client( - mut stream: tokio_rustls::client::TlsStream>, -) -> Result, Error> -where - IO: AsRawFd + AsyncRead + AsyncWrite + Unpin, -{ - stream.get_mut().0.corked = true; - let drained = drain(&mut stream).await.map_err(Error::DrainError)?; - let (io, conn) = stream.into_inner(); - let io = io.io; - - setup_inner(io, Connection::Client(conn))?; - Ok(KtlsStream::new(io, drained)) -} - -/// Read all the bytes we can read without blocking. This is used to drained the -/// already-decrypted buffer from a tokio-rustls I/O type -async fn drain(stream: &mut (impl AsyncRead + Unpin)) -> std::io::Result>> { - tracing::trace!("Draining rustls stream"); - let mut drained = vec![0u8; 128 * 1024]; - let mut filled = 0; - - loop { - tracing::trace!("stream.read called"); - let n = match stream.read(&mut drained[filled..]).await { - Ok(n) => n, - Err(ref e) if e.kind() == std::io::ErrorKind::UnexpectedEof => { - // actually this is expected for us! - tracing::trace!("stream.read returned UnexpectedEof, that's expected for us"); - break; - } - Err(e) => { - tracing::trace!("stream.read returned error: {e}"); - return Err(e); - } - }; - tracing::trace!("stream.read returned {n}"); - if n == 0 { - // that's what CorkStream returns when it's at a message boundary - break; - } - filled += n; - } - - let maybe_drained = if filled == 0 { - None - } else { - tracing::trace!("Draining rustls stream done: drained {filled} bytes"); - drained.resize(filled, 0); - Some(drained) - }; - Ok(maybe_drained) -} - -fn setup_inner(socket: &Fd, conn: Connection) -> Result<(), Error> { - let cipher_suite = match conn.negotiated_cipher_suite() { - Some(cipher_suite) => cipher_suite, - None => { - return Err(Error::NoNegotiatedCipherSuite); - } - }; - - let secrets = match conn.dangerous_extract_secrets() { - Ok(secrets) => secrets, - Err(err) => return Err(Error::ExportSecrets(err)), - }; - - setup::setup_ulp(socket).map_err(Error::UlpError)?; - - setup::setup_tls_params(socket, cipher_suite, secrets)?; - - Ok(()) -} - /// TLS versions supported by this crate #[non_exhaustive] #[derive(Debug, Clone, Copy)] diff --git a/ktls/src/stream.rs b/ktls/src/stream.rs new file mode 100644 index 0000000..710e874 --- /dev/null +++ b/ktls/src/stream.rs @@ -0,0 +1,186 @@ +//! See [`KtlsStream`]. + +pub mod context; +pub mod error; + +use std::os::fd::AsFd; +use std::pin::Pin; + +use rustls::client::UnbufferedClientConnection; +use rustls::server::UnbufferedServerConnection; + +use crate::error::Error; +use crate::stream::context::{Context, StreamState, TlsConnData}; + +const DEFAULT_SCRATCH_CAPACITY: usize = 64; + +#[pin_project::pin_project(project = KTlsStreamProject, PinnedDrop)] +/// A thin wrapper around an inner socket with kernel TLS (kTLS) offload +/// configured. +/// +/// This implements traits [`Read`](std::io::Read) and +/// [`Write`](std::io::Write), [`AsyncRead`](tokio::io::AsyncRead) and +/// [`AsyncWrite`](tokio::io::AsyncWrite) (when feature `async-io-tokio` is +/// enabled). +/// +/// For those who may need low-level access to the inner socket, feature +/// `raw-api` provides an unsafe method [`as_raw`](Self::as_raw) to get a +/// mutable reference to the inner socket. +/// +/// ## Behaviours +/// +/// Once a TLS `close_notify` alert from the peer is received, all subsequent +/// read operations will return EOF. +/// +/// Once the caller explicitly calls `(poll_)shutdown` on the stream, all +/// subsequent write operations will return 0 bytes, indicating that the +/// stream is closed for writing. +/// +/// Once the stream is being dropped, a `close_notify` alert would be sent to +/// the peer automatically before shutting down the inner socket, according to +/// [RFC 8446, section 6.1]. +/// +/// The caller may call `(poll_)shutdown` on the stream to shutdown explicitly +/// both sides of the stream. Currently, there's no way provided by this crate +/// to shutdown the TLS stream write side only. For TLS 1.2, this is ideal since +/// once one party sends a `close_notify` alert, *the other party MUST respond +/// with a `close_notify` alert of its own and close down the connection +/// immediately*, according to [RFC 5246, section 7.2.1]; for TLS 1.3, *both +/// parties need not wait to receive a "`close_notify`" alert before +/// closing their read side of the connection*, according to [RFC 8446, section +/// 6.1]. +/// +/// [RFC 5246, section 7.2.1]: https://tools.ietf.org/html/rfc5246#section-7.2.1 +/// [RFC 8446, section 6.1]: https://tools.ietf.org/html/rfc8446#section-6.1 +pub struct KtlsStream +where + S: AsFd, +{ + #[pin] + inner: S, + + /// The context of the kTLS stream. + ctx: Context, +} + +#[pin_project::pinned_drop] +impl PinnedDrop for KtlsStream { + fn drop(self: Pin<&mut Self>) { + let this = self.project(); + + // TODO: No need to flush? It's a no-op anyway for TcpStream / UnixStream. + this.ctx.shutdown(&*this.inner); + } +} + +impl KtlsStream +where + S: AsFd, +{ + /// Attempts to construct a new [`KtlsStream`] from the provided socket and + /// [`UnbufferedClientConnection`]. + /// + /// ## Prerequisites + /// + /// - The provided [`UnbufferedClientConnection`] must meet the following + /// requirements: + /// + /// - TLS handshake must be completed + /// - [`enable_extract_secrets`](rustls::ClientConfig::enable_secret_extraction) must be set to `true` + /// + /// For detailed information about these prerequisites, see the + /// [`rustls::kernel`] module documentation. + /// + /// - The socket provided must have ULP configured with + /// [`setup_ktls_ulp`](crate::setup::setup_ulp) in advance. + /// + /// ## Errors + /// + /// Returns an error if the connection does not meet the prerequisites or if + /// the underlying kernel TLS setup fails. + pub fn from_unbuffered_client_connnection( + socket: S, + conn: UnbufferedClientConnection, + ) -> Result { + let (secrets, conn) = conn + .dangerous_into_kernel_connection() + .map_err(Error::ExtractSecrets)?; + + let supported_cipher_suite = conn.negotiated_cipher_suite(); + + let mut this = Self { + inner: socket, + ctx: Context::new(StreamState::empty(), Vec::new(), TlsConnData::Client(conn)), + }; + + let ret = crate::setup::setup_tls_params(&this.inner, supported_cipher_suite, secrets); + + if ret.is_err() { + this.ctx.shutdown(&this.inner); + + ret?; + } + + Ok(this) + } + + /// Attempts to construct a new [`KtlsStream`] from the provided socket and + /// [`UnbufferedServerConnection`]. + /// + /// ## Prerequisites + /// + /// - The provided [`UnbufferedServerConnection`] must meet the following + /// requirements: + /// + /// - TLS handshake must be completed + /// - [`enable_extract_secrets`](rustls::ServerConfig::enable_secret_extraction) must be set to `true` + /// + /// For detailed information about these prerequisites, see the + /// [`rustls::kernel`] module documentation. + /// + /// - The socket provided must have ULP configured with + /// [`setup_ktls_ulp`](crate::setup::setup_ulp) in advance. + /// + /// ## Errors + /// + /// Returns an error if the connection does not meet the prerequisites or if + /// the underlying kernel TLS setup fails. + pub fn from_unbuffered_server_connnection( + socket: S, + conn: UnbufferedServerConnection, + early_data_received: Option>, + ) -> Result { + let (secrets, conn) = conn + .dangerous_into_kernel_connection() + .map_err(Error::ExtractSecrets)?; + + // From here, the connection is considered established and handshake is + // completed + + let supported_cipher_suite = conn.negotiated_cipher_suite(); + let (state, buffer) = match early_data_received { + Some(early_data_received) if !early_data_received.is_empty() => { + (StreamState::HAS_BUFFERED_DATA, early_data_received) + } + _ => ( + StreamState::empty(), + Vec::with_capacity(DEFAULT_SCRATCH_CAPACITY), + ), + }; + + let mut this = Self { + inner: socket, + ctx: Context::new(state, buffer, TlsConnData::Server(conn)), + }; + + let ret = crate::setup::setup_tls_params(&this.inner, supported_cipher_suite, secrets); + + if ret.is_err() { + this.ctx.shutdown(&this.inner); + + ret?; + } + + Ok(this) + } +} diff --git a/ktls/src/stream/context.rs b/ktls/src/stream/context.rs new file mode 100644 index 0000000..a78d206 --- /dev/null +++ b/ktls/src/stream/context.rs @@ -0,0 +1,956 @@ +//! kTLS stream context + +use std::num::NonZeroUsize; +use std::os::fd::{AsFd, AsRawFd}; +use std::{fmt, io, mem, ops, slice}; + +use nix::errno::Errno; +use nix::sys::socket::{cmsg_space, recvmsg, ControlMessageOwned, MsgFlags, TlsGetRecordType}; +use rustls::client::ClientConnectionData; +use rustls::internal::msgs::codec::Reader; +use rustls::internal::msgs::enums::AlertLevel; +use rustls::kernel::KernelConnection; +use rustls::server::ServerConnectionData; +use rustls::{ + AlertDescription, ConnectionTrafficSecrets, ContentType, HandshakeType, InvalidMessage, + PeerMisbehaved, ProtocolVersion, SupportedCipherSuite, +}; + +use crate::protocol::{KeyUpdateRequest, KEY_UPDATE_NOT_REQUESTED, KEY_UPDATE_REQUESTED}; +use crate::setup::{setup_tls_params_rx, setup_tls_params_tx}; +use crate::stream::error::KtlsStreamError; + +/// Helper macro to handle the return value of an I/O operation on the +/// kTLS stream. +macro_rules! handle_ret { + ($this:expr, $($tt:tt)+) => { + loop { + let ret = $($tt)+; + + if let Some(ret) = $this.ctx.handle_io_result(&$this.inner, ret).transpose() { + return ret.into(); + }; + } + }; +} + +#[allow(unused)] +/// `poll` version of `handle_ret` macro, for async I/O operations. +macro_rules! handle_ret_async { + ($this:expr, $($tt:tt)+) => { + loop { + let ret = std::task::ready!($($tt)+); + + if let Some(ret) = $this.ctx.handle_io_result(&*$this.inner, ret).transpose() { + return std::task::Poll::Ready(ret); + }; + } + }; +} + +#[allow(unused)] +pub(crate) use {handle_ret, handle_ret_async}; + +macro_rules! abort_and_return_error { + ($ctx:expr, $stream:expr, $desc:expr, $error:expr) => { + let _ = $ctx.abort($stream, Some($desc)); + + return Err($error); + }; + ($ctx:expr, $stream:expr, $error:expr) => { + let _ = $ctx.abort($stream, None); + + return Err($error); + }; +} + +#[derive(Debug)] +/// kTLS stream context. +pub(crate) struct Context { + /// The I/O state + state: StreamState, + + /// Shared buffer + buffer: Buffer, + + /// The TLS connection data, managing connection secrets and session + /// tickets. + data: TlsConnData, +} + +impl Context { + /// Creates a new context. + pub(crate) fn new(state: StreamState, buffer: Vec, data: TlsConnData) -> Self { + Self { + state, + buffer: Buffer { + inner: buffer, + offset: 0, + }, + data, + } + } + + /// Returns the current state. + pub(crate) fn state(&self) -> &StreamState { + &self.state + } + + /// Reads buffered data from the inner buffer into the provided one, and + /// returns the number of bytes read. + pub(crate) fn read_buffer(&mut self, buf: &mut [u8]) -> Option { + crate::trace!( + "Reading from internal buffer, remaining_len={}", + self.buffer.len() + ); + + // Read from the inner buffer into the provided buffer + let has_read = self.buffer.read(buf); + + if self.buffer.is_read_done() { + // Clear the buffer. + self.buffer.reset(); + self.state.set_has_buffered_data(false); + } + + has_read + } + + #[must_use = "The buffered data must be handled."] + #[cfg_attr(not(feature = "raw-api"), allow(unused))] + /// Takes the buffered data, if any. + pub(crate) fn take_buffer(&mut self) -> Option { + if self.state.has_buffered_data() { + self.state.set_has_buffered_data(false); + + Some(mem::take(&mut self.buffer)) + } else { + None + } + } + + // /// Returns the TLS connection data. + // pub(crate) fn data(&self) -> &TlsConnData { + // &self.data + // } + + /// Shuts down the TLS stream and sends a `close_notify` alert to the peer. + pub(crate) fn shutdown(&mut self, socket: &S) { + crate::trace!("Shutting down the TLS stream with `close_notify` alert"); + + #[allow(unused)] + if let Err(e) = self.set_closed_state_and_try_send_alert( + socket, + AlertLevel::Warning, + AlertDescription::CloseNotify, + ) { + crate::error!("Failed to send `close_notify` alert: {}", e); + } else { + crate::trace!("`close_notify` alert sent"); + }; + } + + /// Aborts the TLS stream and sends an `internal_error` alert to the peer. + pub(crate) fn abort(&mut self, socket: &S, desc: Option) { + crate::trace!("Aborting the TLS stream with `internal_error` alert"); + + #[allow(unused)] + if let Err(e) = self.set_closed_state_and_try_send_alert( + socket, + AlertLevel::Fatal, + desc.unwrap_or(AlertDescription::InternalError), + ) { + crate::error!("Failed to send `internal_error` alert: {}", e); + } else { + crate::trace!("`internal_error` alert sent"); + }; + } + + fn set_closed_state_and_try_send_alert( + &mut self, + socket: &S, + level: AlertLevel, + desc: AlertDescription, + ) -> io::Result<()> { + let ret = if self.state.is_write_closed() { + Ok(()) + } else { + Self::try_send_tls_control_message( + socket, + ContentType::Alert, + &[level.into(), desc.into()], + ) + .map(|_| ()) + }; + + self.state.set_closed(); + + ret + } + + #[inline] + #[cfg_attr( + feature = "tracing", + tracing::instrument( + level = "INFO", + name = "Context::handle_io_result", + skip(socket, ret), + err + ) + )] + /// Inspects and handles the [`io::Result`] returned by a I/O operation on + /// the inner socket directly. + /// + /// - If the result is `Ok`, it returns `Some(T)`. + /// - If the errno is `EIO`, it tries to handle any TLS control messages + /// received, and returns `None` if succeeded. + /// - If the error kind is `BrokenPipe`, it marks the stream as closed and + /// returns `None`. + /// - Otherwise, it aborts the connection with `internal_error` alert and + /// returns the error. + /// + /// ## Errors + /// + /// The unrecoverable original [`io::Error`]. + pub(crate) fn handle_io_result( + &mut self, + socket: &S, + ret: io::Result, + ) -> io::Result> { + match ret { + Ok(ret) => Ok(Some(ret)), + Err(e) if e.raw_os_error() == Some(libc::EIO) => { + crate::debug!("Received EIO, trying to receive TLS control message"); + + self.try_recv_tls_control_message(socket)?; + + Ok(None) + } + Err(e) if e.kind() == io::ErrorKind::BrokenPipe => { + // The peer may send a `close_notify` alert and close the connection + // immediately? + crate::debug!("The peer closed the connection: {}", e); + + self.state.set_closed(); + + Ok(None) + } + Err(e) => { + self.abort(socket, None); + + Err(e) + } + } + } + + // === Internal methods === + + /// Other than application data, TLS has control messages such as alert + /// messages (record type 21) and handshake messages (record type 22), etc. + /// These messages can be sent over the socket with this method. + /// + /// Control message data should be provided unencrypted, and will be + /// encrypted by the kernel. + fn try_send_tls_control_message( + socket: &S, + typ: ContentType, + encoded_payload: &[u8], + ) -> io::Result { + // Nix does not support sending control messages with `sendmsg` yet. + + // TODO: Should an error here abort the whole connection? + + crate::ffi::sendmsg( + socket.as_fd().as_raw_fd(), + &mut [io::IoSlice::new(encoded_payload)], + &mut crate::ffi::Cmsg::new(libc::SOL_TLS, libc::TLS_SET_RECORD_TYPE, [typ.into()]), + 0, + ) + } + + #[allow(clippy::too_many_lines)] + /// Handles TLS control messages received by kernel. + /// + /// The caller **SHOULD** first check if the raw os error returned were + /// `EIO`, which indicates that there is a TLS control message available. + /// But in fact, this method can be called even if there's no TLS control + /// message (not recommended to do so). + /// + /// Will abort the connection if the control message is invalid or + /// unexpected, and return an error. + fn try_recv_tls_control_message(&mut self, socket: &S) -> io::Result<()> { + // Reuse the existing buffer to avoid extra allocations. + self.buffer.inner.reserve(u16::MAX as usize + 5); + + #[allow(unsafe_code)] + // Safety: We have reserved enough space in the buffer above. + let mut buffer: &mut [u8] = unsafe { + slice::from_raw_parts_mut( + self.buffer + .inner + .as_mut_ptr() + .add(self.buffer.inner.len()) + .cast(), + self.buffer.inner.capacity() - self.buffer.inner.len(), + ) + }; + let buffer_capacity = buffer.len(); + + // Read the control message and the associated data into the buffer. + let content_type = { + let (content_type, recv_bytes) = { + // For Linux kernel <= 5.10, will read more cmsgs than one. + let cmsg_buffer = + &mut [mem::MaybeUninit::::zeroed(); cmsg_space::() * 24]; + + let iov = &mut [io::IoSliceMut::new(buffer)]; + + let recv_msg = match recvmsg::<()>( + socket.as_fd().as_raw_fd(), + iov, + { + #[allow(unsafe_code)] + // Safety: will access only the initialized part of the buffer below. + Some(unsafe { + slice::from_raw_parts_mut( + cmsg_buffer.as_mut_ptr().cast(), + cmsg_buffer.len(), + ) + }) + }, + MsgFlags::MSG_DONTWAIT, + ) { + Ok(recv_msg) => recv_msg, + Err(Errno::EAGAIN) => { + return Ok(()); + } + Err(e) => { + abort_and_return_error!( + self, + socket, + io::Error::other(format!("recvmsg failed: {e}")) + ); + } + }; + + if recv_msg.bytes > buffer_capacity { + abort_and_return_error!( + self, + socket, + io::Error::other(format!( + "recvmsg read more bytes ({}) than maximum ({})?", + recv_msg.bytes, buffer_capacity + )) + ); + } + + let mut cmsgs = recv_msg + .cmsgs() + .expect("should have 1..24 control message received"); + + match cmsgs.next().expect("should have at least one CMSG?") { + ControlMessageOwned::TlsGetRecordType(content_type) => { + // `recv` will never return data from mixed types of TLS records. + debug_assert!(cmsgs.all(|cmsg| { + matches!(cmsg, ControlMessageOwned::TlsGetRecordType(_)) + })); + + (content_type, recv_msg.bytes) + } + value => { + abort_and_return_error!( + self, + socket, + io::Error::other(format!( + "unknown control message received: {value:?}" + )) + ); + } + } + }; + + // Safety: We have just written `recv_msg.bytes` bytes to the spare capacity of + // the buffer. + buffer = &mut buffer[..recv_bytes]; + + content_type + }; + + match content_type { + TlsGetRecordType::Handshake => { + self.try_handle_tls_control_message_handshake(socket, buffer)?; + } + TlsGetRecordType::Alert => { + if let [level, desc] = buffer { + self.try_handle_tls_control_message_alert( + socket, + (*level).into(), + (*desc).into(), + )?; + } else { + // The peer sent an invalid alert. We send back an error + // and close the connection. + + crate::error!("Invalid alert message received: {:?}", &buffer); + + abort_and_return_error!( + self, + socket, + AlertDescription::DecodeError, + KtlsStreamError::InvalidMessage(InvalidMessage::MessageTooLarge).into() + ); + } + } + TlsGetRecordType::ChangeCipherSpec => { + // ChangeCipherSpec should only be sent under the following conditions: + // + // * TLS 1.2: during a handshake or a rehandshake + // * TLS 1.3: during a handshake + // + // We don't have to worry about handling messages during a handshake + // and rustls does not support TLS 1.2 rehandshakes so we just emit + // an error here and abort the connection. + + abort_and_return_error!( + self, + socket, + AlertDescription::UnexpectedMessage, + KtlsStreamError::PeerMisbehaved( + PeerMisbehaved::IllegalMiddleboxChangeCipherSpec, + ) + .into() + ); + } + TlsGetRecordType::ApplicationData => { + // This shouldn't happen in normal operation. + + crate::warn!( + "Received {} bytes of application data when handling TLS control message", + buffer.len() + ); + + if !buffer.is_empty() { + #[allow(unsafe_code)] + // SAFETY: We have checked the buffer length above. + unsafe { + self.buffer + .inner + .set_len(self.buffer.inner.len() + buffer.len()); + } + + self.state.set_has_buffered_data(true); + } + } + _ => { + crate::error!( + "Received unexpected TLS control message: {content_type:?}, with data {:?}", + buffer + ); + + abort_and_return_error!( + self, + socket, + AlertDescription::UnexpectedMessage, + KtlsStreamError::InvalidMessage(InvalidMessage::InvalidContentType).into() + ); + } + } + + Ok(()) + } + + /// Handles a TLS alert received from the peer. + fn try_handle_tls_control_message_alert( + &mut self, + socket: &S, + level: AlertLevel, + desc: AlertDescription, + ) -> io::Result<()> { + match desc { + AlertDescription::CloseNotify + if self.data.protocol_version() == ProtocolVersion::TLSv1_2 => + { + // RFC 5246, section 7.2.1: Unless some other fatal alert has been transmitted, + // each party is required to send a close_notify alert before closing the write + // side of the connection. The other party MUST respond with a close_notify + // alert of its own and close down the connection immediately, discarding any + // pending writes. + crate::trace!("Received `close_notify` alert, should shutdown the TLS stream"); + + self.shutdown(socket); + } + AlertDescription::CloseNotify => { + // RFC 8446, section 6.1: Each party MUST send a "close_notify" alert before + // closing its write side of the connection, unless it has already sent some + // error alert. This does not have any effect on its read side of the + // connection. Note that this is a change from versions of TLS prior to TLS 1.3 + // in which implementations were required to react to a "close_notify" by + // discarding pending writes and sending an immediate "close_notify" alert of + // their own. That previous requirement could cause truncation in the read + // side. Both parties need not wait to receive a "close_notify" alert before + // closing their read side of the connection, though doing so would introduce + // the possibility of truncation. + + crate::trace!( + "Received `close_notify` alert, should shutdown the read side of TLS stream" + ); + + self.state.set_read_closed(); + } + _ if self.data.protocol_version() == ProtocolVersion::TLSv1_2 + && level == AlertLevel::Warning => + { + // RFC 5246, section 7.2.2: If an alert with a level of warning + // is sent and received, generally the connection can continue + // normally. + + crate::warn!("Received alert, level={level:?}, desc: {desc:?}"); + } + _ => { + // All other alerts are treated as fatal and result in us immediately shutting + // down the connection and emitting an error. + + crate::error!("Received fatal alert, level={level:?}, desc: {desc:?}"); + + self.state.set_closed(); + + return Err(KtlsStreamError::Alert(desc).into()); + } + } + + Ok(()) + } + + #[allow(clippy::too_many_lines)] + /// Handles a TLS alert received from the peer. + fn try_handle_tls_control_message_handshake( + &mut self, + socket: &S, + payload: &[u8], + ) -> io::Result<()> { + fn read_message<'a>(reader: &mut Reader<'a>) -> Option<(HandshakeType, &'a [u8])> { + let &[typ, a, b, c] = reader.take(4)? else { + unreachable!() + }; + + let handshake_type = HandshakeType::from(typ); + let length = u32::from_be_bytes([0, a, b, c]) as usize; + + let payload = reader.take(length)?; + + Some((handshake_type, payload)) + } + + let mut reader = Reader::init(payload); + let mut sub_message_count = 0; + + loop { + let Some((handshake_type, payload)) = read_message(&mut reader) else { + crate::error!( + "Received truncated handshake message, payload: {:?}", + payload + ); + + abort_and_return_error!( + self, + socket, + AlertDescription::DecodeError, + KtlsStreamError::InvalidMessage(InvalidMessage::MessageTooShort).into() + ); + }; + + sub_message_count += 1; + + match handshake_type { + HandshakeType::KeyUpdate + if self.data.protocol_version() == ProtocolVersion::TLSv1_3 => + { + self.try_handle_tls_control_message_handshake_key_update( + socket, + payload, + &reader, + sub_message_count, + )?; + } + HandshakeType::NewSessionTicket + if self.data.protocol_version() == ProtocolVersion::TLSv1_3 => + { + let TlsConnData::Client(conn) = &mut self.data else { + abort_and_return_error!( + self, + socket, + AlertDescription::UnexpectedMessage, + KtlsStreamError::InvalidMessage(InvalidMessage::UnexpectedMessage( + "TLS 1.2 peer sent a TLS 1.3 NewSessionTicket message", + )) + .into() + ); + }; + + match conn.handle_new_session_ticket(payload) { + Ok(()) => (), + // Convert some messages into their higher-level equivalents + Err(rustls::Error::InvalidMessage(err)) => { + abort_and_return_error!( + self, + socket, + AlertDescription::DecodeError, + KtlsStreamError::InvalidMessage(err).into() + ); + } + Err(rustls::Error::PeerMisbehaved(err)) => { + abort_and_return_error!( + self, + socket, + AlertDescription::UnexpectedMessage, + KtlsStreamError::PeerMisbehaved(err).into() + ); + } + + // Other errors are not necessarily fatal + Err(_) => {} + } + } + _ if self.data.protocol_version() == ProtocolVersion::TLSv1_3 => { + abort_and_return_error!( + self, + socket, + AlertDescription::UnexpectedMessage, + KtlsStreamError::InvalidMessage(InvalidMessage::UnexpectedMessage( + "expected KeyUpdate or NewSessionTicket handshake messages only", + )) + .into() + ); + } + _ => { + crate::error!( + "Unexpected handshake message: ver={:?}, typ={handshake_type:?}", + self.data.protocol_version() + ); + + abort_and_return_error!( + self, + socket, + AlertDescription::UnexpectedMessage, + KtlsStreamError::InvalidMessage(InvalidMessage::UnexpectedMessage( + "handshake messages are not expected on TLS 1.2 connections", + )) + .into() + ); + } + } + + if reader.any_left() { + crate::trace!("Processing next sub messages."); + } else { + crate::trace!("All sub messages are processed."); + return Ok(()); + } + } + } + + fn try_handle_tls_control_message_handshake_key_update( + &mut self, + socket: &S, + payload: &[u8], + reader: &Reader<'_>, + sub_message_count: usize, + ) -> io::Result<()> { + if sub_message_count != 1 || reader.any_left() { + // RFC 8446, section 5.1: Handshake messages MUST NOT span key changes. + abort_and_return_error!( + self, + socket, + AlertDescription::UnexpectedMessage, + KtlsStreamError::PeerMisbehaved(PeerMisbehaved::KeyEpochWithPendingFragment).into() + ); + } + + let key_update_request = match payload { + [KEY_UPDATE_REQUESTED] => KeyUpdateRequest::UpdateRequested, + [KEY_UPDATE_NOT_REQUESTED] => KeyUpdateRequest::UpdateNotRequested, + _ => { + crate::error!("Received invalid KeyUpdateRequest: {:?}", payload); + + abort_and_return_error!( + self, + socket, + AlertDescription::DecodeError, + KtlsStreamError::InvalidMessage(InvalidMessage::InvalidKeyUpdate).into() + ); + } + }; + + { + let (seq, secrets) = match self.data.update_rx_secret() { + Ok(secrets) => secrets, + Err(e) => { + abort_and_return_error!( + self, + socket, + AlertDescription::InternalError, + KtlsStreamError::KeyUpdateFailed(e).into() + ); + } + }; + + if let Err(e) = + setup_tls_params_rx(socket, self.data.negotiated_cipher_suite(), (seq, secrets)) + { + abort_and_return_error!(self, socket, AlertDescription::InternalError, e.into()); + } + } + + match key_update_request { + KeyUpdateRequest::UpdateNotRequested => return Ok(()), + KeyUpdateRequest::UpdateRequested => { + let message = [ + HandshakeType::KeyUpdate.into(), // typ + 0, + 0, + 1, // length + KeyUpdateRequest::UpdateNotRequested.into(), + ]; + + if let Err(e) = + Self::try_send_tls_control_message(socket, ContentType::Handshake, &message) + { + abort_and_return_error!( + self, + socket, + AlertDescription::InternalError, + io::Error::other(format!("Failed to send KeyUpdate message: {e}")) + ); + } + + let (seq, secrets) = match self.data.update_tx_secret() { + Ok(secrets) => secrets, + Err(e) => { + abort_and_return_error!( + self, + socket, + AlertDescription::InternalError, + KtlsStreamError::KeyUpdateFailed(e).into() + ); + } + }; + + if let Err(e) = + setup_tls_params_tx(socket, self.data.negotiated_cipher_suite(), (seq, secrets)) + { + abort_and_return_error!( + self, + socket, + AlertDescription::InternalError, + e.into() + ); + } + } + _ => { + unreachable!( + "KeyUpdateRequest should only be UpdateNotRequested or UpdateRequested here" + ); + } + } + + Ok(()) + } +} + +/// [`KernelConnection`], client side or server side. +pub(crate) enum TlsConnData { + Client(KernelConnection), + Server(KernelConnection), +} + +impl fmt::Debug for TlsConnData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Client(_) => f.debug_struct("TlsConnData::Client").finish(), + Self::Server(_) => f.debug_struct("TlsConnData::Server").finish(), + } + } +} + +impl TlsConnData { + #[inline] + fn protocol_version(&self) -> ProtocolVersion { + match self { + Self::Client(data) => data.protocol_version(), + Self::Server(data) => data.protocol_version(), + } + } + + #[inline] + fn negotiated_cipher_suite(&self) -> SupportedCipherSuite { + match self { + Self::Client(conn) => conn.negotiated_cipher_suite(), + Self::Server(conn) => conn.negotiated_cipher_suite(), + } + } + + #[inline] + fn update_tx_secret(&mut self) -> Result<(u64, ConnectionTrafficSecrets), rustls::Error> { + match self { + Self::Client(conn) => conn.update_tx_secret(), + Self::Server(conn) => conn.update_tx_secret(), + } + } + + #[inline] + fn update_rx_secret(&mut self) -> Result<(u64, ConnectionTrafficSecrets), rustls::Error> { + match self { + Self::Client(conn) => conn.update_rx_secret(), + Self::Server(conn) => conn.update_rx_secret(), + } + } +} + +bitflags::bitflags! { + #[derive(Debug)] + pub(crate) struct StreamState : u8 { + /// The read side of the TLS stream is closed. + const READ_CLOSED = 0b0000_0001; + + /// The write side of the TLS stream is closed. + const WRITE_CLOSED = 0b0000_0010; + + /// The stream is closed, both read and write sides. + const CLOSED = StreamState::READ_CLOSED.bits() | StreamState::WRITE_CLOSED.bits(); + + /// Has buffered data that needs to be handled before reading from the inner stream. + const HAS_BUFFERED_DATA = 0b0001_0000; + } +} + +impl StreamState { + #[inline] + /// If the read side of the TLS stream were closed. + pub(crate) fn is_read_closed(&self) -> bool { + self.contains(Self::READ_CLOSED) + } + + #[inline] + /// Sets the read side of the TLS stream as closed. + pub(crate) fn set_read_closed(&mut self) { + self.insert(Self::READ_CLOSED); + } + + #[inline] + /// If the write side of the TLS stream were closed. + pub(crate) fn is_write_closed(&self) -> bool { + self.contains(Self::WRITE_CLOSED) + } + + // #[inline] + // /// Sets the write side of the TLS stream as closed. + // pub(crate) fn set_write_closed(&mut self) { + // self.insert(Self::WRITE_CLOSED); + // } + + #[cfg_attr(not(feature = "raw-api"), allow(unused))] + #[inline] + /// If the stream is partially closed, either read or write side. + pub(crate) fn is_partially_closed(&self) -> bool { + self.is_read_closed() || self.is_write_closed() + } + + // #[inline] + // /// If the stream is closed, both read and write sides. + // pub(crate) fn is_closed(self) -> bool { + // self.contains(Self::CLOSED) + // } + + #[inline] + /// Sets the stream as closed, both read and write sides. + pub(crate) fn set_closed(&mut self) { + self.insert(Self::CLOSED); + } + + #[inline] + /// If the stream has buffered data that needs to be handled before reading + /// from the inner stream. + pub(crate) fn has_buffered_data(&self) -> bool { + self.contains(Self::HAS_BUFFERED_DATA) + } + + #[inline] + /// Sets the stream as having buffered data that needs to be handled before + /// reading from the inner stream. + pub(crate) fn set_has_buffered_data(&mut self, val: bool) { + self.set(Self::HAS_BUFFERED_DATA, val); + } +} + +#[derive(Clone, Default)] +/// A simple buffer with a read offset. +pub struct Buffer { + /// The inner buffer data. + inner: Vec, + + /// Read offset of the buffer. + offset: usize, +} + +impl fmt::Debug for Buffer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Buffer") + .field("inner", &self.inner.len()) + .field("offset", &self.offset) + .finish() + } +} + +impl ops::Deref for Buffer { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + // TODO: Here can actually avoid panic check, since `self.offset` is always + // guaranteed to be less than or equal to `self.inner.len()`, but the MSRV + // limits so. + &self.inner[self.offset..] + } +} + +impl AsRef<[u8]> for Buffer { + fn as_ref(&self) -> &[u8] { + self + } +} + +impl Buffer { + #[inline] + /// Reads from the inner buffer into the provided buffer, and advances the + /// read offset. + /// + /// Returns the number of bytes read. + pub fn read(&mut self, buf: &mut [u8]) -> Option { + // Read zero: buffer is empty or offset is at the end + let to_read = NonZeroUsize::new(buf.len().min(self.len()))?; + + buf[..to_read.get()].copy_from_slice(&self[..to_read.get()]); + + self.offset += to_read.get(); + + Some(to_read) + } + + #[allow(clippy::must_use_candidate)] + #[inline] + /// Check if the read offset has reached the end of the inner buffer. + pub fn is_read_done(&self) -> bool { + self.offset >= self.inner.len() + } + + #[inline] + /// Resets the buffer, clearing the inner data and resetting the read + /// offset. + pub(crate) fn reset(&mut self) { + #[allow(unsafe_code)] + // SAFETY: We are setting length to 0, which is always valid. + unsafe { + self.inner.set_len(0); + } + self.offset = 0; + } +} diff --git a/ktls/src/stream/error.rs b/ktls/src/stream/error.rs new file mode 100644 index 0000000..acc8676 --- /dev/null +++ b/ktls/src/stream/error.rs @@ -0,0 +1,57 @@ +//! Error type of `KtlsStream` and related operations. + +use std::io; + +use rustls::{AlertDescription, InvalidMessage, PeerMisbehaved}; + +#[non_exhaustive] +#[derive(Debug)] +#[derive(thiserror::Error)] +/// The error type for `KtlsStream` and related operations. +pub enum KtlsStreamError { + #[error("Received corrupt message of type {0:?}")] + /// A corrupt message was received from the peer. + InvalidMessage(InvalidMessage), + + #[error("Peer misbehaved: {0:?}")] + /// The peer misbehaved in some way. + PeerMisbehaved(PeerMisbehaved), + + #[error("Key update failed: {0}")] + /// Failed to handle a key update request. + KeyUpdateFailed(#[source] rustls::Error), + + #[error("Failed to handle a provided session ticket: {0}")] + /// Failed to handle a provided session ticket. + SessionTicketFailed(#[source] rustls::Error), + + #[error("the connection has been closed by the peer")] + /// The connection has been closed by the peer. + Closed, + + #[error("cannot handle control messages while there is buffered data to read")] + /// Cannot handle control messages while there is buffered data to read. + ControlMessageWithBufferedData, + + #[error("Connection peer closed the connection with an alert: {0:?}")] + /// The connection peer closed the connection with an alert. + Alert(AlertDescription), +} + +impl From for KtlsStreamError { + fn from(error: InvalidMessage) -> Self { + Self::InvalidMessage(error) + } +} + +impl From for KtlsStreamError { + fn from(error: PeerMisbehaved) -> Self { + Self::PeerMisbehaved(error) + } +} + +impl From for io::Error { + fn from(value: KtlsStreamError) -> Self { + Self::other(value) + } +} From 7e691eddcc1aaaec4a75b2244b94680fd5c53b19 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 10:25:34 +0800 Subject: [PATCH 10/33] PR 62: partial: implement Read / Write for KtlsStream --- ktls/src/stream.rs | 1 + ktls/src/stream/impl_std.rs | 64 +++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 ktls/src/stream/impl_std.rs diff --git a/ktls/src/stream.rs b/ktls/src/stream.rs index 710e874..566d349 100644 --- a/ktls/src/stream.rs +++ b/ktls/src/stream.rs @@ -2,6 +2,7 @@ pub mod context; pub mod error; +pub mod impl_std; use std::os::fd::AsFd; use std::pin::Pin; diff --git a/ktls/src/stream/impl_std.rs b/ktls/src/stream/impl_std.rs new file mode 100644 index 0000000..16adba0 --- /dev/null +++ b/ktls/src/stream/impl_std.rs @@ -0,0 +1,64 @@ +//! `Read` / `Write` support for `KtlsStream`. + +use std::io::{self, Read, Write}; +use std::os::fd::AsFd; + +use crate::stream::context::handle_ret; +use crate::stream::KtlsStream; + +impl KtlsStream { + /// Shuts down both read and write sides of the TLS stream. + pub fn shutdown(&mut self) { + self.ctx.shutdown(&self.inner); + } +} + +impl Read for KtlsStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + handle_ret!(self, { + let state = self.ctx.state(); + + if state.is_read_closed() { + crate::trace!("Read closed, returning EOF"); + + // received a `close_notify` alert from the peer, return EOF. + return Ok(0); + } + + if state.has_buffered_data() { + // Unlikely path, actually. + + if let Some(has_read) = self.ctx.read_buffer(buf) { + return Ok(has_read.get()); + } + } + + self.inner.read(buf) + }) + } +} + +impl Write for KtlsStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + handle_ret!(self, { + if self.ctx.state().is_write_closed() { + crate::trace!("Write closed, returning EOF"); + + return Ok(0); + } + + self.inner.write(buf) + }) + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } + + fn by_ref(&mut self) -> &mut Self + where + Self: Sized, + { + self + } +} From 591993d08b2e5a17f0479ac4392ea5f2d98f77d0 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 10:28:37 +0800 Subject: [PATCH 11/33] PR 62: partial: optional implement AsyncRead / AsyncWrite for KtlsStream --- Cargo.lock | 11 ---- ktls/Cargo.toml | 16 +++-- ktls/src/stream.rs | 2 + ktls/src/stream/impl_tokio.rs | 121 ++++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 ktls/src/stream/impl_tokio.rs diff --git a/Cargo.lock b/Cargo.lock index aeeaac7..98181b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -292,7 +292,6 @@ dependencies = [ "test-case", "thiserror", "tokio", - "tokio-rustls", "tracing", "tracing-subscriber", ] @@ -918,16 +917,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-rustls" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" -dependencies = [ - "rustls", - "tokio", -] - [[package]] name = "tracing" version = "0.1.41" diff --git a/ktls/Cargo.toml b/ktls/Cargo.toml index 67437e1..ca18631 100644 --- a/ktls/Cargo.toml +++ b/ktls/Cargo.toml @@ -23,8 +23,7 @@ nix = { version = "0.30.1", features = ["socket", "uio", "net"] } pin-project = "1.1" rustls = { version = "0.23.31", default-features = false, features = ["std"] } thiserror = "2.0" -tokio = { version = "1.39.2", features = ["net", "macros", "io-util"] } -tokio-rustls = { version = "0.26.0", default-features = false } +tokio = { version = "1.40", optional = true } tracing = { version = "0.1.41", optional = true } [dev-dependencies] @@ -37,15 +36,18 @@ tokio = { version = "1.39.2", features = ["full"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } [features] -default = ["aws_lc_rs", "tls12"] -aws_lc_rs = ["rustls/aws_lc_rs", "tokio-rustls/aws_lc_rs"] -aws-lc-rs = ["aws_lc_rs"] # Alias because Cargo features commonly use `-` -ring = ["rustls/ring", "tokio-rustls/ring"] -tls12 = ["rustls/tls12", "tokio-rustls/tls12"] +default = ["aws_lc_rs", "tls12", "async-io-tokio"] +aws_lc_rs = ["rustls/aws_lc_rs"] +aws-lc-rs = ["aws_lc_rs"] # Alias because Cargo features commonly use `-` +ring = ["rustls/ring"] +tls12 = ["rustls/tls12"] # Expose some low-level APIs raw-api = [] +# Implements tokio's `AsyncRead` and `AsyncWrite` traits for `KtlsStream` +async-io-tokio = ["dep:tokio"] + # Logging support with `log` crate log = ["dep:log"] diff --git a/ktls/src/stream.rs b/ktls/src/stream.rs index 566d349..cf68d0a 100644 --- a/ktls/src/stream.rs +++ b/ktls/src/stream.rs @@ -3,6 +3,8 @@ pub mod context; pub mod error; pub mod impl_std; +#[cfg(feature = "async-io-tokio")] +pub mod impl_tokio; use std::os::fd::AsFd; use std::pin::Pin; diff --git a/ktls/src/stream/impl_tokio.rs b/ktls/src/stream/impl_tokio.rs new file mode 100644 index 0000000..c042064 --- /dev/null +++ b/ktls/src/stream/impl_tokio.rs @@ -0,0 +1,121 @@ +//! Optional: Tokio's `AsyncRead` / `AsyncWrite` support for `KtlsStream`. + +use std::os::fd::AsFd; +use std::pin::Pin; +use std::{io, ptr, task}; + +use tokio::io::{self as async_io, AsyncRead, AsyncWrite}; + +use crate::stream::context::handle_ret_async; +use crate::stream::KtlsStream; + +impl AsyncRead for KtlsStream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + buf: &mut async_io::ReadBuf<'_>, + ) -> task::Poll> { + let mut this = self.project(); + + handle_ret_async!(this, { + let state = this.ctx.state(); + + if state.is_read_closed() { + // received a `close_notify` alert from the peer, return EOF. + + crate::trace!("Read closed, returning EOF"); + + return task::Poll::Ready(Ok(())); + } + + if state.has_buffered_data() { + // Unlikely path, actually. + + #[allow(unsafe_code)] + #[allow(trivial_casts)] + // Safety: will set the initialized part after reading. + if let Some(has_read) = this + .ctx + .read_buffer(unsafe { &mut *(ptr::from_mut(buf.unfilled_mut()) as *mut [u8]) }) + { + #[allow(unsafe_code)] + // Safety: has filled and written `has_read` bytes. + unsafe { + buf.assume_init(has_read.get()); + }; + buf.advance(has_read.get()); + + return task::Poll::Ready(Ok(())); + } + } + + this.inner.as_mut().poll_read(cx, buf) + }) + } +} + +impl AsyncWrite for KtlsStream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + buf: &[u8], + ) -> task::Poll> { + let mut this = self.project(); + + handle_ret_async!(this, { + if this.ctx.state().is_write_closed() { + crate::trace!("Write closed, returning EOF"); + + return task::Poll::Ready(Ok(0)); + } + + this.inner.as_mut().poll_write(cx, buf) + }) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll> { + let mut this = self.project(); + + handle_ret_async!(this, { + if this.ctx.state().is_write_closed() { + return task::Poll::Ready(Ok(())); + } + + this.inner.as_mut().poll_flush(cx) + }) + } + + /// Shuts down both read and write sides of the TLS stream. + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + ) -> task::Poll> { + let this = self.project(); + + this.ctx.shutdown(&*this.inner); + + this.inner.poll_shutdown(cx) + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> task::Poll> { + let mut this = self.project(); + + handle_ret_async!(this, { + if this.ctx.state().is_write_closed() { + crate::trace!("Write closed, returning EOF"); + + return task::Poll::Ready(Ok(0)); + } + + this.inner.as_mut().poll_write_vectored(cx, bufs) + }) + } + + fn is_write_vectored(&self) -> bool { + self.inner.is_write_vectored() + } +} From 02aa2300593f32527449fbbb01097c66eff60171 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Thu, 28 Aug 2025 22:47:57 +0800 Subject: [PATCH 12/33] refactor(probe): probe ktls compatibility --- Cargo.lock | 367 ++------------------------------------- ktls/Cargo.toml | 10 +- ktls/src/lib.rs | 300 +------------------------------- ktls/src/setup.rs | 2 +- ktls/src/setup/tls.rs | 7 +- ktls/src/utils.rs | 5 + ktls/src/utils/suites.rs | 193 ++++++++++++++++++++ 7 files changed, 226 insertions(+), 658 deletions(-) create mode 100644 ktls/src/utils.rs create mode 100644 ktls/src/utils/suites.rs diff --git a/Cargo.lock b/Cargo.lock index 98181b6..1dad207 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,29 +32,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "aws-lc-rs" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" -dependencies = [ - "bindgen", - "cc", - "cmake", - "dunce", - "fs_extra", -] - [[package]] name = "backtrace" version = "0.3.75" @@ -67,7 +44,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -76,29 +53,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bindgen" -version = "0.69.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "itertools", - "lazy_static", - "lazycell", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn", - "which", -] - [[package]] name = "bitflags" version = "2.9.3" @@ -117,20 +71,9 @@ version = "1.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" dependencies = [ - "jobserver", - "libc", "shlex", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" version = "1.0.3" @@ -143,26 +86,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "cmake" -version = "0.1.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" -dependencies = [ - "cc", -] - [[package]] name = "deranged" version = "0.4.0" @@ -172,34 +95,6 @@ dependencies = [ "powerfmt", ] -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "errno" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - [[package]] name = "getrandom" version = "0.2.16" @@ -208,19 +103,7 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi", ] [[package]] @@ -229,21 +112,6 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "io-uring" version = "0.7.10" @@ -255,25 +123,6 @@ dependencies = [ "libc", ] -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.3", - "libc", -] - [[package]] name = "ktls" version = "6.0.2" @@ -306,34 +155,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" -[[package]] -name = "libloading" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" -dependencies = [ - "cfg-if", - "windows-targets 0.53.3", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "lock_api" version = "0.4.13" @@ -374,12 +201,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -396,7 +217,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.59.0", ] @@ -413,16 +234,6 @@ dependencies = [ "memoffset", ] -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -486,7 +297,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -531,16 +342,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro2" version = "1.0.101" @@ -559,12 +360,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "rcgen" version = "0.13.2" @@ -639,7 +434,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom", "libc", "untrusted", "windows-sys 0.52.0", @@ -651,34 +446,13 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - [[package]] name = "rustls" version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ - "aws-lc-rs", "once_cell", - "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -700,7 +474,6 @@ version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ - "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -1002,27 +775,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] - [[package]] name = "winapi" version = "0.3.9" @@ -1045,19 +797,13 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -1066,16 +812,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.3", + "windows-targets", ] [[package]] @@ -1084,31 +821,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -1117,105 +837,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] - [[package]] name = "yasna" version = "0.5.2" diff --git a/ktls/Cargo.toml b/ktls/Cargo.toml index ca18631..4fc4ccf 100644 --- a/ktls/Cargo.toml +++ b/ktls/Cargo.toml @@ -36,10 +36,9 @@ tokio = { version = "1.39.2", features = ["full"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } [features] -default = ["aws_lc_rs", "tls12", "async-io-tokio"] -aws_lc_rs = ["rustls/aws_lc_rs"] -aws-lc-rs = ["aws_lc_rs"] # Alias because Cargo features commonly use `-` -ring = ["rustls/ring"] +default = ["tls12", "async-io-tokio", "probe-ktls-compatibility"] + +# Enable rustls TLS 1.2 support tls12 = ["rustls/tls12"] # Expose some low-level APIs @@ -53,3 +52,6 @@ log = ["dep:log"] # Logging support with `tracing` crate tracing = ["dep:tracing"] + +# Probes for the compatibility of the current kernel with kTLS +probe-ktls-compatibility = [] diff --git a/ktls/src/lib.rs b/ktls/src/lib.rs index bc5073f..5bcd521 100644 --- a/ktls/src/lib.rs +++ b/ktls/src/lib.rs @@ -21,308 +21,10 @@ #[cfg(not(target_os = "linux"))] compile_error!("This crate only supports Linux"); -use rustls::{SupportedCipherSuite, SupportedProtocolVersion}; - -#[cfg(all(not(feature = "ring"), not(feature = "aws_lc_rs")))] -compile_error!("This crate needs wither the 'ring' or 'aws_lc_rs' feature enabled"); -#[cfg(all(feature = "ring", feature = "aws_lc_rs"))] -compile_error!("The 'ring' and 'aws_lc_rs' features are mutually exclusive"); -#[cfg(feature = "aws_lc_rs")] -use rustls::crypto::aws_lc_rs::cipher_suite; -#[cfg(feature = "ring")] -use rustls::crypto::ring::cipher_suite; -use tokio::net::{TcpListener, TcpStream}; - pub mod error; mod ffi; pub mod log; mod protocol; pub mod setup; pub mod stream; - -#[derive(Debug, Default)] -pub struct CompatibleCiphers { - pub tls12: CompatibleCiphersForVersion, - pub tls13: CompatibleCiphersForVersion, -} - -#[derive(Debug, Default)] -pub struct CompatibleCiphersForVersion { - pub aes_gcm_128: bool, - pub aes_gcm_256: bool, - pub chacha20_poly1305: bool, -} - -impl CompatibleCiphers { - /// List compatible ciphers. This listens on a TCP socket and blocks for a - /// little while. Do once at the very start of a program. Should probably be - /// behind a lazy_static / once_cell - pub async fn new() -> io::Result { - let mut ciphers = CompatibleCiphers::default(); - - let ln = TcpListener::bind("0.0.0.0:0").await?; - let local_addr = ln.local_addr()?; - - // Accepted conns of ln - let mut accepted_conns: SmallVec<[TcpStream; 12]> = SmallVec::new(); - - let accept_conns_fut = async { - loop { - if let Ok((conn, _addr)) = ln.accept().await { - accepted_conns.push(conn); - } - } - }; - - ciphers.test_ciphers(local_addr, accept_conns_fut).await?; - - Ok(ciphers) - } - - async fn test_ciphers( - &mut self, - local_addr: SocketAddr, - accept_conns_fut: impl Future, - ) -> io::Result<()> { - let ciphers: Vec<(SupportedCipherSuite, &mut bool)> = vec![ - ( - cipher_suite::TLS13_AES_128_GCM_SHA256, - &mut self.tls13.aes_gcm_128, - ), - ( - cipher_suite::TLS13_AES_256_GCM_SHA384, - &mut self.tls13.aes_gcm_256, - ), - ( - cipher_suite::TLS13_CHACHA20_POLY1305_SHA256, - &mut self.tls13.chacha20_poly1305, - ), - ( - cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - &mut self.tls12.aes_gcm_128, - ), - ( - cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - &mut self.tls12.aes_gcm_256, - ), - ( - cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - &mut self.tls12.chacha20_poly1305, - ), - ]; - - let create_connections_fut = - try_join_all((0..ciphers.len()).map(|_| TcpStream::connect(local_addr))); - - let socks = tokio::select! { - // Use biased here to optimize performance. - // - // With biased, tokio::select! would first poll create_connections_fut, - // which would poll all `TcpStream::connect` futures and requests - // new connections to `ln` then returns `Poll::Pending`. - // - // Then accept_conns_fut would be polled, which accepts all pending - // connections, wake up create_connections_fut then returns - // `Poll::Pending`. - // - // Finally, create_connections_fut wakes up and all connections - // are ready, the result is collected into a Vec and ends - // the tokio::select!. - biased; - - res = create_connections_fut => res?, - _ = accept_conns_fut => unreachable!(), - }; - - assert_eq!(ciphers.len(), socks.len()); - - ciphers - .into_iter() - .zip(socks) - .for_each(|((cipher_suite, field), sock)| { - *field = sample_cipher_setup(&sock, cipher_suite).is_ok(); - }); - - Ok(()) - } - - /// Returns true if we're reasonably confident that functions like - /// [config_ktls_client] and [config_ktls_server] will succeed. - pub fn is_compatible(&self, suite: SupportedCipherSuite) -> bool { - let kcs = match KtlsCipherSuite::try_from(suite) { - Ok(kcs) => kcs, - Err(_) => return false, - }; - - let fields = match kcs.version { - KtlsVersion::TLS12 => &self.tls12, - KtlsVersion::TLS13 => &self.tls13, - }; - - match kcs.typ { - KtlsCipherType::AesGcm128 => fields.aes_gcm_128, - KtlsCipherType::AesGcm256 => fields.aes_gcm_256, - KtlsCipherType::Chacha20Poly1305 => fields.chacha20_poly1305, - } - } -} - -fn sample_cipher_setup(sock: &TcpStream, cipher_suite: SupportedCipherSuite) -> Result<(), Error> { - let kcs = match KtlsCipherSuite::try_from(cipher_suite) { - Ok(kcs) => kcs, - Err(_) => panic!("unsupported cipher suite"), - }; - - let ffi_version = match kcs.version { - KtlsVersion::TLS12 => ffi::TLS_1_2_VERSION_NUMBER, - KtlsVersion::TLS13 => ffi::TLS_1_3_VERSION_NUMBER, - }; - - let crypto_info = match kcs.typ { - KtlsCipherType::AesGcm128 => CryptoInfo::AesGcm128(sys::tls12_crypto_info_aes_gcm_128 { - info: sys::tls_crypto_info { - version: ffi_version, - cipher_type: sys::TLS_CIPHER_AES_GCM_128 as _, - }, - iv: Default::default(), - key: Default::default(), - salt: Default::default(), - rec_seq: Default::default(), - }), - KtlsCipherType::AesGcm256 => CryptoInfo::AesGcm256(sys::tls12_crypto_info_aes_gcm_256 { - info: sys::tls_crypto_info { - version: ffi_version, - cipher_type: sys::TLS_CIPHER_AES_GCM_256 as _, - }, - iv: Default::default(), - key: Default::default(), - salt: Default::default(), - rec_seq: Default::default(), - }), - KtlsCipherType::Chacha20Poly1305 => { - CryptoInfo::Chacha20Poly1305(sys::tls12_crypto_info_chacha20_poly1305 { - info: sys::tls_crypto_info { - version: ffi_version, - cipher_type: sys::TLS_CIPHER_CHACHA20_POLY1305 as _, - }, - iv: Default::default(), - key: Default::default(), - salt: Default::default(), - rec_seq: Default::default(), - }) - } - }; - let fd = sock.as_raw_fd(); - - setup_ulp(fd).map_err(Error::UlpError)?; - - setup_tls_info(fd, ffi::Direction::Tx, crypto_info)?; - - Ok(()) -} - -/// TLS versions supported by this crate -#[non_exhaustive] -#[derive(Debug, Clone, Copy)] -pub enum KtlsVersion { - TLS12, - TLS13, -} - -impl KtlsVersion { - /// Converts into the equivalent rustls [SupportedProtocolVersion] - pub fn as_supported_version(&self) -> &'static SupportedProtocolVersion { - match self { - KtlsVersion::TLS12 => &rustls::version::TLS12, - KtlsVersion::TLS13 => &rustls::version::TLS13, - } - } -} - -/// A TLS cipher suite. Used mostly internally. -#[derive(Clone, Copy)] -pub struct KtlsCipherSuite { - /// The TLS version - pub version: KtlsVersion, - - /// The cipher type - pub typ: KtlsCipherType, -} - -/// Cipher types supported by this crate -#[non_exhaustive] -#[derive(Debug, Clone, Copy)] -pub enum KtlsCipherType { - AesGcm128, - AesGcm256, - Chacha20Poly1305, -} - -#[derive(Debug, thiserror::Error)] -pub enum CipherSuiteError { - #[error("TLS 1.2 support not built in")] - Tls12NotBuiltIn, - - #[error("unsupported cipher suite")] - UnsupportedCipherSuite(SupportedCipherSuite), -} - -impl TryFrom for KtlsCipherSuite { - type Error = CipherSuiteError; - - fn try_from(#[allow(unused)] suite: SupportedCipherSuite) -> Result { - { - let version = match suite { - SupportedCipherSuite::Tls12(..) => { - if !cfg!(feature = "tls12") { - return Err(CipherSuiteError::Tls12NotBuiltIn); - } - KtlsVersion::TLS12 - } - SupportedCipherSuite::Tls13(..) => KtlsVersion::TLS13, - }; - - let family = { - if suite == cipher_suite::TLS13_AES_128_GCM_SHA256 { - KtlsCipherType::AesGcm128 - } else if suite == cipher_suite::TLS13_AES_256_GCM_SHA384 { - KtlsCipherType::AesGcm256 - } else if suite == cipher_suite::TLS13_CHACHA20_POLY1305_SHA256 { - KtlsCipherType::Chacha20Poly1305 - } else if suite == cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 { - KtlsCipherType::AesGcm128 - } else if suite == cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 { - KtlsCipherType::AesGcm256 - } else if suite == cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 { - KtlsCipherType::Chacha20Poly1305 - } else { - return Err(CipherSuiteError::UnsupportedCipherSuite(suite)); - } - }; - - Ok(Self { - typ: family, - version, - }) - } - } -} - -impl KtlsCipherSuite { - pub fn as_supported_cipher_suite(&self) -> SupportedCipherSuite { - match self.version { - KtlsVersion::TLS12 => match self.typ { - KtlsCipherType::AesGcm128 => cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - KtlsCipherType::AesGcm256 => cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - KtlsCipherType::Chacha20Poly1305 => { - cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 - } - }, - KtlsVersion::TLS13 => match self.typ { - KtlsCipherType::AesGcm128 => cipher_suite::TLS13_AES_128_GCM_SHA256, - KtlsCipherType::AesGcm256 => cipher_suite::TLS13_AES_256_GCM_SHA384, - KtlsCipherType::Chacha20Poly1305 => cipher_suite::TLS13_CHACHA20_POLY1305_SHA256, - }, - } - } -} +pub mod utils; diff --git a/ktls/src/setup.rs b/ktls/src/setup.rs index 9ca743a..b38fe45 100644 --- a/ktls/src/setup.rs +++ b/ktls/src/setup.rs @@ -23,7 +23,7 @@ mod tls; mod ulp; #[cfg(not(feature = "raw-api"))] -pub(crate) use tls::{setup_tls_params, setup_tls_params_rx, setup_tls_params_tx}; +pub(crate) use tls::{setup_tls_params, setup_tls_params_rx, setup_tls_params_tx, TlsCryptoInfoTx}; #[cfg(feature = "raw-api")] pub use tls::{ setup_tls_params, setup_tls_params_rx, setup_tls_params_tx, TlsCryptoInfoRx, TlsCryptoInfoTx, diff --git a/ktls/src/setup/tls.rs b/ktls/src/setup/tls.rs index fab8ed5..529084f 100644 --- a/ktls/src/setup/tls.rs +++ b/ktls/src/setup/tls.rs @@ -119,7 +119,8 @@ pub type TlsCryptoInfoTx = TlsCryptoInfo<{ libc::TLS_TX }>; /// See [`TlsCryptoInfo`]. pub type TlsCryptoInfoRx = TlsCryptoInfo<{ libc::TLS_RX }>; -#[cfg(feature = "raw-api")] +#[cfg(any(feature = "raw-api", feature = "probe-ktls-compatibility"))] +#[allow(unreachable_pub)] impl TlsCryptoInfo { /// Create a custom [`TlsCryptoInfo`] from the given /// [`libc::tls12_crypto_info_aes_gcm_128`]. @@ -145,7 +146,9 @@ impl TlsCryptoInfo { /// Sets the kTLS parameters on the given file descriptor. pub fn set(&self, fd: &Fd) -> Result<(), Error> { - setsockopt(fd, TcpTls {}, self).map_err(io::Error::from) + setsockopt(fd, TcpTls {}, self) + .map_err(io::Error::from) + .map_err(Error::IO) } } diff --git a/ktls/src/utils.rs b/ktls/src/utils.rs new file mode 100644 index 0000000..257821a --- /dev/null +++ b/ktls/src/utils.rs @@ -0,0 +1,5 @@ +//! Utilities + +mod suites; + +pub use suites::CompatibleCipherSuites; diff --git a/ktls/src/utils/suites.rs b/ktls/src/utils/suites.rs new file mode 100644 index 0000000..30f148d --- /dev/null +++ b/ktls/src/utils/suites.rs @@ -0,0 +1,193 @@ +//! kTLS cipher suite compatibility probe + +use std::collections::HashSet; +use std::io; +use std::net::{TcpListener, TcpStream}; + +use crate::setup::{setup_ulp, SetupError, TlsCryptoInfoTx}; +use rustls::{CipherSuite, SupportedCipherSuite, SupportedProtocolVersion}; + +#[allow(clippy::module_name_repetitions)] +#[derive(Debug, Clone)] +/// A collection of compatible cipher suites for current kernel. +pub struct CompatibleCipherSuites { + suites: HashSet, + + /// The supported protocol versions. + pub protocol_versions: &'static [&'static SupportedProtocolVersion], +} + +impl CompatibleCipherSuites { + /// Probes the current Linux kernel for kTLS cipher suite compatibility. + /// + /// Returns `None` if the kernel does not support kTLS, otherwise returns + /// a `CompatibleCipherSuites` containing supported cipher suites and + /// protocol versions. + /// + /// # Notes + /// + /// - The caller may enable feature `rustls/tls12` to include TLS 1.2 + /// support, or the protocol versions may be empty if only TLS 1.2 is + /// supported by current Linux kernel. + /// - The caller may cache the result, as probing is expensive. + /// + /// ## Errors + /// + /// [`io::Error`]. + pub fn probe() -> io::Result> { + let listener = TcpListener::bind("127.0.0.1:0")?; + + let local_addr = listener.local_addr()?; + + let mut inner = HashSet::new(); + + let mut tls12_supported = false; + let mut tls13_supported = false; + + macro_rules! test_param { + ($method:ident, $data:ident, $version:expr, $cipher_type:expr) => {{ + let stream = match setup_ulp(TcpStream::connect(local_addr)?) { + Ok(stream) => stream, + Err(SetupError { + socket: Some(_), .. + }) => { + // kTLS is not supported + return Ok(None); + } + Err(SetupError { error, .. }) => { + return Err(error); + } + }; + + #[allow(unsafe_code)] + // SAFETY: zeroed is fine for libc structs as we will set all the fields + let mut data: libc::$data = unsafe { std::mem::zeroed() }; + + data.info = libc::tls_crypto_info { + version: $version, + cipher_type: $cipher_type, + }; + + TlsCryptoInfoTx::$method(data).set(&stream).is_ok() + }}; + } + + // Test TLS 1.2, AES-GCM-128 + if test_param!( + custom_aes_128_gcm, + tls12_crypto_info_aes_gcm_128, + libc::TLS_1_2_VERSION, + libc::TLS_CIPHER_AES_GCM_128 + ) { + inner.insert(CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256.into()); + inner.insert(CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256.into()); + + tls12_supported = true; + } + + // Test TLS 1.2, AES-GCM-256 + if test_param!( + custom_aes_256_gcm, + tls12_crypto_info_aes_gcm_256, + libc::TLS_1_2_VERSION, + libc::TLS_CIPHER_AES_GCM_256 + ) { + inner.insert(CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384.into()); + inner.insert(CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384.into()); + + tls12_supported = true; + } + + // Test TLS 1.2, ChaCha20-Poly1305 + if test_param!( + custom_chacha20_poly1305, + tls12_crypto_info_chacha20_poly1305, + libc::TLS_1_2_VERSION, + libc::TLS_CIPHER_CHACHA20_POLY1305 + ) { + inner.insert(CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256.into()); + inner.insert(CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256.into()); + + tls12_supported = true; + } + + // Test TLS 1.3, AES-GCM-128 + if test_param!( + custom_aes_128_gcm, + tls12_crypto_info_aes_gcm_128, + libc::TLS_1_3_VERSION, + libc::TLS_CIPHER_AES_GCM_128 + ) { + inner.insert(CipherSuite::TLS13_AES_128_GCM_SHA256.into()); + + tls13_supported = true; + } + + // Test TLS 1.3, AES-GCM-256 + if test_param!( + custom_aes_256_gcm, + tls12_crypto_info_aes_gcm_256, + libc::TLS_1_3_VERSION, + libc::TLS_CIPHER_AES_GCM_256 + ) { + inner.insert(CipherSuite::TLS13_AES_256_GCM_SHA384.into()); + + tls13_supported = true; + } + + // Test TLS 1.3, ChaCha20-Poly1305 + if test_param!( + custom_chacha20_poly1305, + tls12_crypto_info_chacha20_poly1305, + libc::TLS_1_3_VERSION, + libc::TLS_CIPHER_CHACHA20_POLY1305 + ) { + inner.insert(CipherSuite::TLS13_CHACHA20_POLY1305_SHA256.into()); + + tls13_supported = true; + } + + Ok(Some(Self { + suites: inner, + protocol_versions: match (tls12_supported, tls13_supported) { + (true, true) => rustls::DEFAULT_VERSIONS, + // The first element is TLS 1.3 + (false, true) => &rustls::DEFAULT_VERSIONS[..1], + // The first element is TLS 1.2 (maybe, but empty slice is OK, let the caller handle + // it) + (true, false) => &rustls::DEFAULT_VERSIONS[1..], + // No supported versions + (false, false) => return Ok(None), + }, + })) + } + + /// Filters the provided cipher suites list in place, removing suites + /// which is incompatible. + /// + /// ## Examples + /// + /// ```no_run + /// use std::sync::Arc; + /// + /// use ktls_util::suites::CompatibleCipherSuites; + /// use rustls::crypto::CryptoProvider; + /// + /// // Get a crypto provider, for example, the default ring provider: + /// let mut crypto_provider = rustls::crypto::ring::default_provider(); + /// + /// // Filter it: + /// let compatible_ciphers: &CompatibleCipherSuites = ...; + /// compatible_ciphers.filter(&mut crypto_provider.cipher_suites); + /// + /// // Create client/server configuration with`builder_with_provider`, for example: + /// let root_store = ...; + /// let config = rustls::ClientConfig::builder_with_provider(Arc::new(crypto_provider)) + /// .with_protocol_versions(compatible_ciphers.protocol_versions)? + /// .with_root_certificates(root_store) + /// .with_no_client_auth(); + /// ``` + pub fn filter(&self, suite: &mut Vec) { + suite.retain(|s| self.suites.contains(&s.suite().into())); + } +} From 0db74e5bb1d7a25a66bf329c0685088aeb05c3ae Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 10:33:52 +0800 Subject: [PATCH 13/33] PR 62: partial: export most-frequently-used type --- ktls/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ktls/src/lib.rs b/ktls/src/lib.rs index 5bcd521..6885f1b 100644 --- a/ktls/src/lib.rs +++ b/ktls/src/lib.rs @@ -28,3 +28,6 @@ mod protocol; pub mod setup; pub mod stream; pub mod utils; + +pub use error::Error; +pub use stream::KtlsStream; From 25b590950b81bff5e8f1933bf49d52d03dbeb030 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 10:31:47 +0800 Subject: [PATCH 14/33] PR 62: partial: prepare ktls-util crate --- Cargo.lock | 4 ++++ Cargo.toml | 2 +- ktls-util/Cargo.toml | 14 ++++++++++++++ ktls-util/src/lib.rs | 20 ++++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 ktls-util/Cargo.toml create mode 100644 ktls-util/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 1dad207..5a3392d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,6 +149,10 @@ dependencies = [ name = "ktls-sys" version = "1.0.2" +[[package]] +name = "ktls-util" +version = "0.0.0" + [[package]] name = "lazy_static" version = "1.5.0" diff --git a/Cargo.toml b/Cargo.toml index 2b61f3a..0b3c37e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["ktls", "ktls-sys"] +members = ["ktls", "ktls-sys", "ktls-util"] resolver = "2" [workspace.package] diff --git a/ktls-util/Cargo.toml b/ktls-util/Cargo.toml new file mode 100644 index 0000000..7068c33 --- /dev/null +++ b/ktls-util/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "ktls-util" +version = "0.0.0" +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +categories.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true +publish = false # Currently for test only + +[dependencies] diff --git a/ktls-util/src/lib.rs b/ktls-util/src/lib.rs new file mode 100644 index 0000000..26d49b7 --- /dev/null +++ b/ktls-util/src/lib.rs @@ -0,0 +1,20 @@ +//! Utilities for ktls crate. + +#![warn( + unsafe_code, + unused_must_use, + clippy::alloc_instead_of_core, + clippy::exhaustive_enums, + clippy::exhaustive_structs, + clippy::manual_let_else, + clippy::use_self, + clippy::upper_case_acronyms, + elided_lifetimes_in_paths, + missing_docs, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unused_import_braces, + unused_extern_crates, + unused_qualifications +)] From 722f28e1a6279a4a9c6045e4b955990122405ba9 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 10:36:53 +0800 Subject: [PATCH 15/33] PR 62: partial: implement KtlsConnector in ktls-util --- Cargo.lock | 266 +++++++++++++++++++++++++++++++++++++++- ktls-util/Cargo.toml | 16 +++ ktls-util/src/client.rs | 127 +++++++++++++++++++ ktls-util/src/error.rs | 38 ++++++ ktls-util/src/lib.rs | 57 +++++++++ 5 files changed, 501 insertions(+), 3 deletions(-) create mode 100644 ktls-util/src/client.rs create mode 100644 ktls-util/src/error.rs diff --git a/Cargo.lock b/Cargo.lock index 5a3392d..a892703 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,29 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -53,6 +76,29 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + [[package]] name = "bitflags" version = "2.9.3" @@ -71,9 +117,20 @@ version = "1.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" dependencies = [ + "jobserver", + "libc", "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.3" @@ -86,6 +143,26 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + [[package]] name = "deranged" version = "0.4.0" @@ -95,6 +172,34 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "getrandom" version = "0.2.16" @@ -103,7 +208,19 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.3+wasi-0.2.4", ] [[package]] @@ -112,6 +229,21 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "io-uring" version = "0.7.10" @@ -123,6 +255,25 @@ dependencies = [ "libc", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + [[package]] name = "ktls" version = "6.0.2" @@ -152,6 +303,12 @@ version = "1.0.2" [[package]] name = "ktls-util" version = "0.0.0" +dependencies = [ + "ktls", + "rustls", + "thiserror", + "tokio", +] [[package]] name = "lazy_static" @@ -159,12 +316,34 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "lock_api" version = "0.4.13" @@ -205,6 +384,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -221,7 +406,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -238,6 +423,16 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -346,6 +541,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.101" @@ -364,6 +569,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rcgen" version = "0.13.2" @@ -438,7 +649,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -450,13 +661,34 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustls" version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ + "aws-lc-rs", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -478,6 +710,7 @@ version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -779,6 +1012,27 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasi" +version = "0.14.3+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" @@ -883,6 +1137,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "wit-bindgen" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" + [[package]] name = "yasna" version = "0.5.2" diff --git a/ktls-util/Cargo.toml b/ktls-util/Cargo.toml index 7068c33..6c7890d 100644 --- a/ktls-util/Cargo.toml +++ b/ktls-util/Cargo.toml @@ -12,3 +12,19 @@ repository.workspace = true publish = false # Currently for test only [dependencies] +ktls = { path = "../ktls", default-features = false, features = ["raw-api"] } +rustls = { version = "0.23.27", default-features = false, features = ["std"] } +thiserror = "2.0.16" +tokio = { version = "1.40", features = ["io-util"] } + +[features] +default = ["tls12", "aws-lc-rs"] + +# Enable rustls TLS 1.2 support +tls12 = ["ktls/tls12", "rustls/tls12"] + +# Enable rustls ring crypto backend +ring = ["rustls/ring"] + +# Enable rustls aws-lc-rs crypto backend +aws-lc-rs = ["rustls/aws-lc-rs"] diff --git a/ktls-util/src/client.rs b/ktls-util/src/client.rs new file mode 100644 index 0000000..cc0d179 --- /dev/null +++ b/ktls-util/src/client.rs @@ -0,0 +1,127 @@ +//! A TLS connector with kTLS offload support. + +// TODO: Well, this should be included in `tokio-rustls`? Or we should provide +// both sync/async versions? + +use std::io; +use std::os::fd::AsFd; +use std::sync::Arc; + +use ktls::setup::{setup_ulp, SetupError}; +use ktls::stream::KtlsStream; +use rustls::client::UnbufferedClientConnection; +use rustls::pki_types::ServerName; +use rustls::unbuffered::{ConnectionState, EncodeError, UnbufferedStatus}; +use rustls::ClientConfig; +use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt}; + +use crate::error::Error; +use crate::read_record; + +#[derive(Debug, Clone)] +/// A TLS connector with kTLS offload support. +pub struct KtlsConnector { + config: Arc, +} + +impl KtlsConnector { + #[must_use] + /// Create a new [`KtlsConnector`] with the given [`ClientConfig`]. + pub const fn new(config: Arc) -> Self { + Self { config } + } + + /// Connects to a TLS server using the given socket and server name. + /// + /// ## Errors + /// + /// [`SetupError`]. This may contain the original socket if the setup failed + /// and the caller can fallback to normal TLS connector implementation. + pub async fn try_connect( + &self, + socket: S, + server_name: ServerName<'static>, + ) -> Result, SetupError> + where + S: AsyncRead + AsyncWrite + AsFd + Unpin, + { + let socket = setup_ulp(socket)?; + + self.internal_try_connect(socket, server_name) + .await + .map_err(|error| SetupError { + error: io::Error::other(error), + socket: None, + }) + } + + // `rustls` has poor support for async/await... + async fn internal_try_connect( + &self, + mut socket: S, + server_name: ServerName<'static>, + ) -> Result, Error> + where + S: AsyncRead + AsyncWrite + AsFd + Unpin, + { + let mut conn = UnbufferedClientConnection::new(self.config.clone(), server_name) + .map_err(Error::Config)?; + + let mut incoming = Vec::with_capacity(u16::MAX as usize + 5); + let mut outgoing = Vec::with_capacity(u16::MAX as usize + 5); + let mut outgoing_used = 0usize; + + loop { + let UnbufferedStatus { discard, state } = conn.process_tls_records(&mut incoming); + + let state = state.map_err(Error::Handshake)?; + + match state { + ConnectionState::BlockedHandshake => { + read_record(&mut socket, &mut incoming).await?; + } + ConnectionState::PeerClosed | ConnectionState::Closed => { + return Err(Error::ConnectionClosedBeforeHandshakeCompleted); + } + ConnectionState::EncodeTlsData(mut state) => { + match state.encode(&mut outgoing[outgoing_used..]) { + Ok(count) => outgoing_used += count, + Err(EncodeError::AlreadyEncoded) => unreachable!(), + Err(EncodeError::InsufficientSize(e)) => { + outgoing.resize(outgoing_used + e.required_size, 0u8); + + match state.encode(&mut outgoing[outgoing_used..]) { + Ok(count) => outgoing_used += count, + Err(e) => unreachable!("encode failed after resizing buffer: {e}"), + } + } + } + } + ConnectionState::TransmitTlsData(data) => { + // FIXME: may_encrypt_app_data to check if we can send early data? + + socket + .write_all(&outgoing[..outgoing_used]) + .await + .map_err(Error::IO)?; + outgoing_used = 0; + data.done(); + } + ConnectionState::WriteTraffic(_) => { + // Handshake is done + incoming.drain(..discard); + + break; + } + ConnectionState::ReadTraffic(_) => unreachable!( + "ReadTraffic should not be encountered during the handshake process" + ), + _ => unreachable!("unexpected connection state"), + } + + incoming.drain(..discard); + } + + KtlsStream::from_unbuffered_client_connnection(socket, conn).map_err(Error::Ktls) + } +} diff --git a/ktls-util/src/error.rs b/ktls-util/src/error.rs new file mode 100644 index 0000000..c942c1b --- /dev/null +++ b/ktls-util/src/error.rs @@ -0,0 +1,38 @@ +//! Errors + +use std::io; + +#[non_exhaustive] +#[derive(Debug, thiserror::Error)] +/// Unified error type for this crate +pub(crate) enum Error { + #[error(transparent)] + /// General IO error. + IO(#[from] io::Error), + + #[error("The peer closed the connection before the TLS handshake could be completed")] + /// (Reserved) The peer closed the connection before the TLS handshake could + /// be completed. + ConnectionClosedBeforeHandshakeCompleted, + + #[error("Failed to create rustls unbuffered connection: {0}")] + /// (Reserved) + Config(#[source] rustls::Error), + + #[error("An error occurred during handshake: {0}")] + /// (Reserved) + Handshake(#[source] rustls::Error), + + #[error(transparent)] + /// Errors from ktls crate. + Ktls(#[from] ktls::Error), +} + +impl From for io::Error { + fn from(error: Error) -> Self { + match error { + Error::IO(error) => error, + _ => Self::other(error), + } + } +} diff --git a/ktls-util/src/lib.rs b/ktls-util/src/lib.rs index 26d49b7..c4709a5 100644 --- a/ktls-util/src/lib.rs +++ b/ktls-util/src/lib.rs @@ -18,3 +18,60 @@ unused_extern_crates, unused_qualifications )] + +use std::{io, slice}; + +use tokio::io::{AsyncRead, AsyncReadExt}; + +pub mod client; +pub(crate) mod error; + +pub(crate) async fn read_record(socket: &mut S, incoming: &mut Vec) -> io::Result<()> +where + S: AsyncRead + Unpin, +{ + const RECORD_HDR_SIZE: usize = 5; + + incoming.reserve(RECORD_HDR_SIZE); + + #[allow(unsafe_code)] + // Safety: We just reserved enough space for the header. + let record_hdr = unsafe { + slice::from_raw_parts_mut( + incoming.spare_capacity_mut().as_mut_ptr().cast(), + RECORD_HDR_SIZE, + ) + }; + + socket + .read_exact(record_hdr) + .await + .map_err(ktls::Error::IO)?; + + let payload_length = u16::from_be_bytes([record_hdr[3], record_hdr[4]]) as usize; + + incoming.reserve(payload_length); + + #[allow(unsafe_code)] + // Safety: We just reserved enough space for the payload. + let payload = unsafe { + slice::from_raw_parts_mut( + incoming + .spare_capacity_mut() + .as_mut_ptr() + .add(RECORD_HDR_SIZE) + .cast(), + payload_length, + ) + }; + + socket.read_exact(payload).await.map_err(ktls::Error::IO)?; + + #[allow(unsafe_code)] + // Safety: We have just read data into the space we reserved. + unsafe { + incoming.set_len(incoming.len() + RECORD_HDR_SIZE + payload_length); + } + + Ok(()) +} From 0fa74fef56f9d6f247098ba95e02a5f98b894eec Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 10:37:29 +0800 Subject: [PATCH 16/33] PR 62: partial: implement KtlsAcceptor in ktls-util --- ktls-util/src/lib.rs | 1 + ktls-util/src/server.rs | 124 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 ktls-util/src/server.rs diff --git a/ktls-util/src/lib.rs b/ktls-util/src/lib.rs index c4709a5..3919a08 100644 --- a/ktls-util/src/lib.rs +++ b/ktls-util/src/lib.rs @@ -25,6 +25,7 @@ use tokio::io::{AsyncRead, AsyncReadExt}; pub mod client; pub(crate) mod error; +pub mod server; pub(crate) async fn read_record(socket: &mut S, incoming: &mut Vec) -> io::Result<()> where diff --git a/ktls-util/src/server.rs b/ktls-util/src/server.rs new file mode 100644 index 0000000..a5cb59f --- /dev/null +++ b/ktls-util/src/server.rs @@ -0,0 +1,124 @@ +//! A TLS acceptor with kTLS offload support. + +// TODO: Well, this should be included in `tokio-rustls`? Or we should provide +// both sync/async versions? + +use std::io; +use std::os::fd::AsFd; +use std::sync::Arc; + +use ktls::setup::{setup_ulp, SetupError}; +use ktls::stream::KtlsStream; +use rustls::server::UnbufferedServerConnection; +use rustls::unbuffered::{ConnectionState, EncodeError, UnbufferedStatus}; +use rustls::ServerConfig; +use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt}; + +use crate::error::Error; +use crate::read_record; + +#[derive(Debug, Clone)] +/// A TLS acceptor with kTLS offload support. +pub struct KtlsAcceptor { + config: Arc, +} + +impl KtlsAcceptor { + #[must_use] + /// Create a new [`KtlsAcceptor`] with the given [`ServerConfig`]. + pub const fn new(config: Arc) -> Self { + Self { config } + } + + /// Accepts a TLS connection on the given socket. + /// + /// ## Errors + /// + /// [`SetupError`]. This may contain the original socket if the setup failed + /// and the caller can fallback to normal TLS acceptor implementation. + pub async fn try_accept(&self, socket: S) -> Result, SetupError> + where + S: AsyncRead + AsyncWrite + AsFd + Unpin, + { + let socket = setup_ulp(socket)?; + + self.internal_try_accept(socket) + .await + .map_err(|error| SetupError { + error: io::Error::other(error), + socket: None, + }) + } + + async fn internal_try_accept(&self, mut socket: S) -> Result, Error> + where + S: AsyncWrite + AsyncRead + AsFd + Unpin, + { + let mut conn = + UnbufferedServerConnection::new(self.config.clone()).map_err(Error::Config)?; + + let mut incoming = Vec::with_capacity(u16::MAX as usize + 5); + let mut outgoing = Vec::with_capacity(u16::MAX as usize + 5); + let mut outgoing_used = 0usize; + let mut early_data_received = Vec::new(); + + loop { + let UnbufferedStatus { mut discard, state } = conn.process_tls_records(&mut incoming); + + let state = state.map_err(Error::Handshake)?; + + match state { + ConnectionState::BlockedHandshake => { + read_record(&mut socket, &mut incoming).await?; + } + ConnectionState::PeerClosed | ConnectionState::Closed => { + return Err(Error::ConnectionClosedBeforeHandshakeCompleted); + } + ConnectionState::ReadEarlyData(mut data) => { + while let Some(record) = data.next_record() { + let record = record.map_err(Error::Handshake)?; + + discard += record.discard; + + early_data_received.extend_from_slice(record.payload); + } + } + ConnectionState::EncodeTlsData(mut state) => { + match state.encode(&mut outgoing[outgoing_used..]) { + Ok(count) => outgoing_used += count, + Err(EncodeError::AlreadyEncoded) => unreachable!(), + Err(EncodeError::InsufficientSize(e)) => { + outgoing.resize(outgoing_used + e.required_size, 0u8); + + match state.encode(&mut outgoing[outgoing_used..]) { + Ok(count) => outgoing_used += count, + Err(e) => unreachable!("encode failed after resizing buffer: {e}"), + } + } + } + } + ConnectionState::TransmitTlsData(data) => { + socket + .write_all(&outgoing[..outgoing_used]) + .await + .map_err(Error::IO)?; + outgoing_used = 0; + data.done(); + } + ConnectionState::WriteTraffic(_) => { + incoming.drain(..discard); + break; + } + ConnectionState::ReadTraffic(_) => unreachable!( + "ReadTraffic should not be encountered during the handshake process" + ), + _ => unreachable!("unexpected connection state"), + } + + incoming.drain(..discard); + } + + KtlsStream::from_unbuffered_server_connnection(socket, conn, Some(early_data_received)) + .map_err(Error::Ktls) + } +} From 677c79d2b09fba2dcc4452296fbc4f036adb4015 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 10:51:03 +0800 Subject: [PATCH 17/33] PR 62: partial: export raw-apis of KtlsStream --- ktls/src/stream.rs | 75 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/ktls/src/stream.rs b/ktls/src/stream.rs index cf68d0a..4bbdb86 100644 --- a/ktls/src/stream.rs +++ b/ktls/src/stream.rs @@ -6,6 +6,8 @@ pub mod impl_std; #[cfg(feature = "async-io-tokio")] pub mod impl_tokio; +#[cfg(feature = "raw-api")] +use std::io; use std::os::fd::AsFd; use std::pin::Pin; @@ -13,6 +15,8 @@ use rustls::client::UnbufferedClientConnection; use rustls::server::UnbufferedServerConnection; use crate::error::Error; +#[cfg(feature = "raw-api")] +use crate::stream::context::Buffer; use crate::stream::context::{Context, StreamState, TlsConnData}; const DEFAULT_SCRATCH_CAPACITY: usize = 64; @@ -186,4 +190,75 @@ where Ok(this) } + + #[allow(unsafe_code)] + #[cfg(feature = "raw-api")] + #[inline] + /// Returns a mutable reference to the inner socket if the TLS stream is not + /// closed (unidirectionally or bidirectionally). + /// + /// This requires a mutable reference to the [`KtlsStream`] to ensure a + /// exclusive access to the inner socket. + /// + /// ## Safety + /// + /// The caller must ensure that: + /// + /// * All buffered data **MUST** be retrieved using + /// [`Self::take_buffered_data`] and properly consumed before accessing + /// the inner socket. Buffered data typically consists of: + /// + /// - Early data received during handshake. + /// - Application data received due to improper usage of + /// [`Self::handle_io_result`]. + /// + /// * The caller **MAY** handle any [`io::Result`]s returned by I/O + /// operations on the inner socket with [`Self::handle_io_result`]. + /// + /// * The caller **MUST NOT** shutdown the inner socket directly, which will + /// lead to undefined behaviours. Instead, the caller **MAY** call + /// `(poll_)shutdown` explictly on the [`KtlsStream`] to gracefully + /// shutdown the TLS stream (with `close_notify` be sent) manually, or + /// just drop the stream to do automatic graceful shutdown. + /// + /// [RFC 8446, section 6.1]: https://tools.ietf.org/html/rfc8446#section-6.1 + pub unsafe fn as_raw(&mut self) -> Option<&mut S> { + debug_assert!( + !self.ctx.state().has_buffered_data(), + "Buffered data must be consumed before accessing the inner stream." + ); + + if self.ctx.state().is_partially_closed() { + return None; + } + + Some(&mut self.inner) + } + + #[cfg(feature = "raw-api")] + /// Inspects and handles the [`io::Result`] returned by a I/O operation on + /// the inner socket directly. + /// + /// - If the result is `Ok`, it returns `Some(T)`. + /// - If the errno is [`EIO`](libc::EIO), it tries to handle any TLS control messages + /// received, and returns `None` if succeeded. + /// - Otherwise, it aborts the connection with `internal_error` alert and + /// returns the error. + /// + /// ## Errors + /// + /// The unrecoverable original [`io::Error`]. + pub fn handle_io_result(&mut self, ret: io::Result) -> io::Result> { + self.ctx.handle_io_result(&self.inner, ret) + } + + #[cfg(feature = "raw-api")] + #[must_use = "The buffered data must be handled."] + /// Takes the buffered data, if any, and resets the buffer state. + /// + /// This method is useful and should be called before performing low-level + /// I/O operations on the inner socket. + pub fn take_buffered_data(&mut self) -> Option { + self.ctx.take_buffer() + } } From e58ecf162eb18fd35143ad86f64195f41e92b250 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 10:54:30 +0800 Subject: [PATCH 18/33] PR 62: partial: tests and examples --- Cargo.lock | 87 +++- ktls/Cargo.toml | 13 +- ktls/examples/client.rs | 28 ++ ktls/examples/common/client/mod.rs | 36 ++ ktls/examples/common/client/verifier.rs | 59 +++ ktls/examples/common/mod.rs | 373 ++++++++++++++++ ktls/examples/common/server.rs | 47 ++ ktls/examples/server.rs | 31 ++ ktls/tests/client.rs | 181 ++++++++ ktls/tests/echo.rs | 25 ++ tests/integration_test.rs | 554 ------------------------ 11 files changed, 851 insertions(+), 583 deletions(-) create mode 100644 ktls/examples/client.rs create mode 100644 ktls/examples/common/client/mod.rs create mode 100644 ktls/examples/common/client/verifier.rs create mode 100644 ktls/examples/common/mod.rs create mode 100644 ktls/examples/common/server.rs create mode 100644 ktls/examples/server.rs create mode 100644 ktls/tests/client.rs create mode 100644 ktls/tests/echo.rs delete mode 100644 tests/integration_test.rs diff --git a/Cargo.lock b/Cargo.lock index a892703..e88c15f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,16 +279,15 @@ name = "ktls" version = "6.0.2" dependencies = [ "bitflags", - "lazy_static", + "ktls-util", "libc", "log", "memoffset", "nix", - "oorandom", "pin-project", + "rand", "rcgen", "rustls", - "socket2 0.5.10", "test-case", "thiserror", "tokio", @@ -464,12 +463,6 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "oorandom" -version = "11.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" - [[package]] name = "overload" version = "0.1.1" @@ -541,6 +534,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -575,11 +577,40 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + [[package]] name = "rcgen" -version = "0.13.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +checksum = "0068c5b3cab1d4e271e0bb6539c87563c43411cad90b057b15c79958fbeb41f7" dependencies = [ "pem", "ring", @@ -778,16 +809,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.0" @@ -907,11 +928,10 @@ dependencies = [ "io-uring", "libc", "mio", - "parking_lot", "pin-project-lite", "signal-hook-registry", "slab", - "socket2 0.6.0", + "socket2", "tokio-macros", "windows-sys 0.59.0", ] @@ -979,6 +999,7 @@ dependencies = [ "matchers", "nu-ansi-term", "once_cell", + "parking_lot", "regex", "sharded-slab", "smallvec", @@ -1152,6 +1173,26 @@ dependencies = [ "time", ] +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zeroize" version = "1.8.1" diff --git a/ktls/Cargo.toml b/ktls/Cargo.toml index 4fc4ccf..eccf070 100644 --- a/ktls/Cargo.toml +++ b/ktls/Cargo.toml @@ -27,13 +27,14 @@ tokio = { version = "1.40", optional = true } tracing = { version = "0.1.41", optional = true } [dev-dependencies] -lazy_static = "1.5.0" -oorandom = "11.1.4" -rcgen = "0.13.1" -socket2 = "0.5.7" +ktls-util = { path = "../ktls-util", default-features = false, features = ["tls12", "ring"] } +rand = "0.9.2" +rcgen = { version = "0.14.3" } +rustls = { version = "0.23.27", default-features = false, features = ["std", "ring"] } test-case = "3.3.1" -tokio = { version = "1.39.2", features = ["full"] } -tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +tokio = { version = "1.40", features = ["io-util", "macros", "net", "rt-multi-thread", "signal", "sync", "time"] } +tracing = "0.1.41" +tracing-subscriber = { version = "0.3.19", features = ["env-filter", "parking_lot"] } [features] default = ["tls12", "async-io-tokio", "probe-ktls-compatibility"] diff --git a/ktls/examples/client.rs b/ktls/examples/client.rs new file mode 100644 index 0000000..ed14aef --- /dev/null +++ b/ktls/examples/client.rs @@ -0,0 +1,28 @@ +//! Example: TLS client using `ktls`. + +mod common; + +use std::error::Error; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let (ret1, ret2, ret3, ret4) = tokio::join!( + common::run_echo_test(common::CloseParty::Client, common::TestOption::empty()), + common::run_echo_test( + common::CloseParty::Client, + common::TestOption::HANDLE_IO_RESULT + ), + common::run_echo_test(common::CloseParty::Server, common::TestOption::empty()), + common::run_echo_test( + common::CloseParty::Server, + common::TestOption::HANDLE_IO_RESULT + ), + ); + + ret1?; + ret2?; + ret3?; + ret4?; + + Ok(()) +} diff --git a/ktls/examples/common/client/mod.rs b/ktls/examples/common/client/mod.rs new file mode 100644 index 0000000..cfaf79c --- /dev/null +++ b/ktls/examples/common/client/mod.rs @@ -0,0 +1,36 @@ +//! + +#![allow(dead_code)] + +use std::sync::{Arc, OnceLock}; + +use ktls::utils::CompatibleCipherSuites; +use ktls_util::client::KtlsConnector; + +pub mod verifier; + +/// Example to get a `KtlsConnector`. +pub fn get_ktls_connector(compatible_cipher_suites: &CompatibleCipherSuites) -> KtlsConnector { + static KTLS_CONNECTOR: OnceLock = OnceLock::new(); + + KTLS_CONNECTOR + .get_or_init(|| { + let mut crypto_provider = rustls::crypto::ring::default_provider(); + + compatible_cipher_suites.filter(&mut crypto_provider.cipher_suites); + + let mut config = rustls::ClientConfig::builder_with_provider(Arc::new(crypto_provider)) + .with_protocol_versions(compatible_cipher_suites.protocol_versions) + .expect("invalid protocol versions") + .dangerous() + .with_custom_certificate_verifier(verifier::NoCertificateVerification::new()) + .with_no_client_auth(); + + config.enable_secret_extraction = true; + + tracing::info!("Client config: {config:#?}"); + + KtlsConnector::new(Arc::new(config)) + }) + .clone() +} diff --git a/ktls/examples/common/client/verifier.rs b/ktls/examples/common/client/verifier.rs new file mode 100644 index 0000000..db50890 --- /dev/null +++ b/ktls/examples/common/client/verifier.rs @@ -0,0 +1,59 @@ +use std::sync::Arc; + +use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; +use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; +use rustls::DigitallySignedStruct; + +#[derive(Debug, Clone, Copy)] +pub struct NoCertificateVerification; + +impl NoCertificateVerification { + pub fn new() -> Arc { + Arc::new(Self) + } +} + +impl ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _: &CertificateDer<'_>, + _: &[CertificateDer<'_>], + _: &ServerName<'_>, + _: &[u8], + _: UnixTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _: &[u8], + _: &CertificateDer<'_>, + _: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _: &[u8], + _: &CertificateDer<'_>, + _: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + vec![ + rustls::SignatureScheme::RSA_PKCS1_SHA256, + rustls::SignatureScheme::RSA_PKCS1_SHA384, + rustls::SignatureScheme::RSA_PKCS1_SHA512, + rustls::SignatureScheme::RSA_PSS_SHA512, + rustls::SignatureScheme::RSA_PSS_SHA384, + rustls::SignatureScheme::RSA_PSS_SHA256, + rustls::SignatureScheme::ECDSA_NISTP256_SHA256, + rustls::SignatureScheme::ECDSA_NISTP384_SHA384, + rustls::SignatureScheme::ED25519, + ] + } +} diff --git a/ktls/examples/common/mod.rs b/ktls/examples/common/mod.rs new file mode 100644 index 0000000..af7e9a7 --- /dev/null +++ b/ktls/examples/common/mod.rs @@ -0,0 +1,373 @@ +// Shared code for client and server examples. + +use std::io; +use std::sync::OnceLock; +use std::time::Duration; + +use ktls::utils::CompatibleCipherSuites; +use ktls_util::server::KtlsAcceptor; +use nix::errno::Errno; +use rustls::pki_types::ServerName; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::{TcpListener, TcpStream}; +use tokio::sync::oneshot; +use tokio::time::sleep; + +pub mod client; +pub mod server; + +#[allow(dead_code)] +/// Echo test, shared by examples and tests. +pub async fn run_echo_test(close_party: CloseParty, test_option: TestOption) -> io::Result<()> { + let _ = tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::new("TRACE")) + .pretty() + .try_init(); + + let Some(compatible_cipher_suites) = compatible_cipher_suites() else { + return Ok(()); + }; + + let server_name = ServerName::try_from("localhost").unwrap(); + let listener = TcpListener::bind("0.0.0.0:0").await.expect("Bind error"); + let server_addr = listener.local_addr().expect("Cannot get local_addr"); + + let acceptor = server::get_ktls_acceptor(compatible_cipher_suites); + let connector = client::get_ktls_connector(compatible_cipher_suites); + + // Start echo server + let (shutdown_signal_tx, shutdown_signal_rx) = oneshot::channel::(); + let server_handle = tokio::spawn(echo_server( + listener, + acceptor, + shutdown_signal_rx, + test_option, + )); + + let mut ktls_stream = connector + .try_connect( + TcpStream::connect(server_addr) + .await + .expect("Connect to server error"), + server_name, + ) + .await + .map_err(io::Error::other)?; + + let test_data = test_data(); + + { + // Test: poll_write + ktls_stream.write_all(&test_data[..16]).await?; + tracing::info!("Sent 16 bytes"); + + let mut buf = [0u8; 16]; + + ktls_stream.read_exact(&mut buf).await?; + + assert!(buf == &test_data[..16]); + } + + { + // Test: poll_flush + ktls_stream.write_all(&test_data[..16]).await?; + ktls_stream.flush().await?; + tracing::info!("Sent 16 bytes and flushed"); + + let mut buf = [0u8; 16]; + + ktls_stream.read_exact(&mut buf).await?; + + assert!(buf == &test_data[..16]); + } + + { + const CHUNK_SIZE: usize = 17; + const VECTORED_WRITE_COUNT: usize = 5; + const TOTAL: usize = CHUNK_SIZE * VECTORED_WRITE_COUNT; + + let bufs: Vec<_> = test_data.chunks(17).map(io::IoSlice::new).take(5).collect(); + + // Test: poll_write_vectored + let mut has_read = ktls_stream.write_vectored(&bufs).await?; + + if let Some(buf) = test_data.get(has_read..TOTAL) { + tracing::warn!( + "Not all data sent, sent {has_read} bytes, remaining {} bytes", + buf.len() + ); + + ktls_stream.write_all(buf).await?; + + has_read += buf.len(); + } + + assert_eq!(has_read, TOTAL); + + let mut buf = [0u8; TOTAL]; + + ktls_stream.read_exact(&mut buf).await?; + + assert!(buf == &test_data[..TOTAL]); + } + + { + // Test: large data (> u16::MAX) + ktls_stream.write_all(test_data).await?; + tracing::info!("Sent {} bytes", test_data.len()); + + let mut buf = vec![0u8; test_data.len()]; + + ktls_stream.read_exact(&mut buf).await?; + + assert!(buf == test_data); + } + + match close_party { + CloseParty::Client => { + tracing::info!("Client performing active shutdown"); + ktls_stream.shutdown().await?; + + // Try write after shutdown, should write 0 bytes + let n = ktls_stream.write(b"after shutdown").await?; + assert_eq!(n, 0); + + // Try write vectored after shutdown, should write 0 bytes + let bufs = [io::IoSlice::new(b"after shutdown vectored")]; + let n = ktls_stream.write_vectored(&bufs).await?; + assert_eq!(n, 0); + + // Try flush after shutdown, should be ok + ktls_stream.flush().await?; + + server_handle.await??; + } + CloseParty::Server => { + tracing::info!("Client performing passive shutdown, waiting for server to close"); + + // Notify server to close + shutdown_signal_tx.send(false).unwrap(); + + // Try read after server closed, should read 0 bytes (EOF) + let mut buf = [0u8; 16]; + + let n = ktls_stream.read(&mut buf).await?; + + assert_eq!(n, 0); + } + } + + Ok(()) +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy)] +/// Which party will close the connection actively. +pub enum CloseParty { + /// + Client, + + /// + Server, +} + +fn test_data() -> &'static [u8] { + static BUFFER: OnceLock> = OnceLock::new(); + + BUFFER.get_or_init(|| { + let mut v = vec![0; u16::MAX as usize + 1]; + + v.fill_with(rand::random); + + v + }) +} + +bitflags::bitflags! { + #[derive(Debug, Clone, Copy)] + /// Options for echo test. + pub struct TestOption: u8 { + /// Whether to test vectored write. + const HANDLE_IO_RESULT = 0b0000_0001; + } +} + +#[allow(dead_code)] +#[tracing::instrument(err)] +/// A simple echo server. +pub async fn echo_server( + listener: TcpListener, + acceptor: KtlsAcceptor, + mut shutdown_signal_rx: oneshot::Receiver, + test_option: TestOption, +) -> io::Result<()> { + match listener.accept().await { + Ok((stream, remote_addr)) => { + tracing::info!("Accepted connection from {remote_addr}"); + + match acceptor.try_accept(stream).await { + Ok(mut ktls_stream) => { + tracing::info!("Connection established with kTLS"); + + let mut buf = [0u8; 1024]; + + loop { + if test_option.contains(TestOption::HANDLE_IO_RESULT) { + tracing::info!("Testing improper usage of `handle_io_result`"); + + // Try read application data with improper usage of handle_io_result + ktls_stream.handle_io_result(Err::<(), _>(Errno::EIO.into()))?; + + // Again + ktls_stream.handle_io_result(Err::<(), _>(Errno::EIO.into()))?; + } + + let ret = tokio::select! { + biased; + is_brutal = &mut shutdown_signal_rx => { + tracing::info!("Received shutdown signal, is_brutal: {:?}", is_brutal); + + if is_brutal.unwrap_or(false) { + // Drop the connection brutally + drop(ktls_stream); + } else { + ktls_stream.shutdown().await.unwrap(); + + // Try write after shutdown, should write 0 bytes + let n = ktls_stream.write(b"after shutdown").await.unwrap(); + + assert_eq!(n, 0); + } + + loop { + sleep(Duration::from_secs(1)).await; + } + } + ret = ktls_stream.read(&mut buf) => ret + }; + + match ret { + Ok(0) => { + tracing::info!("Read EOF, client closed connection"); + break; + } + Ok(n) => { + tracing::trace!("Received {n} bytes"); + + if let Err(e) = ktls_stream.write_all(&buf[..n]).await { + tracing::error!( + "Failed to write to stream from {remote_addr}: {e:#?}" + ); + + break; + } + } + Err(e) => { + tracing::error!( + "Failed to read from stream from {remote_addr}: {e:#?}" + ); + + break; + } + } + } + } + Err(e) => { + tracing::error!("Failed to accept connection from {remote_addr}: {e:#?}"); + + return Err(io::Error::other(e)); + } + } + } + Err(e) => { + tracing::error!("Failed to accept connection: {e:#?}"); + + return Err(e); + } + } + + tracing::info!("Server shutting down"); + + Ok(()) +} + +#[allow(dead_code)] +#[tracing::instrument(err)] +/// A simple echo server. +pub async fn echo_server_loop(listener: TcpListener, acceptor: KtlsAcceptor) -> io::Result<()> { + loop { + match listener.accept().await { + Ok((stream, remote_addr)) => { + tracing::info!("Accepted connection from {remote_addr}"); + + match acceptor.try_accept(stream).await { + Ok(mut ktls_stream) => { + tokio::spawn(async move { + tracing::info!("Connection established with kTLS"); + + let mut buf = [0u8; 1024]; + + loop { + match ktls_stream.read(&mut buf).await { + Ok(0) => { + tracing::info!("Read EOF, client closed connection"); + break; + } + Ok(n) => { + tracing::trace!("Received {n} bytes"); + + if let Err(e) = ktls_stream.write_all(&buf[..n]).await { + tracing::error!( + "Failed to write to stream from {remote_addr}: \ + {e:#?}" + ); + + break; + } + } + Err(e) => { + tracing::error!( + "Failed to read from stream from {remote_addr}: {e:#?}" + ); + + break; + } + } + } + }); + } + Err(e) => { + tracing::error!("Failed to accept connection from {remote_addr}: {e:#?}"); + + return Err(io::Error::other(e)); + } + } + } + Err(e) => { + tracing::error!("Failed to accept connection: {e:#?}"); + + return Err(e); + } + } + } +} + +#[allow(dead_code)] +/// Get the compatible cipher suites for the current kernel. +pub fn compatible_cipher_suites() -> Option<&'static CompatibleCipherSuites> { + static COMPATIBLE_CIPHER_SUITES: OnceLock> = OnceLock::new(); + + COMPATIBLE_CIPHER_SUITES + .get_or_init(|| { + let c = CompatibleCipherSuites::probe().expect("probe error"); + + if let Some(c) = &c { + tracing::info!("Compatible cipher suites: {c:#?}"); + } else { + tracing::info!("The current kernel does not support kTLS"); + } + + c + }) + .as_ref() +} diff --git a/ktls/examples/common/server.rs b/ktls/examples/common/server.rs new file mode 100644 index 0000000..8f0db7e --- /dev/null +++ b/ktls/examples/common/server.rs @@ -0,0 +1,47 @@ +//! Example: TLS server using `ktls`. + +#![allow(dead_code)] + +use std::sync::{Arc, OnceLock}; + +use ktls::utils::CompatibleCipherSuites; +use ktls_util::server::KtlsAcceptor; +use rcgen::{generate_simple_self_signed, CertifiedKey}; +use rustls::pki_types::PrivateKeyDer; +use rustls::ServerConfig; + +/// Example to get a global `KtlsAcceptor`. +pub fn get_ktls_acceptor(compatible_cipher_suites: &CompatibleCipherSuites) -> KtlsAcceptor { + static KTLS_ACCEPTOR: OnceLock = OnceLock::new(); + + KTLS_ACCEPTOR + .get_or_init(|| { + let subject_alt_names = vec!["localhost".to_string()]; + + let CertifiedKey { cert, signing_key } = + generate_simple_self_signed(subject_alt_names).unwrap(); + + let mut crypto_provider = rustls::crypto::ring::default_provider(); + + compatible_cipher_suites.filter(&mut crypto_provider.cipher_suites); + + let mut config = ServerConfig::builder_with_provider(Arc::new(crypto_provider)) + .with_protocol_versions(compatible_cipher_suites.protocol_versions) + .expect("invalid protocol versions") + .with_no_client_auth() + .with_single_cert( + vec![cert.der().clone()], + PrivateKeyDer::try_from(signing_key.serialized_der()) + .expect("invalid key") + .clone_key(), + ) + .expect("invalid certificate/key"); + + config.enable_secret_extraction = true; + + tracing::info!("Server config: {config:#?}"); + + KtlsAcceptor::new(Arc::new(config)) + }) + .clone() +} diff --git a/ktls/examples/server.rs b/ktls/examples/server.rs new file mode 100644 index 0000000..66e0a66 --- /dev/null +++ b/ktls/examples/server.rs @@ -0,0 +1,31 @@ +//! Example: TLS server using `ktls`. + +use tokio::net::TcpListener; + +mod common; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let _ = tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::new("TRACE")) + .try_init(); + + let Some(compatible_cipher_suites) = common::compatible_cipher_suites() else { + return Ok(()); + }; + + let listener = TcpListener::bind("0.0.0.0:8443").await.expect("Bind error"); + let acceptor = common::server::get_ktls_acceptor(compatible_cipher_suites); + + tokio::select! { + biased; + _ = tokio::signal::ctrl_c() => { + tracing::info!("Received Ctrl + C..."); + } + result = common::echo_server_loop(listener, acceptor) => { + result?; + } + } + + Ok(()) +} diff --git a/ktls/tests/client.rs b/ktls/tests/client.rs new file mode 100644 index 0000000..7ba4cdf --- /dev/null +++ b/ktls/tests/client.rs @@ -0,0 +1,181 @@ +//! Test: client connect to real world websites. + +use core::num::NonZeroUsize; +use core::time::Duration; +use std::io; + +use ktls::KtlsStream; +use rustls::pki_types::ServerName; +use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpStream; +use tokio::time::timeout; + +mod common { + include!("../examples/common/mod.rs"); +} + +#[test_case::test_matrix( + [ + "www.google.com", // Google CDN + "www.bing.com", // Azure CDN + "github.com", // Azure CDN + "www.baidu.com", // Baidu CDN + "stackoverflow.com", // Cloudflare CDN + "fastly.com", // Fastly CDN + ] +)] +#[tokio::test] +async fn test_connecct_sites(server_name: &'static str) -> io::Result<()> { + timeout( + Duration::from_secs(10), + test_connecct_sites_impl(server_name), + ) + .await + .unwrap_or_else(|e| { + tracing::warn!("Test to {server_name} timed out?"); + + Err(io::Error::new(io::ErrorKind::TimedOut, e)) + }) +} + +async fn test_connecct_sites_impl(server_name: &'static str) -> io::Result<()> { + let _ = tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::new("TRACE")) + .pretty() + .try_init(); + + let Some(compatible_cipher_suites) = common::compatible_cipher_suites() else { + return Ok(()); + }; + + let Ok(Ok(socket)) = timeout( + Duration::from_secs(1), + TcpStream::connect(format!("{server_name}:443")), + ) + .await + else { + tracing::warn!("Failed to connect to {server_name}, skipped."); + + return Ok(()); + }; + + let connector = common::client::get_ktls_connector(compatible_cipher_suites); + + let mut ktls_stream = connector + .try_connect(socket, ServerName::try_from(server_name).unwrap()) + .await + .map_err(io::Error::other)?; + + // Test 1 + tracing::info!("First request to {server_name}"); + http_request(&mut ktls_stream, server_name).await?; + + // Test 2 + tracing::info!("Second request to {server_name}"); + http_request(&mut ktls_stream, server_name).await?; + + Ok(()) +} + +async fn http_request( + ktls_stream: &mut KtlsStream, + server_name: &str, +) -> io::Result<()> { + // Write HTTP/1.1 request + { + ktls_stream + .write_all( + format!( + "GET / HTTP/1.1\r\nHost: {}\r\nconnection: keep-alive\r\naccept-encoding: \ + identity\r\ntransfer-encoding: identity\r\n\r\n", + server_name + ) + .as_bytes(), + ) + .await?; + + tracing::debug!("Request sent to {server_name}"); + + // Read response + let mut response = Vec::new(); + + let mut buf_stream = tokio::io::BufStream::new(ktls_stream); + + let mut content_length = None; + + loop { + let total_has_read = response.len(); + + let has_read = buf_stream.read_until(b'\n', &mut response).await?; + + if has_read == 0 || response.ends_with(b"\r\n\r\n") { + break; + } + + let has_read_bytes = &response[total_has_read..]; + tracing::trace!( + "Received from {server_name}: {}", + String::from_utf8_lossy(has_read_bytes) + ); + + const PREFIX: &[u8; 16] = b"content-length: "; + + if has_read_bytes + .get(..PREFIX.len()) + .map(|v| v.eq_ignore_ascii_case(PREFIX)) + == Some(true) + { + let v = std::str::from_utf8(&has_read_bytes[PREFIX.len()..]) + .expect("content length should be a number string") + .trim() + .parse::() + .expect("content length should be a number"); + + content_length = Some(v); + } + } + + // Read body + { + let Some(Some(content_length)) = content_length.map(NonZeroUsize::new) else { + tracing::warn!("No body found in response from {server_name}, skipped."); + + return Ok(()); + }; + + tracing::debug!( + "Headers received from {server_name}, reading body ({content_length} bytes)..." + ); + + response.reserve(content_length.get()); + + #[allow(unsafe_code)] + // Safety: we have reserved enough space above. + buf_stream + .read_exact(unsafe { + std::slice::from_raw_parts_mut( + response.as_mut_ptr().add(response.len()), + content_length.get(), + ) + }) + .await?; + + #[allow(unsafe_code)] + // Safety: we just initialized the buffer above. + unsafe { + response.set_len(response.len() + content_length.get()); + } + } + + let response = String::from_utf8_lossy(&response); + + tracing::info!("Got response from {server_name}"); + + tracing::trace!( + "Response from {server_name}: {:#?} (...)", + &response[..64.min(response.len())] + ); + } + + Ok(()) +} diff --git a/ktls/tests/echo.rs b/ktls/tests/echo.rs new file mode 100644 index 0000000..36a222f --- /dev/null +++ b/ktls/tests/echo.rs @@ -0,0 +1,25 @@ +//! Test: echo server + +use std::io; + +mod common { + include!("../examples/common/mod.rs"); +} + +#[test_case::test_matrix( + [ + common::CloseParty::Client, + common::CloseParty::Server, + ], + [ + common::TestOption::empty(), + common::TestOption::HANDLE_IO_RESULT, + ] +)] +#[tokio::test] +async fn test_echo( + close_party: common::CloseParty, + test_option: common::TestOption, +) -> io::Result<()> { + common::run_echo_test(close_party, test_option).await +} diff --git a/tests/integration_test.rs b/tests/integration_test.rs deleted file mode 100644 index c20c39d..0000000 --- a/tests/integration_test.rs +++ /dev/null @@ -1,554 +0,0 @@ -use std::{ - io, - os::fd::{AsRawFd, RawFd}, - sync::Arc, - task, - time::Duration, -}; - -use ktls::{AsyncReadReady, CorkStream, KtlsCipherSuite, KtlsCipherType, KtlsVersion}; -use lazy_static::lazy_static; -use rcgen::generate_simple_self_signed; -use rustls::{ - client::Resumption, crypto::CryptoProvider, ClientConfig, RootCertStore, ServerConfig, - SupportedCipherSuite, -}; - -#[cfg(feature = "aws_lc_rs")] -use rustls::crypto::aws_lc_rs::cipher_suite; -#[cfg(feature = "ring")] -use rustls::crypto::ring::cipher_suite; - -use tokio::{ - io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, - net::{TcpListener, TcpStream}, -}; -use tokio_rustls::TlsConnector; -use tracing::{debug, Instrument}; -use tracing_subscriber::EnvFilter; - -const RANDOM_SEED: u128 = 19873239487139847918274_u128; - -struct Payloads { - client: Vec, - server: Vec, -} - -impl Default for Payloads { - fn default() -> Self { - let mut prng = oorandom::Rand64::new(RANDOM_SEED); - let payload_len = 262_144; - let mut gen_payload = || { - (0..payload_len) - .map(|_| (prng.rand_u64() % 256) as u8) - .collect() - }; - - Self { - client: gen_payload(), - server: gen_payload(), - } - } -} - -lazy_static! { - static ref PAYLOADS: Payloads = Payloads::default(); -} - -fn all_suites() -> Vec { - vec![ - cipher_suite::TLS13_AES_128_GCM_SHA256, - cipher_suite::TLS13_AES_256_GCM_SHA384, - cipher_suite::TLS13_CHACHA20_POLY1305_SHA256, - #[cfg(feature = "tls12")] - cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - #[cfg(feature = "tls12")] - cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - #[cfg(feature = "tls12")] - cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - ] -} - -#[tokio::test] -async fn compatible_ciphers() { - let cc = ktls::CompatibleCiphers::new().await.unwrap(); - for suite in all_suites() { - assert!(cc.is_compatible(suite)); - } -} - -#[tokio::test(flavor = "current_thread")] -async fn compatible_ciphers_single_thread() { - let cc = ktls::CompatibleCiphers::new().await.unwrap(); - for suite in all_suites() { - assert!(cc.is_compatible(suite)); - } -} - -#[derive(Clone, Copy)] -enum ServerTestFlavor { - ClientCloses, - ServerCloses, -} - -#[test_case::test_matrix( - [ - KtlsVersion::TLS12, - KtlsVersion::TLS13, - ], - [ - KtlsCipherType::AesGcm128, - KtlsCipherType::AesGcm256, - KtlsCipherType::Chacha20Poly1305, - ], - [ - ServerTestFlavor::ClientCloses, - ServerTestFlavor::ServerCloses, - ] -)] -#[tokio::test] -async fn server_tests(version: KtlsVersion, cipher_type: KtlsCipherType, flavor: ServerTestFlavor) { - if matches!(version, KtlsVersion::TLS12) && !cfg!(feature = "tls12") { - println!("Skipping..."); - return; - } - - let cipher_suite = KtlsCipherSuite { - version, - typ: cipher_type, - }; - - server_test_inner(cipher_suite, flavor).await -} - -async fn server_test_inner(cipher_suite: KtlsCipherSuite, flavor: ServerTestFlavor) { - tracing_subscriber::fmt() - // .with_env_filter(EnvFilter::new("rustls=trace,debug")) - // .with_env_filter(EnvFilter::new("debug")) - .with_env_filter(EnvFilter::new("trace")) - .pretty() - .init(); - - let subject_alt_names = vec!["localhost".to_string()]; - - let ckey = generate_simple_self_signed(subject_alt_names).unwrap(); - - let mut server_config = - ServerConfig::builder_with_provider(single_suite_provider(cipher_suite)) - .with_protocol_versions(&[cipher_suite.version.as_supported_version()]) - .unwrap() - .with_no_client_auth() - .with_single_cert( - vec![ckey.cert.der().clone()], - rustls::pki_types::PrivatePkcs8KeyDer::from(ckey.key_pair.serialize_der()).into(), - ) - .unwrap(); - - server_config.enable_secret_extraction = true; - server_config.key_log = Arc::new(rustls::KeyLogFile::new()); - - let acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(server_config)); - let ln = TcpListener::bind("[::]:0").await.unwrap(); - let addr = ln.local_addr().unwrap(); - - let jh = tokio::spawn( - async move { - let (stream, addr) = ln.accept().await.unwrap(); - debug!("Accepted TCP conn from {}", addr); - let stream = SpyStream(stream, "server"); - let stream = CorkStream::new(stream); - - let stream = acceptor.accept(stream).await.unwrap(); - debug!("Completed TLS handshake"); - - // sleep for a bit to let client write more data and stress test - // the draining logic - tokio::time::sleep(Duration::from_millis(100)).await; - - let mut stream = ktls::config_ktls_server(stream).await.unwrap(); - debug!("Configured kTLS"); - - debug!("Server reading data (1/5)"); - let mut buf = vec![0u8; PAYLOADS.client.len()]; - stream.read_exact(&mut buf).await.unwrap(); - assert_eq!(buf, PAYLOADS.client); - - debug!("Server writing data (2/5)"); - stream.write_all(&PAYLOADS.server).await.unwrap(); - stream.flush().await.unwrap(); - - debug!("Server reading data (3/5)"); - let mut buf = vec![0u8; PAYLOADS.client.len()]; - stream.read_exact(&mut buf).await.unwrap(); - assert_eq!(buf, PAYLOADS.client); - - debug!("Server writing data (4/5)"); - stream.write_all(&PAYLOADS.server).await.unwrap(); - stream.flush().await.unwrap(); - - match flavor { - ServerTestFlavor::ClientCloses => { - debug!("Server reading from closed session (5/5)"); - assert!( - stream.read_exact(&mut buf[..1]).await.is_err(), - "Session still open?" - ); - } - ServerTestFlavor::ServerCloses => { - debug!("Server sending close notify (5/5)"); - stream.shutdown().await.unwrap(); - - debug!("Server trying to write after closing"); - stream.write_all(&PAYLOADS.server).await.unwrap_err(); - } - } - - assert_eq!(stream.get_ref().1, "server"); - assert_eq!(stream.get_mut().1, "server"); - assert_eq!(stream.into_raw().1 .1, "server"); - } - .instrument(tracing::info_span!("server")), - ); - - let mut root_store = RootCertStore::empty(); - root_store.add(ckey.cert.der().clone()).unwrap(); - - let client_config = ClientConfig::builder() - .with_root_certificates(root_store) - .with_no_client_auth(); - - let tls_connector = TlsConnector::from(Arc::new(client_config)); - - let stream = TcpStream::connect(addr).await.unwrap(); - let mut stream = tls_connector - .connect("localhost".try_into().unwrap(), stream) - .await - .unwrap(); - - debug!("Client writing data (1/5)"); - stream.write_all(&PAYLOADS.client).await.unwrap(); - debug!("Flushing"); - stream.flush().await.unwrap(); - - debug!("Client reading data (2/5)"); - let mut buf = vec![0u8; PAYLOADS.server.len()]; - stream.read_exact(&mut buf).await.unwrap(); - assert_eq!(buf, PAYLOADS.server); - - debug!("Client writing data (3/5)"); - stream.write_all(&PAYLOADS.client).await.unwrap(); - debug!("Flushing"); - stream.flush().await.unwrap(); - - debug!("Client reading data (4/5)"); - let mut buf = vec![0u8; PAYLOADS.server.len()]; - stream.read_exact(&mut buf).await.unwrap(); - assert_eq!(buf, PAYLOADS.server); - - match flavor { - ServerTestFlavor::ClientCloses => { - debug!("Client sending close notify (5/5)"); - stream.shutdown().await.unwrap(); - - debug!("Client trying to write after closing"); - stream.write_all(&PAYLOADS.client).await.unwrap_err(); - } - ServerTestFlavor::ServerCloses => { - debug!("Client reading from closed session (5/5)"); - assert!( - stream.read_exact(&mut buf[..1]).await.is_err(), - "Session still open?" - ); - } - } - - jh.await.unwrap(); -} - -#[test_case::test_matrix( - [ - KtlsVersion::TLS12, - KtlsVersion::TLS13, - ], - [ - KtlsCipherType::AesGcm128, - KtlsCipherType::AesGcm256, - KtlsCipherType::Chacha20Poly1305, - ], - [ - ClientTestFlavor::ShortLastBuffer, - ClientTestFlavor::LongLastBuffer, - ] -)] -#[tokio::test] -async fn client_tests(version: KtlsVersion, cipher_type: KtlsCipherType, flavor: ClientTestFlavor) { - if matches!(version, KtlsVersion::TLS12) && !cfg!(feature = "tls12") { - println!("Skipping..."); - return; - } - - let cipher_suite = KtlsCipherSuite { - version, - typ: cipher_type, - }; - - client_test_inner(cipher_suite, flavor).await -} - -enum ClientTestFlavor { - ShortLastBuffer, - LongLastBuffer, -} - -async fn client_test_inner(cipher_suite: KtlsCipherSuite, flavor: ClientTestFlavor) { - tracing_subscriber::fmt() - // .with_env_filter(EnvFilter::new("rustls=trace,debug")) - // .with_env_filter(EnvFilter::new("debug")) - .with_env_filter(EnvFilter::new("trace")) - .pretty() - .init(); - - let subject_alt_names = vec!["localhost".to_string()]; - - let ckey = generate_simple_self_signed(subject_alt_names).unwrap(); - - let mut server_config = - ServerConfig::builder_with_provider(single_suite_provider(cipher_suite)) - .with_protocol_versions(&[cipher_suite.version.as_supported_version()]) - .unwrap() - .with_no_client_auth() - .with_single_cert( - vec![ckey.cert.der().clone()], - rustls::pki_types::PrivatePkcs8KeyDer::from(ckey.key_pair.serialize_der()).into(), - ) - .unwrap(); - - server_config.key_log = Arc::new(rustls::KeyLogFile::new()); - // server_config.send_tls13_tickets = 0; - - let acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(server_config)); - let ln = TcpListener::bind("[::]:0").await.unwrap(); - let addr = ln.local_addr().unwrap(); - - let jh = tokio::spawn( - async move { - let (stream, addr) = ln.accept().await.unwrap(); - - debug!("Accepted TCP conn from {}", addr); - let mut stream = acceptor.accept(stream).await.unwrap(); - debug!("Completed TLS handshake"); - - debug!("Server reading data (1/5)"); - let mut buf = vec![0u8; PAYLOADS.client.len()]; - stream.read_exact(&mut buf).await.unwrap(); - assert_eq!(buf, PAYLOADS.client); - - debug!("Server writing data (2/5)"); - stream.write_all(&PAYLOADS.server).await.unwrap(); - - debug!("Server reading data (3/5)"); - let mut buf = vec![0u8; PAYLOADS.client.len()]; - stream.read_exact(&mut buf).await.unwrap(); - assert_eq!(buf, PAYLOADS.client); - - for _i in 0..3 { - debug!("Making the client wait (to make busywaits REALLY obvious)"); - tokio::time::sleep(Duration::from_millis(250)).await; - } - - debug!("Server writing data (4/5)"); - stream.write_all(&PAYLOADS.server).await.unwrap(); - - debug!("Server sending close notify (5/5)"); - stream.shutdown().await.unwrap(); - - debug!("Server trying to write after close notify"); - stream.write_all(&PAYLOADS.server).await.unwrap_err(); - - debug!("Server is happy with the exchange"); - } - .instrument(tracing::info_span!("server")), - ); - - let mut root_store = RootCertStore::empty(); - root_store.add(ckey.cert.der().clone()).unwrap(); - - let mut client_config = ClientConfig::builder() - .with_root_certificates(root_store) - .with_no_client_auth(); - - client_config.enable_secret_extraction = true; - client_config.resumption = Resumption::disabled(); - - let tls_connector = TlsConnector::from(Arc::new(client_config)); - - let stream = TcpStream::connect(addr).await.unwrap(); - let stream = CorkStream::new(stream); - - let stream = tls_connector - .connect("localhost".try_into().unwrap(), stream) - .await - .unwrap(); - - let stream = ktls::config_ktls_client(stream).await.unwrap(); - let mut stream = SpyStream(stream, "client"); - - debug!("Client writing data (1/5)"); - stream.write_all(&PAYLOADS.client).await.unwrap(); - debug!("Flushing"); - stream.flush().await.unwrap(); - - tokio::time::sleep(Duration::from_millis(250)).await; - - debug!("Client reading data (2/5)"); - let mut buf = vec![0u8; PAYLOADS.server.len()]; - stream.read_exact(&mut buf).await.unwrap(); - assert_eq!(buf, PAYLOADS.server); - - debug!("Client writing data (3/5)"); - stream.write_all(&PAYLOADS.client).await.unwrap(); - debug!("Flushing"); - stream.flush().await.unwrap(); - - debug!("Client reading data (4/5)"); - let mut buf = vec![0u8; PAYLOADS.server.len()]; - stream.read_exact(&mut buf).await.unwrap(); - assert_eq!(buf, PAYLOADS.server); - - let buf = match flavor { - ClientTestFlavor::ShortLastBuffer => &mut buf[..1], - ClientTestFlavor::LongLastBuffer => &mut buf[..2], - }; - debug!( - "Client reading from closed session (with buffer of size {})", - buf.len() - ); - assert!(stream.read_exact(buf).await.is_err(), "Session still open?"); - - jh.await.unwrap(); -} - -struct SpyStream(IO, &'static str); - -impl AsyncRead for SpyStream -where - IO: AsyncRead, -{ - fn poll_read( - self: std::pin::Pin<&mut Self>, - cx: &mut task::Context<'_>, - buf: &mut tokio::io::ReadBuf<'_>, - ) -> task::Poll> { - let old_filled = buf.filled().len(); - let name = self.1; - let res = unsafe { - let io = self.map_unchecked_mut(|s| &mut s.0); - io.poll_read(cx, buf) - }; - - match &res { - task::Poll::Ready(res) => match res { - Ok(_) => { - let num_read = buf.filled().len() - old_filled; - tracing::debug!(%name, "SpyStream read {num_read} bytes",); - } - Err(e) => { - tracing::debug!(%name, "SpyStream read errored: {e}"); - } - }, - task::Poll::Pending => { - tracing::debug!(%name, "SpyStream read would've blocked") - } - } - res - } -} - -impl AsyncReadReady for SpyStream -where - IO: AsyncReadReady, -{ - fn poll_read_ready(&self, cx: &mut task::Context<'_>) -> task::Poll> { - self.0.poll_read_ready(cx) - } -} - -impl AsyncWrite for SpyStream -where - IO: AsyncWrite, -{ - fn poll_write( - self: std::pin::Pin<&mut Self>, - cx: &mut task::Context<'_>, - buf: &[u8], - ) -> task::Poll> { - let res = unsafe { - let io = self.map_unchecked_mut(|s| &mut s.0); - io.poll_write(cx, buf) - }; - - match &res { - task::Poll::Ready(res) => match res { - Ok(n) => { - tracing::debug!("SpyStream wrote {n} bytes"); - } - Err(e) => { - tracing::debug!("SpyStream writing errored: {e}"); - } - }, - task::Poll::Pending => { - tracing::debug!("SpyStream writing would've blocked") - } - } - res - } - - fn poll_flush( - self: std::pin::Pin<&mut Self>, - cx: &mut task::Context<'_>, - ) -> task::Poll> { - unsafe { - let io = self.map_unchecked_mut(|s| &mut s.0); - io.poll_flush(cx) - } - } - - fn poll_shutdown( - self: std::pin::Pin<&mut Self>, - cx: &mut task::Context<'_>, - ) -> task::Poll> { - unsafe { - let io = self.map_unchecked_mut(|s| &mut s.0); - io.poll_shutdown(cx) - } - } -} - -impl AsRawFd for SpyStream -where - IO: AsRawFd, -{ - fn as_raw_fd(&self) -> RawFd { - self.0.as_raw_fd() - } -} - -fn single_suite_provider(cipher_suite: KtlsCipherSuite) -> Arc { - let mut provider = { - #[cfg(feature = "aws_lc_rs")] - { - rustls::crypto::aws_lc_rs::default_provider() - } - - #[cfg(feature = "ring")] - { - rustls::crypto::ring::default_provider() - } - }; - provider.cipher_suites.clear(); - provider - .cipher_suites - .push(cipher_suite.as_supported_cipher_suite()); - - Arc::new(provider) -} From 4dd5fe7b0d893301605b599f2c728e1b4fdb038b Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 10:56:39 +0800 Subject: [PATCH 19/33] PR 62: partial: adjust Justfile --- Justfile | 54 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/Justfile b/Justfile index 1c1059f..e87eb07 100644 --- a/Justfile +++ b/Justfile @@ -4,35 +4,45 @@ _default: just --list # Run all tests with nextest and cargo-llvm-cov -ci-test: +ci-test *args: #!/bin/bash -eux - for backend in ring aws_lc_rs; do - cargo llvm-cov nextest --no-default-features --features tls12,$backend --lcov --output-path coverage.lcov - done + cargo llvm-cov nextest {{args}} --locked --all-features --lcov --output-path coverage.lcov -# Show coverage locally -cov: - #!/bin/bash -eux - cargo llvm-cov nextest --hide-instantiations --html --output-dir coverage +# =========== LOCAL COMMANDS =========== -t *args: - just test {{args}} +build *args: + cargo build {{args}} --locked -# Run all tests -test *args: +b *args: + just build {{args}} + +# Show coverage locally +cov *args: #!/bin/bash -eux - export RUST_BACKTRACE=1 - for feature in ring aws_lc_rs; do - cargo nextest run --no-default-features --features tls12,$feature {{args}} - done + cargo llvm-cov nextest {{args}} --locked --all-features --hide-instantiations --html --output-dir coverage + +check *args: + cargo check {{args}} --locked --all-features c *args: - just check {{args}} + just check {{args}} clippy *args: - cargo clippy {{args}} --no-default-features --features tls12,ring - cargo clippy {{args}} --no-default-features --features tls12,aws_lc_rs + cargo clippy {{args}} --locked --all-features -- -Dclippy::all -Dclippy::pedantic -check *args: - cargo check {{args}} --no-default-features --features tls12,ring - cargo check {{args}} --no-default-features --features tls12,aws_lc_rs +example *args: + cargo run --example {{args}} + +e *args: + just example {{args}} + +msrv *args: + cargo +1.77.0 clippy {{args}} --locked --all-features -- -Dclippy::all -Dclippy::pedantic + +t *args: + just test {{args}} + +test *args: + #!/bin/bash -eux + export RUST_BACKTRACE=1 + cargo nextest run {{args}} --locked --all-features From b3dfee6638719d958ad235de4d7d3203b36903ea Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 10:56:47 +0800 Subject: [PATCH 20/33] PR 62: partial: adjust CI tests --- .github/workflows/ci.yml | 111 +++++++++++++++++++++++++++++++++++++ .github/workflows/test.yml | 58 ------------------- 2 files changed, 111 insertions(+), 58 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..939ea55 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,111 @@ +name: CI + +on: + push: + branches: ["main"] + pull_request: + merge_group: + +permissions: + contents: read + +env: + RUSTFLAGS: -Dwarnings + RUST_BACKTRACE: 1 + +jobs: + test: + name: Test (Rust ${{matrix.toolchain}}, target ${{matrix.target}}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + toolchain: ["nightly", "beta", "stable"] + target: ["x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl"] + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{matrix.toolchain}} + components: llvm-tools, clippy, rust-src + - uses: taiki-e/install-action@v2 + with: + tool: just,cargo-llvm-cov,cargo-nextest + - name: Enable type layout randomization + if: matrix.toolchain == 'nightly' + run: echo RUSTFLAGS=${RUSTFLAGS}\ -Zrandomize-layout >> $GITHUB_ENV + - run: sudo apt-get update && sudo apt-get install -y musl-tools + if: endsWith(matrix.target, 'musl') + - run: rustup target add ${{matrix.target}} + - run: just example client --target ${{matrix.target}} + - run: just build --tests --release --target ${{matrix.target}} + - run: just ci-test --target ${{matrix.target}} + + msrv: + name: MSRV + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.77.0 + - uses: taiki-e/install-action@v2 + with: + tool: just + - run: just build --package ktls + - run: just build --package ktls --features raw-api + - run: just build --package ktls-sys + - run: just build --package ktls-util + + doc: + name: Documentation + runs-on: ubuntu-latest + timeout-minutes: 45 + env: + RUSTDOCFLAGS: -Dwarnings + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - uses: dtolnay/install@cargo-docs-rs + - run: cargo docs-rs --package ktls + + clippy: + name: Clippy + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.77.0 + components: clippy + - uses: taiki-e/install-action@v2 + with: + tool: just + - run: just clippy + + coverage: + name: Test Coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.77.0 + components: llvm-tools, clippy, rust-src + - uses: taiki-e/install-action@v2 + with: + tool: just,cargo-llvm-cov,cargo-nextest + - run: just example client + - run: just build --tests --release + - run: just ci-test + - name: Upload coverage information + run: | + curl -Os https://uploader.codecov.io/latest/linux/codecov + chmod +x codecov + ./codecov diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 751f67b..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: test - -on: - push: - branches: ["main"] - pull_request: - merge_group: - -jobs: - test: - name: test - runs-on: ubuntu-latest - env: - RUSTC_WRAPPER: sccache - SCCACHE_GHA_ENABLED: true - CARGO_INCREMENTAL: 0 - steps: - - name: Check out repository code - uses: actions/checkout@v4 - with: - persist-credentials: false - - uses: dtolnay/rust-toolchain@stable - with: - components: llvm-tools, clippy, rust-src - - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.9 - - uses: taiki-e/install-action@v2 - with: - tool: just,cargo-llvm-cov,cargo-nextest - - name: Run tests - run: | - cd ${{ github.workspace }} - just clippy --all-targets - RUST_BACKTRACE=1 just ci-test - - name: Upload coverage information - run: | - curl -Os https://uploader.codecov.io/latest/linux/codecov - chmod +x codecov - ./codecov - - msrv: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - uses: dtolnay/rust-toolchain@master - with: - toolchain: 1.75.0 - components: llvm-tools, clippy, rust-src - - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.9 - - uses: taiki-e/install-action@v2 - with: - tool: just - - name: Check library code with minimum supported Rust version - run: | - just check --lib --locked From f21ef5f881ab4b00b3e7240631184b2b7b9f9a40 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 10:58:58 +0800 Subject: [PATCH 21/33] PR 62: partial: add CI: kernel compatibility test --- .../workflows/kernel-compatibility-test.yml | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 .github/workflows/kernel-compatibility-test.yml diff --git a/.github/workflows/kernel-compatibility-test.yml b/.github/workflows/kernel-compatibility-test.yml new file mode 100644 index 0000000..913767f --- /dev/null +++ b/.github/workflows/kernel-compatibility-test.yml @@ -0,0 +1,180 @@ +# Credits: https://github.com/tokio-rs/io-uring/blob/master/.github/workflows/kernel-version-test.yml +# +# Tests kTLS functionality across multiple kernel versions. +# Default matrix: 6.12, 6.6, 6.1, 5.15, 5.10, 5.4 +# Manual trigger supports custom space-separated version list. + +name: Kernel Compatibility Test + +on: + push: + branches: ["main"] + pull_request: + merge_group: + workflow_dispatch: + inputs: + kernel_versions: + description: "Space-separated list of Linux kernel versions to test (e.g., '6.12 6.6 6.1.148 5.15.189 5.10.240 5.4.296')" + required: true + +jobs: + prepare-matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Set matrix + id: set-matrix + run: | + if [ -n "${{ github.event.inputs.kernel_versions }}" ]; then + # Manual trigger with custom versions + versions="${{ github.event.inputs.kernel_versions }}" + echo "Using manual input versions: $versions" + else + # Default versions for push events + versions="6.12 6.6 6.1.148 5.15.189 5.10.240 5.4.296" + echo "Using default versions: $versions" + fi + + # Convert space-separated list to JSON array + json_array=$(echo "$versions" | tr ' ' '\n' | jq -R . | jq -s -c .) + echo "matrix={\"kernel_version\":$json_array}" >> $GITHUB_OUTPUT + echo "Generated matrix: {\"kernel_version\":$json_array}" + + build: + needs: prepare-matrix + runs-on: ubuntu-latest + strategy: + matrix: ${{fromJson(needs.prepare-matrix.outputs.matrix)}} + fail-fast: false + env: + KERNEL_VERSION: ${{ matrix.kernel_version }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + bison flex libelf-dev \ + qemu-system-x86 busybox-static cpio xz-utils wget e2fsprogs \ + musl-tools + + - name: Install Rust 1.77.0 + uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.77.0 + targets: x86_64-unknown-linux-musl + + - name: Generate the test binary + run: | + cargo build --package ktls --example client --features tracing --release --target x86_64-unknown-linux-musl + + - name: Cache Linux source + id: cache-kernel + uses: actions/cache@v4 + with: + path: linux-${{ env.KERNEL_VERSION }} + key: kernel-${{ env.KERNEL_VERSION }} + + - name: Download & build Linux kernel + if: steps.cache-kernel.outputs.cache-hit != 'true' + run: | + MAJOR=${KERNEL_VERSION%%.*} + wget https://cdn.kernel.org/pub/linux/kernel/v${MAJOR}.x/linux-${KERNEL_VERSION}.tar.xz + tar xf linux-${KERNEL_VERSION}.tar.xz + cd linux-${KERNEL_VERSION} + + # Generate the default config + make defconfig + + # Enable essentials as built-ins + scripts/config --enable CONFIG_DEVTMPFS + scripts/config --enable CONFIG_DEVTMPFS_MOUNT + + # Enable virtio drivers + scripts/config --enable CONFIG_VIRTIO + scripts/config --enable CONFIG_VIRTIO_PCI + scripts/config --enable CONFIG_VIRTIO_BLK + + # Enable kTLS support + scripts/config --enable CONFIG_TLS + scripts/config --enable CONFIG_TLS_DEVICE + + # Generate the updated config + make olddefconfig + + make -j$(nproc) + + - name: Prepare initramfs + tests binaries + run: | + rm -rf initramfs && mkdir -p initramfs/{bin,sbin,proc,sys,tmp} + + # Copy the test binary + cp target/x86_64-unknown-linux-musl/release/examples/client initramfs/bin/ktls-test + + # Add necessary binaries from busybox + cp /usr/bin/busybox initramfs/bin/ + for cmd in sh mount ip ifconfig cat; do ln -sf busybox initramfs/bin/$cmd; done + ln -sf ../bin/busybox initramfs/sbin/poweroff + + # Generate init script + cat > initramfs/init << 'EOF' + #!/bin/sh + set -e + + # Activating the loopback interface (it's required for some network tests) + ip link set lo up + + mkdir -p /dev + + # Enable necessary devices + # https://www.kernel.org/doc/Documentation/admin-guide/devices.txt + mknod /dev/port c 1 4 + mknod /dev/null c 1 3 + mknod /dev/zero c 1 5 + mknod /dev/tty c 5 0 + + mkdir -p /tmp && mount -t tmpfs -o mode=1777 tmpfs /tmp + + # Bring up ext4 test volume at /mnt + mount -t devtmpfs devtmpfs /dev + + exit_code=0 + + # Run the test binary + RUST_BACKTRACE=1 /bin/ktls-test || exit_code=1 + + # If the test binary exited with a non-zero code, write it to /dev/port. + # This lets QEMU exit with non-zero exit-code, triggering a CI error. + [ $exit_code -eq 0 ] || printf '\x01' \ + | dd of=/dev/port bs=1 seek=244 count=1 2>/dev/null + + /sbin/poweroff -f + + EOF + + chmod +x initramfs/init + + # Pack into a CPIO archive + (cd initramfs && find . -print0 \ + | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz) + + - name: Run tests in QEMU + run: | + qemu-system-x86_64 \ + -device isa-debug-exit,iobase=0xf4,iosize=0x04 \ + -kernel linux-${{ env.KERNEL_VERSION }}/arch/x86/boot/bzImage \ + -initrd initramfs.cpio.gz \ + -netdev user,id=net0 \ + -device e1000,netdev=net0 \ + -append "console=ttyS0 rootfstype=ramfs panic=1" \ + -nographic -no-reboot -m 1024 -action panic=exit-failure + + if [ $? -ne 0 ]; then + echo "tests failed (QEMU exited abnormally)" + exit 1 + else + echo "all tests passed" + fi From 2f43fd6fc1eafbd4428079113b6ef630d56f65cb Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 11:06:13 +0800 Subject: [PATCH 22/33] PR 62: partial: fix MSRV and clippy refactor: use std instead of memoffset --- Cargo.lock | 5 ++--- Cargo.toml | 2 +- ktls-sys/src/lib.rs | 7 +++++++ ktls/Cargo.toml | 1 - ktls/src/ffi.rs | 2 +- ktls/src/stream.rs | 6 ++++-- 6 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e88c15f..9c245bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -191,7 +191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -282,7 +282,6 @@ dependencies = [ "ktls-util", "libc", "log", - "memoffset", "nix", "pin-project", "rand", @@ -708,7 +707,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0b3c37e..1383727 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ resolver = "2" [workspace.package] edition = "2021" -rust-version = "1.75.0" +rust-version = "1.77.0" # === Publication info === authors = ["Amos Wenger "] categories = ["network-programming"] diff --git a/ktls-sys/src/lib.rs b/ktls-sys/src/lib.rs index d8c16e3..bf28466 100644 --- a/ktls-sys/src/lib.rs +++ b/ktls-sys/src/lib.rs @@ -1,3 +1,10 @@ +//! FFI bindings to the kernel TLS (kTLS) API. + +#![allow(clippy::items_after_statements)] +#![allow(clippy::missing_safety_doc)] +#![allow(clippy::must_use_candidate)] +#![allow(clippy::ptr_as_ptr)] +#![allow(clippy::too_many_lines)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] diff --git a/ktls/Cargo.toml b/ktls/Cargo.toml index eccf070..6979520 100644 --- a/ktls/Cargo.toml +++ b/ktls/Cargo.toml @@ -18,7 +18,6 @@ repository.workspace = true bitflags = "2.9" libc = { version = "0.2.175", features = ["const-extern-fn"] } log = { version = "0.4.27", optional = true } -memoffset = "0.9.1" nix = { version = "0.30.1", features = ["socket", "uio", "net"] } pin-project = "1.1" rustls = { version = "0.23.31", default-features = false, features = ["std"] } diff --git a/ktls/src/ffi.rs b/ktls/src/ffi.rs index 762f4ca..3ba8444 100644 --- a/ktls/src/ffi.rs +++ b/ktls/src/ffi.rs @@ -24,7 +24,7 @@ impl Cmsg { hdr.cmsg_level = level; hdr.cmsg_type = typ; // For MUSL target, this is u32. - hdr.cmsg_len = (memoffset::offset_of!(Self, data) + N) as _; + hdr.cmsg_len = (mem::offset_of!(Self, data) + N) as _; Self { _hdr: hdr, data } } diff --git a/ktls/src/stream.rs b/ktls/src/stream.rs index 4bbdb86..9ef488c 100644 --- a/ktls/src/stream.rs +++ b/ktls/src/stream.rs @@ -1,5 +1,7 @@ //! See [`KtlsStream`]. +#![allow(clippy::module_name_repetitions)] + pub mod context; pub mod error; pub mod impl_std; @@ -240,8 +242,8 @@ where /// the inner socket directly. /// /// - If the result is `Ok`, it returns `Some(T)`. - /// - If the errno is [`EIO`](libc::EIO), it tries to handle any TLS control messages - /// received, and returns `None` if succeeded. + /// - If the errno is [`EIO`](libc::EIO), it tries to handle any TLS control + /// messages received, and returns `None` if succeeded. /// - Otherwise, it aborts the connection with `internal_error` alert and /// returns the error. /// From 87ad31439febc0fed77c3214f4046d4a4aeae552 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 11:09:05 +0800 Subject: [PATCH 23/33] PR 62: partial: cargo docs --- ktls/Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ktls/Cargo.toml b/ktls/Cargo.toml index 6979520..510dee4 100644 --- a/ktls/Cargo.toml +++ b/ktls/Cargo.toml @@ -14,6 +14,10 @@ license.workspace = true readme.workspace = true repository.workspace = true +[package.metadata.docs.rs] +features = ["tls12", "async-io-tokio", "raw-api"] +targets = ["x86_64-unknown-linux-gnu"] + [dependencies] bitflags = "2.9" libc = { version = "0.2.175", features = ["const-extern-fn"] } From 5ea6fa1b6e110362724bb1ea39628973e3acc69a Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 11:10:14 +0800 Subject: [PATCH 24/33] PR 62: partial: cleanup --- ktls-sys/Cargo.lock | 7 ------- ktls-sys/Justfile | 16 ---------------- 2 files changed, 23 deletions(-) delete mode 100644 ktls-sys/Cargo.lock delete mode 100644 ktls-sys/Justfile diff --git a/ktls-sys/Cargo.lock b/ktls-sys/Cargo.lock deleted file mode 100644 index 5abe3bb..0000000 --- a/ktls-sys/Cargo.lock +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ktls-sys" -version = "1.0.1" diff --git a/ktls-sys/Justfile b/ktls-sys/Justfile deleted file mode 100644 index 034b5f0..0000000 --- a/ktls-sys/Justfile +++ /dev/null @@ -1,16 +0,0 @@ -# just manual: https://github.com/casey/just#readme - -_default: - just --list - -# Run all tests with nextest and cargo-llvm-cov -ci-test: - #!/bin/bash -eux - cargo llvm-cov nextest --lcov --output-path coverage.lcov - -# Run all tests with cargo nextest -test *args: - RUST_BACKTRACE=1 cargo nextest run {{args}} - -check: - cargo clippy --all-targets From 9f1fe01c7b5afa59135da084d6b9d443e62a6d4a Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 11:15:23 +0800 Subject: [PATCH 25/33] PR 62: partial: adjust README --- README.md | 11 ++++++----- ktls-sys/README.md | 12 ++++++++---- ktls-util/README.md | 21 +++++++++++++++++++++ ktls/README.md | 23 +++++++++++++++-------- 4 files changed, 50 insertions(+), 17 deletions(-) create mode 100644 ktls-util/README.md diff --git a/README.md b/README.md index f56866d..27ff580 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@ -[![test pipeline](https://github.com/hapsoc/ktls/actions/workflows/test.yml/badge.svg)](https://github.com/hapsoc/ktls/actions/workflows/test.yml?query=branch%3Amain) -[![Coverage Status (codecov.io)](https://codecov.io/gh/hapsoc/ktls/branch/main/graph/badge.svg)](https://codecov.io/gh/hapsoc/ktls/) -[![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](LICENSE-MIT) +[![Test pipeline](https://github.com/rustls/ktls/actions/workflows/ci.yml/badge.svg)](https://github.com/rustls/ktls/actions/workflows/ci.yml?query=branch%3Amain) +[![Coverage Status (codecov.io)](https://codecov.io/gh/rustls/ktls/branch/main/graph/badge.svg)](https://codecov.io/gh/rustls/ktls/) +[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](LICENSE-MIT) # ktls This repository hosts both: - * [ktls](./ktls): higher-level, safe wrappers over kTLS - * [ktls-sys](./ktls-sys): the raw system interface for kTLS on Linux + * [ktls](./ktls): high-level APIs for configuring kTLS (kernel TLS offload) on top of [rustls]. + * [ktls-util](./ktls-util): utilities for crate `ktls`. + * [ktls-sys](./ktls-sys): the raw system interface for kTLS on Linux (deprecated). ## License diff --git a/ktls-sys/README.md b/ktls-sys/README.md index 2cee2cd..7aa372e 100644 --- a/ktls-sys/README.md +++ b/ktls-sys/README.md @@ -1,15 +1,19 @@ -[![test pipeline](https://github.com/hapsoc/ktls/actions/workflows/test.yml/badge.svg)](https://github.com/hapsoc/ktls/actions/workflows/test.yml?query=branch%3Amain) -[![Coverage Status (codecov.io)](https://codecov.io/gh/hapsoc/ktls/branch/main/graph/badge.svg)](https://codecov.io/gh/hapsoc/ktls/) [![Crates.io](https://img.shields.io/crates/v/ktls-sys)](https://crates.io/crates/ktls-sys) -[![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](LICENSE-MIT) +[![Docs.rs](https://docs.rs/ktls-sys/badge.svg)](https://docs.rs/ktls-sys) +[![Test pipeline](https://github.com/rustls/ktls/actions/workflows/ci.yml/badge.svg)](https://github.com/rustls/ktls/actions/workflows/ci.yml?query=branch%3Amain) +[![Coverage Status (codecov.io)](https://codecov.io/gh/rustls/ktls/branch/main/graph/badge.svg)](https://codecov.io/gh/rustls/ktls/) +[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](LICENSE-MIT) # ktls-sys +> [!WARNING] +> This crate is deprecated. + `linux/tls.h` bindings, for TLS kernel offload. Generated with `bindgen tls.h -o src/bindings.rs` -See for a higher-level / safer interface. +See for a higher-level / safer interface. ## License diff --git a/ktls-util/README.md b/ktls-util/README.md new file mode 100644 index 0000000..13a6d72 --- /dev/null +++ b/ktls-util/README.md @@ -0,0 +1,21 @@ +[![Crates.io](https://img.shields.io/crates/v/ktls)](https://crates.io/crates/ktls-util) +[![Docs.rs](https://docs.rs/ktls/badge.svg)](https://docs.rs/ktls-util) +[![Test pipeline](https://github.com/rustls/ktls/actions/workflows/ci.yml/badge.svg)](https://github.com/rustls/ktls/actions/workflows/ci.yml?query=branch%3Amain) +[![Coverage Status (codecov.io)](https://codecov.io/gh/rustls/ktls/branch/main/graph/badge.svg)](https://codecov.io/gh/rustls/ktls/) +[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](LICENSE-MIT) + +# `ktls-util` - Utilities for crate `ktls` + +## MSRV + +1.77.0 + +## LICENSE + +This project is primarily distributed under the terms of both the MIT license +and the Apache License (Version 2.0). + +See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. + +[kernel TLS offload]: https://www.kernel.org/doc/html/latest/networking/tls-offload.html +[rustls]: https://docs.rs/rustls/latest/rustls/kernel/index.html diff --git a/ktls/README.md b/ktls/README.md index 59c8897..dfb23ea 100644 --- a/ktls/README.md +++ b/ktls/README.md @@ -1,17 +1,24 @@ -[![test pipeline](https://github.com/hapsoc/ktls/actions/workflows/test.yml/badge.svg)](https://github.com/hapsoc/ktls/actions/workflows/test.yml?query=branch%3Amain) -[![Coverage Status (codecov.io)](https://codecov.io/gh/hapsoc/ktls/branch/main/graph/badge.svg)](https://codecov.io/gh/hapsoc/ktls/) [![Crates.io](https://img.shields.io/crates/v/ktls)](https://crates.io/crates/ktls) -[![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](LICENSE-MIT) +[![Docs.rs](https://docs.rs/ktls/badge.svg)](https://docs.rs/ktls) +[![Test pipeline](https://github.com/rustls/ktls/actions/workflows/ci.yml/badge.svg)](https://github.com/rustls/ktls/actions/workflows/ci.yml?query=branch%3Amain) +[![Coverage Status (codecov.io)](https://codecov.io/gh/rustls/ktls/branch/main/graph/badge.svg)](https://codecov.io/gh/rustls/ktls/) +[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](LICENSE-MIT) -# ktls +# `ktls` - Kernel TLS offload (kTLS) support built on top of [rustls]. -Configures kTLS ([kernel TLS -offload](https://www.kernel.org/doc/html/latest/networking/tls-offload.html)) -for any type that implements `AsRawFd`, given a rustls `ServerConnection`. +This crate provides high-level APIs for configuring [kernel TLS offload] (kTLS), +extending the bare minimum functionality provided by [rustls]. -## License +## MSRV + +1.77.0 + +## LICENSE This project is primarily distributed under the terms of both the MIT license and the Apache License (Version 2.0). See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. + +[kernel TLS offload]: https://www.kernel.org/doc/html/latest/networking/tls-offload.html +[rustls]: https://docs.rs/rustls/latest/rustls/kernel/index.html From 449bf63cfe0f483d4cfdbafbbca2406b5ecd2796 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 27 Aug 2025 15:15:29 +0800 Subject: [PATCH 26/33] fix(ci): checkout no persist credentials --- .github/workflows/ci.yml | 8 ++++++++ .github/workflows/kernel-compatibility-test.yml | 13 ++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 939ea55..d73994d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,8 @@ jobs: timeout-minutes: 45 steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@master with: toolchain: ${{matrix.toolchain}} @@ -48,6 +50,8 @@ jobs: timeout-minutes: 45 steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@master with: toolchain: 1.77.0 @@ -67,6 +71,8 @@ jobs: RUSTDOCFLAGS: -Dwarnings steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@nightly - uses: dtolnay/install@cargo-docs-rs - run: cargo docs-rs --package ktls @@ -78,6 +84,8 @@ jobs: timeout-minutes: 45 steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@master with: toolchain: 1.77.0 diff --git a/.github/workflows/kernel-compatibility-test.yml b/.github/workflows/kernel-compatibility-test.yml index 913767f..fb49b8b 100644 --- a/.github/workflows/kernel-compatibility-test.yml +++ b/.github/workflows/kernel-compatibility-test.yml @@ -17,6 +17,9 @@ on: description: "Space-separated list of Linux kernel versions to test (e.g., '6.12 6.6 6.1.148 5.15.189 5.10.240 5.4.296')" required: true +permissions: + contents: read + jobs: prepare-matrix: runs-on: ubuntu-latest @@ -26,9 +29,9 @@ jobs: - name: Set matrix id: set-matrix run: | - if [ -n "${{ github.event.inputs.kernel_versions }}" ]; then + if [ -n "${GITHUB_EVENT_INPUTS_KERNEL_VERSIONS}" ]; then # Manual trigger with custom versions - versions="${{ github.event.inputs.kernel_versions }}" + versions="${GITHUB_EVENT_INPUTS_KERNEL_VERSIONS}" echo "Using manual input versions: $versions" else # Default versions for push events @@ -40,6 +43,8 @@ jobs: json_array=$(echo "$versions" | tr ' ' '\n' | jq -R . | jq -s -c .) echo "matrix={\"kernel_version\":$json_array}" >> $GITHUB_OUTPUT echo "Generated matrix: {\"kernel_version\":$json_array}" + env: + GITHUB_EVENT_INPUTS_KERNEL_VERSIONS: ${{ github.event.inputs.kernel_versions }} build: needs: prepare-matrix @@ -52,6 +57,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + persist-credentials: false - name: Install dependencies run: | @@ -165,7 +172,7 @@ jobs: run: | qemu-system-x86_64 \ -device isa-debug-exit,iobase=0xf4,iosize=0x04 \ - -kernel linux-${{ env.KERNEL_VERSION }}/arch/x86/boot/bzImage \ + -kernel linux-${KERNEL_VERSION}/arch/x86/boot/bzImage \ -initrd initramfs.cpio.gz \ -netdev user,id=net0 \ -device e1000,netdev=net0 \ From 2c838fa5d7ed74eaa518259e6fcbf9295878c858 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Thu, 28 Aug 2025 23:55:16 +0800 Subject: [PATCH 27/33] [skip ci] apply rustfmt --- ktls/src/setup.rs | 4 ++-- ktls/src/utils/suites.rs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ktls/src/setup.rs b/ktls/src/setup.rs index b38fe45..99c0896 100644 --- a/ktls/src/setup.rs +++ b/ktls/src/setup.rs @@ -22,10 +22,10 @@ mod tls; mod ulp; -#[cfg(not(feature = "raw-api"))] -pub(crate) use tls::{setup_tls_params, setup_tls_params_rx, setup_tls_params_tx, TlsCryptoInfoTx}; #[cfg(feature = "raw-api")] pub use tls::{ setup_tls_params, setup_tls_params_rx, setup_tls_params_tx, TlsCryptoInfoRx, TlsCryptoInfoTx, }; +#[cfg(not(feature = "raw-api"))] +pub(crate) use tls::{setup_tls_params, setup_tls_params_rx, setup_tls_params_tx, TlsCryptoInfoTx}; pub use ulp::{setup_ulp, SetupError}; diff --git a/ktls/src/utils/suites.rs b/ktls/src/utils/suites.rs index 30f148d..0a6c193 100644 --- a/ktls/src/utils/suites.rs +++ b/ktls/src/utils/suites.rs @@ -4,9 +4,10 @@ use std::collections::HashSet; use std::io; use std::net::{TcpListener, TcpStream}; -use crate::setup::{setup_ulp, SetupError, TlsCryptoInfoTx}; use rustls::{CipherSuite, SupportedCipherSuite, SupportedProtocolVersion}; +use crate::setup::{setup_ulp, SetupError, TlsCryptoInfoTx}; + #[allow(clippy::module_name_repetitions)] #[derive(Debug, Clone)] /// A collection of compatible cipher suites for current kernel. From 8a442032ef78c2afa84ae104e03cfc105cf03966 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Fri, 29 Aug 2025 00:06:53 +0800 Subject: [PATCH 28/33] refactor: replace pin-project with lite version --- Cargo.lock | 22 +--------- ktls/Cargo.toml | 2 +- ktls/src/stream.rs | 105 +++++++++++++++++++++------------------------ 3 files changed, 52 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c245bd..200d308 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -283,7 +283,7 @@ dependencies = [ "libc", "log", "nix", - "pin-project", + "pin-project-lite", "rand", "rcgen", "rustls", @@ -501,26 +501,6 @@ dependencies = [ "serde", ] -[[package]] -name = "pin-project" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" version = "0.2.16" diff --git a/ktls/Cargo.toml b/ktls/Cargo.toml index 510dee4..6f3a277 100644 --- a/ktls/Cargo.toml +++ b/ktls/Cargo.toml @@ -23,7 +23,7 @@ bitflags = "2.9" libc = { version = "0.2.175", features = ["const-extern-fn"] } log = { version = "0.4.27", optional = true } nix = { version = "0.30.1", features = ["socket", "uio", "net"] } -pin-project = "1.1" +pin-project-lite = "0.2.16" rustls = { version = "0.23.31", default-features = false, features = ["std"] } thiserror = "2.0" tokio = { version = "1.40", optional = true } diff --git a/ktls/src/stream.rs b/ktls/src/stream.rs index 9ef488c..48f7098 100644 --- a/ktls/src/stream.rs +++ b/ktls/src/stream.rs @@ -11,7 +11,6 @@ pub mod impl_tokio; #[cfg(feature = "raw-api")] use std::io; use std::os::fd::AsFd; -use std::pin::Pin; use rustls::client::UnbufferedClientConnection; use rustls::server::UnbufferedServerConnection; @@ -23,62 +22,58 @@ use crate::stream::context::{Context, StreamState, TlsConnData}; const DEFAULT_SCRATCH_CAPACITY: usize = 64; -#[pin_project::pin_project(project = KTlsStreamProject, PinnedDrop)] -/// A thin wrapper around an inner socket with kernel TLS (kTLS) offload -/// configured. -/// -/// This implements traits [`Read`](std::io::Read) and -/// [`Write`](std::io::Write), [`AsyncRead`](tokio::io::AsyncRead) and -/// [`AsyncWrite`](tokio::io::AsyncWrite) (when feature `async-io-tokio` is -/// enabled). -/// -/// For those who may need low-level access to the inner socket, feature -/// `raw-api` provides an unsafe method [`as_raw`](Self::as_raw) to get a -/// mutable reference to the inner socket. -/// -/// ## Behaviours -/// -/// Once a TLS `close_notify` alert from the peer is received, all subsequent -/// read operations will return EOF. -/// -/// Once the caller explicitly calls `(poll_)shutdown` on the stream, all -/// subsequent write operations will return 0 bytes, indicating that the -/// stream is closed for writing. -/// -/// Once the stream is being dropped, a `close_notify` alert would be sent to -/// the peer automatically before shutting down the inner socket, according to -/// [RFC 8446, section 6.1]. -/// -/// The caller may call `(poll_)shutdown` on the stream to shutdown explicitly -/// both sides of the stream. Currently, there's no way provided by this crate -/// to shutdown the TLS stream write side only. For TLS 1.2, this is ideal since -/// once one party sends a `close_notify` alert, *the other party MUST respond -/// with a `close_notify` alert of its own and close down the connection -/// immediately*, according to [RFC 5246, section 7.2.1]; for TLS 1.3, *both -/// parties need not wait to receive a "`close_notify`" alert before -/// closing their read side of the connection*, according to [RFC 8446, section -/// 6.1]. -/// -/// [RFC 5246, section 7.2.1]: https://tools.ietf.org/html/rfc5246#section-7.2.1 -/// [RFC 8446, section 6.1]: https://tools.ietf.org/html/rfc8446#section-6.1 -pub struct KtlsStream -where - S: AsFd, -{ - #[pin] - inner: S, - - /// The context of the kTLS stream. - ctx: Context, -} +pin_project_lite::pin_project! { + #[project = KTlsStreamProject] + /// A thin wrapper around an inner socket with kernel TLS (kTLS) offload + /// configured. + /// + /// This implements traits [`Read`](std::io::Read) and + /// [`Write`](std::io::Write), [`AsyncRead`](tokio::io::AsyncRead) and + /// [`AsyncWrite`](tokio::io::AsyncWrite) (when feature `async-io-tokio` is + /// enabled). + /// + /// For those who may need low-level access to the inner socket, feature + /// `raw-api` provides an unsafe method [`as_raw`](Self::as_raw) to get a + /// mutable reference to the inner socket. + /// + /// ## Behaviours + /// + /// Once a TLS `close_notify` alert from the peer is received, all subsequent + /// read operations will return EOF. + /// + /// Once the caller explicitly calls `(poll_)shutdown` on the stream, all + /// subsequent write operations will return 0 bytes, indicating that the + /// stream is closed for writing. + /// + /// Once the stream is being dropped, a `close_notify` alert would be sent to + /// the peer automatically before shutting down the inner socket, according to + /// [RFC 8446, section 6.1]. + /// + /// The caller may call `(poll_)shutdown` on the stream to shutdown explicitly + /// both sides of the stream. Currently, there's no way provided by this crate + /// to shutdown the TLS stream write side only. For TLS 1.2, this is ideal since + /// once one party sends a `close_notify` alert, *the other party MUST respond + /// with a `close_notify` alert of its own and close down the connection + /// immediately*, according to [RFC 5246, section 7.2.1]; for TLS 1.3, *both + /// parties need not wait to receive a "`close_notify`" alert before + /// closing their read side of the connection*, according to [RFC 8446, section + /// 6.1]. + /// + /// [RFC 5246, section 7.2.1]: https://tools.ietf.org/html/rfc5246#section-7.2.1 + /// [RFC 8446, section 6.1]: https://tools.ietf.org/html/rfc8446#section-6.1 + pub struct KtlsStream { + #[pin] + inner: S, + ctx: Context, + } -#[pin_project::pinned_drop] -impl PinnedDrop for KtlsStream { - fn drop(self: Pin<&mut Self>) { - let this = self.project(); + impl PinnedDrop for KtlsStream { + fn drop(this: Pin<&mut Self>) { + let this = this.project(); - // TODO: No need to flush? It's a no-op anyway for TcpStream / UnixStream. - this.ctx.shutdown(&*this.inner); + // TODO: No need to flush? It's a no-op anyway for TcpStream / UnixStream. + this.ctx.shutdown(&*this.inner); + } } } From d9b529bf04d10f784cd7ebc00da29da69f6f70ee Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Fri, 29 Aug 2025 00:22:24 +0800 Subject: [PATCH 29/33] refactor: remove thiserror (syn) --- Cargo.lock | 109 +++++++++++++++++++++++++++++++++------ ktls/Cargo.toml | 1 - ktls/src/error.rs | 68 +++++++++++++++++++----- ktls/src/setup/ulp.rs | 15 ++++-- ktls/src/stream/error.rs | 53 +++++++++++++++---- 5 files changed, 203 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 200d308..31077cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,7 +67,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -191,7 +191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -288,7 +288,6 @@ dependencies = [ "rcgen", "rustls", "test-case", - "thiserror", "tokio", "tracing", "tracing-subscriber", @@ -333,7 +332,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets", + "windows-targets 0.53.3", ] [[package]] @@ -488,7 +487,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1055,13 +1054,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1070,7 +1075,16 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", ] [[package]] @@ -1079,14 +1093,31 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -1095,48 +1126,96 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "wit-bindgen" version = "0.45.0" diff --git a/ktls/Cargo.toml b/ktls/Cargo.toml index 6f3a277..3182008 100644 --- a/ktls/Cargo.toml +++ b/ktls/Cargo.toml @@ -25,7 +25,6 @@ log = { version = "0.4.27", optional = true } nix = { version = "0.30.1", features = ["socket", "uio", "net"] } pin-project-lite = "0.2.16" rustls = { version = "0.23.31", default-features = false, features = ["std"] } -thiserror = "2.0" tokio = { version = "1.40", optional = true } tracing = { version = "0.1.41", optional = true } diff --git a/ktls/src/error.rs b/ktls/src/error.rs index a35002f..7ec79c1 100644 --- a/ktls/src/error.rs +++ b/ktls/src/error.rs @@ -1,25 +1,50 @@ //! Error types for the `ktls` crate -use std::io; +use std::{fmt, io}; use rustls::SupportedCipherSuite; #[non_exhaustive] -#[derive(Debug, thiserror::Error)] +#[derive(Debug)] /// Unified error type for this crate pub enum Error { - #[error(transparent)] /// Invalid crypto material, e.g., wrong size key or IV. - InvalidCryptoInfo(#[from] InvalidCryptoInfo), + InvalidCryptoInfo(InvalidCryptoInfo), - #[error("failed to extract connection secrets from rustls connection: {0}")] /// Failed to extract connection secrets from rustls connection, e.g., not /// have `config.enable_secret_extraction` set to true - ExtractSecrets(#[source] rustls::Error), + ExtractSecrets(rustls::Error), - #[error(transparent)] /// General IO error. - IO(#[from] io::Error), + IO(io::Error), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidCryptoInfo(e) => e.fmt(f), + Self::ExtractSecrets(e) => { + write!(f, "failed to extract secrets from rustls connection: {e}") + } + Self::IO(e) => e.fmt(f), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::InvalidCryptoInfo(_) => None, + Self::ExtractSecrets(e) => Some(e), + Self::IO(e) => Some(e), + } + } +} + +impl From for Error { + fn from(error: io::Error) -> Self { + Self::IO(error) + } } impl From for io::Error { @@ -31,20 +56,37 @@ impl From for io::Error { } } +impl From for Error { + fn from(error: InvalidCryptoInfo) -> Self { + Self::InvalidCryptoInfo(error) + } +} + +#[non_exhaustive] #[derive(Debug)] -#[derive(thiserror::Error)] /// Crypto material is invalid, e.g., wrong size key or IV. -#[non_exhaustive] pub enum InvalidCryptoInfo { - #[error("Wrong size key")] /// The provided key has an incorrect size (unlikely). WrongSizeKey, - #[error("Wrong size iv")] /// The provided IV has an incorrect size (unlikely). WrongSizeIv, - #[error("the negotiated cipher suite [{0:?}] is not supported")] /// The negotiated cipher suite is not supported by this crate. UnsupportedCipherSuite(SupportedCipherSuite), } + +impl fmt::Display for InvalidCryptoInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::WrongSizeKey => write!(f, "wrong size key"), + Self::WrongSizeIv => write!(f, "wrong size iv"), + Self::UnsupportedCipherSuite(suite) => { + write!( + f, + "the negotiated cipher suite [{suite:?}] is not supported" + ) + } + } + } +} diff --git a/ktls/src/setup/ulp.rs b/ktls/src/setup/ulp.rs index 26786b2..96cf889 100644 --- a/ktls/src/setup/ulp.rs +++ b/ktls/src/setup/ulp.rs @@ -33,11 +33,8 @@ pub fn setup_ulp(socket: S) -> Result> { } #[allow(clippy::exhaustive_structs)] -#[derive(thiserror::Error)] -#[error("{error}")] /// An error that occurred while configuring the ULP. pub struct SetupError { - #[source] /// The I/O error that occurred while configuring the ULP. pub error: io::Error, @@ -50,3 +47,15 @@ impl fmt::Debug for SetupError { self.error.fmt(f) } } + +impl fmt::Display for SetupError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.error) + } +} + +impl std::error::Error for SetupError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.error) + } +} diff --git a/ktls/src/stream/error.rs b/ktls/src/stream/error.rs index acc8676..a47b35d 100644 --- a/ktls/src/stream/error.rs +++ b/ktls/src/stream/error.rs @@ -1,43 +1,74 @@ //! Error type of `KtlsStream` and related operations. -use std::io; +use std::{fmt, io}; use rustls::{AlertDescription, InvalidMessage, PeerMisbehaved}; #[non_exhaustive] #[derive(Debug)] -#[derive(thiserror::Error)] /// The error type for `KtlsStream` and related operations. pub enum KtlsStreamError { - #[error("Received corrupt message of type {0:?}")] /// A corrupt message was received from the peer. InvalidMessage(InvalidMessage), - #[error("Peer misbehaved: {0:?}")] /// The peer misbehaved in some way. PeerMisbehaved(PeerMisbehaved), - #[error("Key update failed: {0}")] /// Failed to handle a key update request. - KeyUpdateFailed(#[source] rustls::Error), + KeyUpdateFailed(rustls::Error), - #[error("Failed to handle a provided session ticket: {0}")] /// Failed to handle a provided session ticket. - SessionTicketFailed(#[source] rustls::Error), + SessionTicketFailed(rustls::Error), - #[error("the connection has been closed by the peer")] /// The connection has been closed by the peer. Closed, - #[error("cannot handle control messages while there is buffered data to read")] /// Cannot handle control messages while there is buffered data to read. ControlMessageWithBufferedData, - #[error("Connection peer closed the connection with an alert: {0:?}")] /// The connection peer closed the connection with an alert. Alert(AlertDescription), } +impl fmt::Display for KtlsStreamError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidMessage(e) => { + write!(f, "Received corrupt message of type {e:?}") + } + Self::PeerMisbehaved(e) => write!(f, "Peer misbehaved: {e:?}"), + Self::KeyUpdateFailed(e) => { + write!(f, "Failed to handle a key update request: {e}") + } + Self::SessionTicketFailed(e) => { + write!(f, "Failed to handle a provided session ticket: {e}") + } + Self::Closed => write!(f, "The connection has been closed by the peer"), + Self::ControlMessageWithBufferedData => { + write!( + f, + "Cannot handle control messages while there is buffered data to read" + ) + } + Self::Alert(desc) => { + write!( + f, + "Connection peer closed the connection with an alert: {desc:?}", + ) + } + } + } +} + +impl std::error::Error for KtlsStreamError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::KeyUpdateFailed(e) | Self::SessionTicketFailed(e) => Some(e), + _ => None, + } + } +} + impl From for KtlsStreamError { fn from(error: InvalidMessage) -> Self { Self::InvalidMessage(error) From 9605c84b4341169fd59a792aafb55303aec59323 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Fri, 29 Aug 2025 00:28:37 +0800 Subject: [PATCH 30/33] refactor: adjust default features --- ktls-util/Cargo.toml | 4 ++-- ktls/Cargo.toml | 2 +- ktls/src/utils.rs | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ktls-util/Cargo.toml b/ktls-util/Cargo.toml index 6c7890d..d1f6006 100644 --- a/ktls-util/Cargo.toml +++ b/ktls-util/Cargo.toml @@ -12,13 +12,13 @@ repository.workspace = true publish = false # Currently for test only [dependencies] -ktls = { path = "../ktls", default-features = false, features = ["raw-api"] } +ktls = { path = "../ktls", default-features = false } rustls = { version = "0.23.27", default-features = false, features = ["std"] } thiserror = "2.0.16" tokio = { version = "1.40", features = ["io-util"] } [features] -default = ["tls12", "aws-lc-rs"] +default = ["tls12", "ring"] # Enable rustls TLS 1.2 support tls12 = ["ktls/tls12", "rustls/tls12"] diff --git a/ktls/Cargo.toml b/ktls/Cargo.toml index 3182008..9c27f6c 100644 --- a/ktls/Cargo.toml +++ b/ktls/Cargo.toml @@ -39,7 +39,7 @@ tracing = "0.1.41" tracing-subscriber = { version = "0.3.19", features = ["env-filter", "parking_lot"] } [features] -default = ["tls12", "async-io-tokio", "probe-ktls-compatibility"] +default = ["tls12", "async-io-tokio"] # Enable rustls TLS 1.2 support tls12 = ["rustls/tls12"] diff --git a/ktls/src/utils.rs b/ktls/src/utils.rs index 257821a..0228ca8 100644 --- a/ktls/src/utils.rs +++ b/ktls/src/utils.rs @@ -1,5 +1,7 @@ //! Utilities +#[cfg(feature = "probe-ktls-compatibility")] mod suites; +#[cfg(feature = "probe-ktls-compatibility")] pub use suites::CompatibleCipherSuites; From 54af66493d5c0b329edf8828fb720a01c65ff60c Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Fri, 29 Aug 2025 01:11:51 +0800 Subject: [PATCH 31/33] fix: clippy and cargo-doc, and feature flag --- .github/workflows/ci.yml | 2 +- .../workflows/kernel-compatibility-test.yml | 2 +- Cargo.lock | 342 ++---------------- Cargo.toml | 2 +- ktls-test/Cargo.toml | 27 ++ {ktls => ktls-test}/examples/client.rs | 4 +- {ktls => ktls-test}/examples/server.rs | 3 +- .../common/mod.rs => ktls-test/src/common.rs | 13 +- .../mod.rs => ktls-test/src/common/client.rs | 2 +- .../src}/common/client/verifier.rs | 1 + .../src}/common/server.rs | 2 +- ktls-test/src/lib.rs | 6 + {ktls => ktls-test}/tests/client.rs | 11 +- {ktls => ktls-test}/tests/echo.rs | 4 +- ktls-util/Cargo.toml | 12 - ktls/Cargo.toml | 12 +- ktls/src/setup.rs | 6 +- ktls/src/setup/tls.rs | 8 +- ktls/src/stream.rs | 21 +- ktls/src/stream/context.rs | 2 +- ktls/src/utils/suites.rs | 11 +- 21 files changed, 97 insertions(+), 396 deletions(-) create mode 100644 ktls-test/Cargo.toml rename {ktls => ktls-test}/examples/client.rs (96%) rename {ktls => ktls-test}/examples/server.rs (97%) rename ktls/examples/common/mod.rs => ktls-test/src/common.rs (97%) rename ktls/examples/common/client/mod.rs => ktls-test/src/common/client.rs (99%) rename {ktls/examples => ktls-test/src}/common/client/verifier.rs (99%) rename {ktls/examples => ktls-test/src}/common/server.rs (97%) create mode 100644 ktls-test/src/lib.rs rename {ktls => ktls-test}/tests/client.rs (97%) rename {ktls => ktls-test}/tests/echo.rs (88%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d73994d..0e7910f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,8 +59,8 @@ jobs: with: tool: just - run: just build --package ktls - - run: just build --package ktls --features raw-api - run: just build --package ktls-sys + - run: just build --package ktls-test - run: just build --package ktls-util doc: diff --git a/.github/workflows/kernel-compatibility-test.yml b/.github/workflows/kernel-compatibility-test.yml index fb49b8b..ba55804 100644 --- a/.github/workflows/kernel-compatibility-test.yml +++ b/.github/workflows/kernel-compatibility-test.yml @@ -76,7 +76,7 @@ jobs: - name: Generate the test binary run: | - cargo build --package ktls --example client --features tracing --release --target x86_64-unknown-linux-musl + cargo build --package ktls-test --example client --release --target x86_64-unknown-linux-musl - name: Cache Linux source id: cache-kernel diff --git a/Cargo.lock b/Cargo.lock index 31077cc..87ea4ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,29 +32,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "aws-lc-rs" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" -dependencies = [ - "bindgen", - "cc", - "cmake", - "dunce", - "fs_extra", -] - [[package]] name = "backtrace" version = "0.3.75" @@ -67,7 +44,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -76,29 +53,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bindgen" -version = "0.69.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "itertools", - "lazy_static", - "lazycell", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn", - "which", -] - [[package]] name = "bitflags" version = "2.9.3" @@ -117,20 +71,9 @@ version = "1.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" dependencies = [ - "jobserver", - "libc", "shlex", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" version = "1.0.3" @@ -143,26 +86,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "cmake" -version = "0.1.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" -dependencies = [ - "cc", -] - [[package]] name = "deranged" version = "0.4.0" @@ -172,34 +95,6 @@ dependencies = [ "powerfmt", ] -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "errno" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - [[package]] name = "getrandom" version = "0.2.16" @@ -229,21 +124,6 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "io-uring" version = "0.7.10" @@ -256,34 +136,31 @@ dependencies = [ ] [[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +name = "ktls" +version = "6.0.2" dependencies = [ - "either", + "bitflags", + "libc", + "log", + "nix", + "pin-project-lite", + "rustls", + "tokio", + "tracing", ] [[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.3", - "libc", -] +name = "ktls-sys" +version = "1.0.2" [[package]] -name = "ktls" -version = "6.0.2" +name = "ktls-test" +version = "0.0.0" dependencies = [ "bitflags", + "ktls", "ktls-util", - "libc", - "log", "nix", - "pin-project-lite", "rand", "rcgen", "rustls", @@ -293,10 +170,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "ktls-sys" -version = "1.0.2" - [[package]] name = "ktls-util" version = "0.0.0" @@ -313,34 +186,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" -[[package]] -name = "libloading" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" -dependencies = [ - "cfg-if", - "windows-targets 0.53.3", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "lock_api" version = "0.4.13" @@ -381,12 +232,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -420,16 +265,6 @@ dependencies = [ "memoffset", ] -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -487,7 +322,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -521,16 +356,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro2" version = "1.0.101" @@ -670,32 +495,12 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - [[package]] name = "rustls" version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ - "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", @@ -719,7 +524,6 @@ version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ - "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -1020,18 +824,6 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] - [[package]] name = "winapi" version = "0.3.9" @@ -1054,19 +846,13 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -1075,16 +861,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.3", + "windows-targets", ] [[package]] @@ -1093,31 +870,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -1126,96 +886,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "wit-bindgen" version = "0.45.0" diff --git a/Cargo.toml b/Cargo.toml index 1383727..56eb375 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["ktls", "ktls-sys", "ktls-util"] +members = ["ktls", "ktls-sys", "ktls-test", "ktls-util"] resolver = "2" [workspace.package] diff --git a/ktls-test/Cargo.toml b/ktls-test/Cargo.toml new file mode 100644 index 0000000..fb7dd7b --- /dev/null +++ b/ktls-test/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "ktls-test" +version = "0.0.0" +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +categories.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true +publish = false + +[dependencies] +bitflags = "2.9" +ktls = { path = "../ktls", features = ["raw-api", "tracing", "probe-ktls-compatibility"] } +ktls-util = { path = "../ktls-util" } +nix = "0.30.1" +rand = "0.9.2" +rcgen = { version = "0.14.3" } +rustls = { version = "0.23.27", default-features = false, features = ["std", "ring"] } +tokio = { version = "1.40", features = ["io-util", "macros", "net", "rt-multi-thread", "signal", "sync", "time"] } +tracing = "0.1.41" +tracing-subscriber = { version = "0.3.19", features = ["env-filter", "parking_lot"] } + +[dev-dependencies] +test-case = "3.3.1" diff --git a/ktls/examples/client.rs b/ktls-test/examples/client.rs similarity index 96% rename from ktls/examples/client.rs rename to ktls-test/examples/client.rs index ed14aef..c672559 100644 --- a/ktls/examples/client.rs +++ b/ktls-test/examples/client.rs @@ -1,9 +1,9 @@ //! Example: TLS client using `ktls`. -mod common; - use std::error::Error; +use ktls_test::common; + #[tokio::main] async fn main() -> Result<(), Box> { let (ret1, ret2, ret3, ret4) = tokio::join!( diff --git a/ktls/examples/server.rs b/ktls-test/examples/server.rs similarity index 97% rename from ktls/examples/server.rs rename to ktls-test/examples/server.rs index 66e0a66..a0c31c3 100644 --- a/ktls/examples/server.rs +++ b/ktls-test/examples/server.rs @@ -1,9 +1,8 @@ //! Example: TLS server using `ktls`. +use ktls_test::common; use tokio::net::TcpListener; -mod common; - #[tokio::main] async fn main() -> Result<(), Box> { let _ = tracing_subscriber::fmt() diff --git a/ktls/examples/common/mod.rs b/ktls-test/src/common.rs similarity index 97% rename from ktls/examples/common/mod.rs rename to ktls-test/src/common.rs index af7e9a7..53d47fe 100644 --- a/ktls/examples/common/mod.rs +++ b/ktls-test/src/common.rs @@ -17,6 +17,7 @@ pub mod client; pub mod server; #[allow(dead_code)] +#[tracing::instrument(err)] /// Echo test, shared by examples and tests. pub async fn run_echo_test(close_party: CloseParty, test_option: TestOption) -> io::Result<()> { let _ = tracing_subscriber::fmt() @@ -65,7 +66,7 @@ pub async fn run_echo_test(close_party: CloseParty, test_option: TestOption) -> ktls_stream.read_exact(&mut buf).await?; - assert!(buf == &test_data[..16]); + assert!(buf == test_data[..16]); } { @@ -78,7 +79,7 @@ pub async fn run_echo_test(close_party: CloseParty, test_option: TestOption) -> ktls_stream.read_exact(&mut buf).await?; - assert!(buf == &test_data[..16]); + assert!(buf == test_data[..16]); } { @@ -108,7 +109,7 @@ pub async fn run_echo_test(close_party: CloseParty, test_option: TestOption) -> ktls_stream.read_exact(&mut buf).await?; - assert!(buf == &test_data[..TOTAL]); + assert!(buf == test_data[..TOTAL]); } { @@ -157,6 +158,8 @@ pub async fn run_echo_test(close_party: CloseParty, test_option: TestOption) -> } } + tracing::info!("Echo test completed: {:#?}", compatible_cipher_suites); + Ok(()) } @@ -164,10 +167,10 @@ pub async fn run_echo_test(close_party: CloseParty, test_option: TestOption) -> #[derive(Debug, Clone, Copy)] /// Which party will close the connection actively. pub enum CloseParty { - /// + /// The client Client, - /// + /// The server Server, } diff --git a/ktls/examples/common/client/mod.rs b/ktls-test/src/common/client.rs similarity index 99% rename from ktls/examples/common/client/mod.rs rename to ktls-test/src/common/client.rs index cfaf79c..c8fe0fb 100644 --- a/ktls/examples/common/client/mod.rs +++ b/ktls-test/src/common/client.rs @@ -1,4 +1,4 @@ -//! +//! Client #![allow(dead_code)] diff --git a/ktls/examples/common/client/verifier.rs b/ktls-test/src/common/client/verifier.rs similarity index 99% rename from ktls/examples/common/client/verifier.rs rename to ktls-test/src/common/client/verifier.rs index db50890..9697cef 100644 --- a/ktls/examples/common/client/verifier.rs +++ b/ktls-test/src/common/client/verifier.rs @@ -8,6 +8,7 @@ use rustls::DigitallySignedStruct; pub struct NoCertificateVerification; impl NoCertificateVerification { + #[must_use] pub fn new() -> Arc { Arc::new(Self) } diff --git a/ktls/examples/common/server.rs b/ktls-test/src/common/server.rs similarity index 97% rename from ktls/examples/common/server.rs rename to ktls-test/src/common/server.rs index 8f0db7e..8a25906 100644 --- a/ktls/examples/common/server.rs +++ b/ktls-test/src/common/server.rs @@ -1,4 +1,4 @@ -//! Example: TLS server using `ktls`. +//! Server #![allow(dead_code)] diff --git a/ktls-test/src/lib.rs b/ktls-test/src/lib.rs new file mode 100644 index 0000000..c304aff --- /dev/null +++ b/ktls-test/src/lib.rs @@ -0,0 +1,6 @@ +//! For testing ktls only + +#![allow(clippy::missing_panics_doc)] +#![allow(clippy::missing_errors_doc)] + +pub mod common; diff --git a/ktls/tests/client.rs b/ktls-test/tests/client.rs similarity index 97% rename from ktls/tests/client.rs rename to ktls-test/tests/client.rs index 7ba4cdf..0d90665 100644 --- a/ktls/tests/client.rs +++ b/ktls-test/tests/client.rs @@ -1,24 +1,21 @@ //! Test: client connect to real world websites. -use core::num::NonZeroUsize; -use core::time::Duration; use std::io; +use std::num::NonZeroUsize; +use std::time::Duration; use ktls::KtlsStream; +use ktls_test::common; use rustls::pki_types::ServerName; use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; use tokio::time::timeout; -mod common { - include!("../examples/common/mod.rs"); -} - #[test_case::test_matrix( [ "www.google.com", // Google CDN "www.bing.com", // Azure CDN - "github.com", // Azure CDN + // "github.com", // Azure CDN "www.baidu.com", // Baidu CDN "stackoverflow.com", // Cloudflare CDN "fastly.com", // Fastly CDN diff --git a/ktls/tests/echo.rs b/ktls-test/tests/echo.rs similarity index 88% rename from ktls/tests/echo.rs rename to ktls-test/tests/echo.rs index 36a222f..687b377 100644 --- a/ktls/tests/echo.rs +++ b/ktls-test/tests/echo.rs @@ -2,9 +2,7 @@ use std::io; -mod common { - include!("../examples/common/mod.rs"); -} +use ktls_test::common; #[test_case::test_matrix( [ diff --git a/ktls-util/Cargo.toml b/ktls-util/Cargo.toml index d1f6006..700e2a1 100644 --- a/ktls-util/Cargo.toml +++ b/ktls-util/Cargo.toml @@ -16,15 +16,3 @@ ktls = { path = "../ktls", default-features = false } rustls = { version = "0.23.27", default-features = false, features = ["std"] } thiserror = "2.0.16" tokio = { version = "1.40", features = ["io-util"] } - -[features] -default = ["tls12", "ring"] - -# Enable rustls TLS 1.2 support -tls12 = ["ktls/tls12", "rustls/tls12"] - -# Enable rustls ring crypto backend -ring = ["rustls/ring"] - -# Enable rustls aws-lc-rs crypto backend -aws-lc-rs = ["rustls/aws-lc-rs"] diff --git a/ktls/Cargo.toml b/ktls/Cargo.toml index 9c27f6c..cb358c6 100644 --- a/ktls/Cargo.toml +++ b/ktls/Cargo.toml @@ -15,7 +15,7 @@ readme.workspace = true repository.workspace = true [package.metadata.docs.rs] -features = ["tls12", "async-io-tokio", "raw-api"] +features = ["tls12", "async-io-tokio", "raw-api", "probe-ktls-compatibility"] targets = ["x86_64-unknown-linux-gnu"] [dependencies] @@ -28,16 +28,6 @@ rustls = { version = "0.23.31", default-features = false, features = ["std"] } tokio = { version = "1.40", optional = true } tracing = { version = "0.1.41", optional = true } -[dev-dependencies] -ktls-util = { path = "../ktls-util", default-features = false, features = ["tls12", "ring"] } -rand = "0.9.2" -rcgen = { version = "0.14.3" } -rustls = { version = "0.23.27", default-features = false, features = ["std", "ring"] } -test-case = "3.3.1" -tokio = { version = "1.40", features = ["io-util", "macros", "net", "rt-multi-thread", "signal", "sync", "time"] } -tracing = "0.1.41" -tracing-subscriber = { version = "0.3.19", features = ["env-filter", "parking_lot"] } - [features] default = ["tls12", "async-io-tokio"] diff --git a/ktls/src/setup.rs b/ktls/src/setup.rs index 99c0896..dfc30b3 100644 --- a/ktls/src/setup.rs +++ b/ktls/src/setup.rs @@ -19,13 +19,11 @@ #![allow(clippy::module_name_repetitions)] -mod tls; -mod ulp; +pub(crate) mod tls; +pub(crate) mod ulp; #[cfg(feature = "raw-api")] pub use tls::{ setup_tls_params, setup_tls_params_rx, setup_tls_params_tx, TlsCryptoInfoRx, TlsCryptoInfoTx, }; -#[cfg(not(feature = "raw-api"))] -pub(crate) use tls::{setup_tls_params, setup_tls_params_rx, setup_tls_params_tx, TlsCryptoInfoTx}; pub use ulp::{setup_ulp, SetupError}; diff --git a/ktls/src/setup/tls.rs b/ktls/src/setup/tls.rs index 529084f..aab3466 100644 --- a/ktls/src/setup/tls.rs +++ b/ktls/src/setup/tls.rs @@ -1,6 +1,7 @@ //! See the [module-level documentation](crate::setup) for more details. #![allow(rustdoc::private_intra_doc_links)] +#![allow(unreachable_pub)] use std::os::fd::{AsFd, AsRawFd}; use std::{io, mem}; @@ -13,7 +14,6 @@ use rustls::{ConnectionTrafficSecrets, ExtractedSecrets, SupportedCipherSuite}; use crate::error::{Error, InvalidCryptoInfo}; -#[cfg_attr(not(feature = "raw-api"), allow(unreachable_pub))] /// Sets the kTLS parameters on the socket after the TLS handshake is completed. /// /// ## Errors @@ -33,7 +33,6 @@ pub fn setup_tls_params( .map_err(Error::IO) } -#[cfg_attr(not(feature = "raw-api"), allow(unreachable_pub))] /// Like [`setup_tls_params`], but only sets up the transmit direction. /// /// This is useful when performing key update. @@ -53,7 +52,6 @@ pub fn setup_tls_params_tx( .map_err(Error::IO) } -#[cfg_attr(not(feature = "raw-api"), allow(unreachable_pub))] /// Like [`setup_tls_params`], but only sets up the receive direction. /// /// This is useful when performing key update. @@ -106,21 +104,17 @@ impl SetSockOpt for TcpTls { } } -#[cfg_attr(not(feature = "raw-api"), allow(unreachable_pub))] #[repr(transparent)] /// Sets the Kernel TLS read/write parameters on the TCP socket. pub struct TlsCryptoInfo(TlsCryptoInfoImpl); -#[cfg_attr(not(feature = "raw-api"), allow(unreachable_pub))] /// See [`TlsCryptoInfo`]. pub type TlsCryptoInfoTx = TlsCryptoInfo<{ libc::TLS_TX }>; -#[cfg_attr(not(feature = "raw-api"), allow(unreachable_pub))] /// See [`TlsCryptoInfo`]. pub type TlsCryptoInfoRx = TlsCryptoInfo<{ libc::TLS_RX }>; #[cfg(any(feature = "raw-api", feature = "probe-ktls-compatibility"))] -#[allow(unreachable_pub)] impl TlsCryptoInfo { /// Create a custom [`TlsCryptoInfo`] from the given /// [`libc::tls12_crypto_info_aes_gcm_128`]. diff --git a/ktls/src/stream.rs b/ktls/src/stream.rs index 48f7098..a13772c 100644 --- a/ktls/src/stream.rs +++ b/ktls/src/stream.rs @@ -16,6 +16,7 @@ use rustls::client::UnbufferedClientConnection; use rustls::server::UnbufferedServerConnection; use crate::error::Error; +use crate::setup::tls::setup_tls_params; #[cfg(feature = "raw-api")] use crate::stream::context::Buffer; use crate::stream::context::{Context, StreamState, TlsConnData}; @@ -112,18 +113,12 @@ where let supported_cipher_suite = conn.negotiated_cipher_suite(); - let mut this = Self { + let this = Self { inner: socket, ctx: Context::new(StreamState::empty(), Vec::new(), TlsConnData::Client(conn)), }; - let ret = crate::setup::setup_tls_params(&this.inner, supported_cipher_suite, secrets); - - if ret.is_err() { - this.ctx.shutdown(&this.inner); - - ret?; - } + setup_tls_params(&this.inner, supported_cipher_suite, secrets)?; Ok(this) } @@ -172,18 +167,12 @@ where ), }; - let mut this = Self { + let this = Self { inner: socket, ctx: Context::new(state, buffer, TlsConnData::Server(conn)), }; - let ret = crate::setup::setup_tls_params(&this.inner, supported_cipher_suite, secrets); - - if ret.is_err() { - this.ctx.shutdown(&this.inner); - - ret?; - } + setup_tls_params(&this.inner, supported_cipher_suite, secrets)?; Ok(this) } diff --git a/ktls/src/stream/context.rs b/ktls/src/stream/context.rs index a78d206..34c8a03 100644 --- a/ktls/src/stream/context.rs +++ b/ktls/src/stream/context.rs @@ -17,7 +17,7 @@ use rustls::{ }; use crate::protocol::{KeyUpdateRequest, KEY_UPDATE_NOT_REQUESTED, KEY_UPDATE_REQUESTED}; -use crate::setup::{setup_tls_params_rx, setup_tls_params_tx}; +use crate::setup::tls::{setup_tls_params_rx, setup_tls_params_tx}; use crate::stream::error::KtlsStreamError; /// Helper macro to handle the return value of an I/O operation on the diff --git a/ktls/src/utils/suites.rs b/ktls/src/utils/suites.rs index 0a6c193..899d6ef 100644 --- a/ktls/src/utils/suites.rs +++ b/ktls/src/utils/suites.rs @@ -1,4 +1,4 @@ -//! kTLS cipher suite compatibility probe +//! See [`CompatibleCipherSuites`] use std::collections::HashSet; use std::io; @@ -6,7 +6,8 @@ use std::net::{TcpListener, TcpStream}; use rustls::{CipherSuite, SupportedCipherSuite, SupportedProtocolVersion}; -use crate::setup::{setup_ulp, SetupError, TlsCryptoInfoTx}; +use crate::setup::tls::TlsCryptoInfoTx; +use crate::setup::ulp::{setup_ulp, SetupError}; #[allow(clippy::module_name_repetitions)] #[derive(Debug, Clone)] @@ -19,11 +20,9 @@ pub struct CompatibleCipherSuites { } impl CompatibleCipherSuites { - /// Probes the current Linux kernel for kTLS cipher suite compatibility. + /// Probes the current Linux kernel for kTLS cipher suites compatibility. /// - /// Returns `None` if the kernel does not support kTLS, otherwise returns - /// a `CompatibleCipherSuites` containing supported cipher suites and - /// protocol versions. + /// Returns `None` if the kernel does not support kTLS. /// /// # Notes /// From 1e6fdb5fb1df954f60de7a6ea07fd624b67d18c6 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Sat, 30 Aug 2025 17:02:51 +0800 Subject: [PATCH 32/33] fix: add Debug impl back for KtlsStream --- ktls/src/stream.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ktls/src/stream.rs b/ktls/src/stream.rs index a13772c..5d06222 100644 --- a/ktls/src/stream.rs +++ b/ktls/src/stream.rs @@ -24,6 +24,7 @@ use crate::stream::context::{Context, StreamState, TlsConnData}; const DEFAULT_SCRATCH_CAPACITY: usize = 64; pin_project_lite::pin_project! { + #[derive(Debug)] #[project = KTlsStreamProject] /// A thin wrapper around an inner socket with kernel TLS (kTLS) offload /// configured. From cd4f967bf0696deaf2f3c94b9ab3bae3d4f80aea Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Wed, 3 Sep 2025 11:14:24 +0800 Subject: [PATCH 33/33] fix: update debug desc of InvalidCryptoInfo::UnsupportedCipherSuite --- ktls/src/error.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ktls/src/error.rs b/ktls/src/error.rs index 7ec79c1..3e6f0b0 100644 --- a/ktls/src/error.rs +++ b/ktls/src/error.rs @@ -72,7 +72,8 @@ pub enum InvalidCryptoInfo { /// The provided IV has an incorrect size (unlikely). WrongSizeIv, - /// The negotiated cipher suite is not supported by this crate. + /// The negotiated cipher suite is not supported for ktls by the running + /// kernel. UnsupportedCipherSuite(SupportedCipherSuite), } @@ -84,7 +85,8 @@ impl fmt::Display for InvalidCryptoInfo { Self::UnsupportedCipherSuite(suite) => { write!( f, - "the negotiated cipher suite [{suite:?}] is not supported" + "the negotiated cipher suite [{suite:?}] is not supported for ktls by the \ + running kernel" ) } }