summaryrefslogtreecommitdiffstats
path: root/third_party/rust/neqo-transport
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/neqo-transport')
-rw-r--r--third_party/rust/neqo-transport/.cargo-checksum.json2
-rw-r--r--third_party/rust/neqo-transport/Cargo.toml22
-rw-r--r--third_party/rust/neqo-transport/src/cc/classic_cc.rs105
-rw-r--r--third_party/rust/neqo-transport/src/cc/mod.rs3
-rw-r--r--third_party/rust/neqo-transport/src/cc/tests/cubic.rs34
-rw-r--r--third_party/rust/neqo-transport/src/cc/tests/new_reno.rs89
-rw-r--r--third_party/rust/neqo-transport/src/connection/mod.rs285
-rw-r--r--third_party/rust/neqo-transport/src/connection/state.rs39
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/cc.rs36
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/close.rs25
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/datagram.rs14
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/ecn.rs392
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/handshake.rs26
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/keys.rs10
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/migration.rs8
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/mod.rs31
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/stream.rs9
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/vn.rs8
-rw-r--r--third_party/rust/neqo-transport/src/ecn.rs225
-rw-r--r--third_party/rust/neqo-transport/src/events.rs4
-rw-r--r--third_party/rust/neqo-transport/src/frame.rs143
-rw-r--r--third_party/rust/neqo-transport/src/lib.rs27
-rw-r--r--third_party/rust/neqo-transport/src/packet/mod.rs1
-rw-r--r--third_party/rust/neqo-transport/src/path.rs77
-rw-r--r--third_party/rust/neqo-transport/src/qlog.rs73
-rw-r--r--third_party/rust/neqo-transport/src/recovery.rs52
-rw-r--r--third_party/rust/neqo-transport/src/send_stream.rs18
-rw-r--r--third_party/rust/neqo-transport/src/sender.rs5
-rw-r--r--third_party/rust/neqo-transport/src/server.rs7
-rw-r--r--third_party/rust/neqo-transport/src/tracking.rs47
-rw-r--r--third_party/rust/neqo-transport/tests/common/mod.rs108
-rw-r--r--third_party/rust/neqo-transport/tests/connection.rs41
-rw-r--r--third_party/rust/neqo-transport/tests/network.rs10
-rw-r--r--third_party/rust/neqo-transport/tests/retry.rs20
-rw-r--r--third_party/rust/neqo-transport/tests/server.rs93
35 files changed, 1508 insertions, 581 deletions
diff --git a/third_party/rust/neqo-transport/.cargo-checksum.json b/third_party/rust/neqo-transport/.cargo-checksum.json
index 3fc724515c..4d0f39c2b4 100644
--- a/third_party/rust/neqo-transport/.cargo-checksum.json
+++ b/third_party/rust/neqo-transport/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"28025605522925d25700870cd043f2a169acad5193b2da09c7a296bd9f73d389","benches/range_tracker.rs":"590dd1f81c92e89ce28af1efdda583d85240438bd9c4c68767286d22a299ad4b","benches/rx_stream_orderer.rs":"53a008357703251a18100521a12d8fa9443c5601ddc3cbd1b3c2899074da4c4f","benches/transfer.rs":"94eb0ec1a0a7d0a4863ddc1c6d006521e52c1f2e7f03c69428b18f7eb827d33f","src/ackrate.rs":"4bb882e1069a0707dc85338b75327e2910c93ee5f36575767a0d58c4c41c9d4f","src/addr_valid.rs":"03c0b2ff85254179c5d425b12acfdcc6b1ea5735aeb0f604b9b3603451b3ef0a","src/cc/classic_cc.rs":"aaaf3670bfaacf10c2ab77b547b4aeec2618ec6b2bb2d921000a8b795f38ea87","src/cc/cubic.rs":"24c6913cc6346e5361007221c26e8096ece51583431fc3ab9c99e4ce4b0a9f5d","src/cc/mod.rs":"e0837937c9991b37edad15cd870ea9e0623489babfccc340074dd8322e1ef401","src/cc/new_reno.rs":"25d0921005688e0f0666efd0a4931b4f8cd44363587d98e5b6404818c5d05dd4","src/cc/tests/cubic.rs":"109fc8be5efba8959e777288c32ae8f2db581fc08719f318ad676e187f655478","src/cc/tests/mod.rs":"44f8df551e742ae1037cd1cdb85b2c1334c2e5ab3c23ed63d856dbc6b8743afc","src/cc/tests/new_reno.rs":"5414e26b6c928c5f82c5eeb42f04772b05be1ec2c8ee21c2b698ce8cb32829a1","src/cid.rs":"9686a3070c593cfca846d7549863728e31211b304b9fa876220f79bff5e24173","src/connection/dump.rs":"bd4fb55785fe42f5c94f7bcc14ccf4ae377d28b691fb55dbf1139ae9412b0ea9","src/connection/idle.rs":"b3bc2ad1290e54278d8703092d135eda973eb12316d1f6dffedaffdf25e2a47e","src/connection/mod.rs":"907ded3ba14ec8ef675e1ea55c5698d6ffe023de5e81a006746d9759eb243640","src/connection/params.rs":"38e0b47c8cc5fbe602e3174d7a70df410829bc240b42f21cebd10818e606ef7c","src/connection/saved.rs":"97eb19792be3c4d721057021a43ea50a52f89a3cfa583d3d3dcf5d9144b332f5","src/connection/state.rs":"c0c4b1c15624a8762eabc8d5fa76f169f3f93945a9ee86f30fbd7714f1ac1d37","src/connection/test_internal.rs":"f3ebfe97b25c9c716d41406066295e5aff4e96a3051ef4e2b5fb258282bbc14c","src/connection/tests/ackrate.rs":"4a2b835575850ae4a14209d3e51883ecb1e69afb44ef91b5e13a5e6cb7174fab","src/connection/tests/cc.rs":"d0d6ac038572ad3dcd9e6734596eaeedc6d3222d24e31b023aaab3540d195e46","src/connection/tests/close.rs":"20bf9b044ba52517693c2bd10820ff04a8c07de01d59c8c47b4e9478aa730211","src/connection/tests/datagram.rs":"f4c85099b6a8739fb99eadd8711b02066ad80fc8341a2e5e0dae2520378af9fe","src/connection/tests/handshake.rs":"c759737ee98c7b33b2327eb7d521f45c63aed15dc8f272b7bbcc510ee8e48877","src/connection/tests/idle.rs":"f3bcb12cd79cb8eabc969ce3fb0fab4eea26d6383b81a323c0e18ca9c42cfb59","src/connection/tests/keys.rs":"55558c057beb4221245beb262076de3991dca3f2661411db61c09d21194873df","src/connection/tests/migration.rs":"624985d925236be197eee52208dbdebe35c0df5bd9d30982d6f183dfda4cbab5","src/connection/tests/mod.rs":"280077d4e69faabd2fc1fe03f754096b8b83a8e2b2438fd05b3d7cd924154489","src/connection/tests/null.rs":"38f76a4ea15e6b11634d4374cb0f2a68bd250e5d35831edfce0fa48deeaa420d","src/connection/tests/priority.rs":"dd3504f52d3fce7a96441624bc1c82c733e6bb556b9b79d24d0f4fb4efaf5a9e","src/connection/tests/recovery.rs":"7f28767f3cca2ff60e3dcfa803e12ef043486a222f54681a8faf2ea2fee564a1","src/connection/tests/resumption.rs":"1a0de0993cd325224fc79a3c094d22636d5b122ab1123d16265d4fafb23574bd","src/connection/tests/stream.rs":"8e4af07d8033a951fc57f2afda570a08843b36931eca53ba3781a5992978afb2","src/connection/tests/vn.rs":"550eb6b4d39d5960aafc70037c25a1a0f5db1232ce0ec6080b2c29a731a9574e","src/connection/tests/zerortt.rs":"67f77721e33d9fa2691c5ea3ef4a90935987541d81f0f42fbcfca31e690b352a","src/crypto.rs":"416b73c06fcc2812cc252936bcb039fc13cf0b715e7e22a54314a3f72aee743c","src/events.rs":"6e115f309c5c46f30f6223e1347bea477ada457f8bb2189ecccc6d65483318d6","src/fc.rs":"ec9de1028286870c0adf88a92e1355acf13dede8b1e91179230df3263e3827a9","src/frame.rs":"5c8e5bc21e1052367f7db31523ee422efa4278ccdfc8cd581bb44a50ee205f16","src/lib.rs":"95810fd3ec1b7da9e42f4786e1360a6e40444d69c427065856e751fd1cf411bb","src/pace.rs":"86a674ac4d086148ea297214910458c3705918bd627b996ba8bbb12f2c4bf99e","src/packet/mod.rs":"e21e594c28c568c5d21bfa1dff2903ff3fe6f9dcb98c478eeca120c535d763ef","src/packet/retry.rs":"d5f999485f21b388a7383cd011fc6e96109c1a9fb5aef79b19017df6844271ff","src/path.rs":"610e6ce83da91b785d0690995591fa4da7b5a1add3d0022eea0be5050612cee9","src/qlog.rs":"f3d3661835a29e6023014f7a0996494fc8dc1f2d062154b94346a0c21bbf6fd1","src/quic_datagrams.rs":"3d33ecb9e6e80c77b812e8260fc807352300fb2305a29b797259ae34c52b67c5","src/recovery.rs":"1dadc6717dd133007943e762231a50680087392466904c2f2e6fface084e2ba9","src/recv_stream.rs":"f21ae0bb786901bb7d726a94cb3352607b0057128beaa331808137f2f57a330b","src/rtt.rs":"4635dc0c401b78a1fd9d34da6f9bf7f6e7f5be3a57ed9716e0efc8f0f94f1e47","src/send_stream.rs":"f717f64b75e368cf5fa4ca43078aa7c1b5aff48b4f6266713e6fa7dc458328aa","src/sender.rs":"5f760988bdd6fbbd5956877a97abe7c17370dd531f68b751a9e4e4459583f87b","src/server.rs":"048aaac84e15d49fd25850294759107fe1855bbbc0481c16f8bd888d8f2a8f6d","src/stats.rs":"257ab1242ea2e6bfac0900e6c4bdad794bc67b666930323d24e022e46b9be82b","src/stream_id.rs":"fd07cbb81709a54bdb0659f676ef851cd145c004b817044ede5b21e54fdb60e4","src/streams.rs":"062b1b61edd1a76a86914f2cc1ca007c03edd9136c0c3409d960ddb805805fc6","src/tparams.rs":"10d62ac64865e0606c3c14141f9674631c610b3f042e274e110bdcef5d388491","src/tracking.rs":"f9a9aa01abc79fdd7a2cfb2c3ae342b9ab709e6a2a11076ec5c475fc89c1f598","src/version.rs":"182484ed9ecc2e17cab73cc61914a86a2d206936cab313825ae76fd37eeade77","tests/common/mod.rs":"a6584d268da0157190f8f61842a655ffe81ee68702b3e6569ae300a169080eab","tests/conn_vectors.rs":"997702f4d8b8fa3b987b33077a0eb325e968b25b61fb4703532f8d97e1d4c98c","tests/connection.rs":"d1bc28294d70a5a484eb869162115e399862742caa791749fbd6b923b702b7cc","tests/network.rs":"9e30b8610124250262fceef27d09fdecf2d6e9c3a96b1e676ff4189b9e06d5ba","tests/retry.rs":"3225b64c0c0ca918df12d94df21f6023091e72606701c1dc8c060ce3c1e09c52","tests/server.rs":"cb83de909d858950bfd75a789fc23c3c44fcdf1d965b63800b2c7b498507987f"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"8082901bf0395c450584af1084608a1568f051acf376b369a82d402d11519acc","benches/range_tracker.rs":"590dd1f81c92e89ce28af1efdda583d85240438bd9c4c68767286d22a299ad4b","benches/rx_stream_orderer.rs":"53a008357703251a18100521a12d8fa9443c5601ddc3cbd1b3c2899074da4c4f","benches/transfer.rs":"94eb0ec1a0a7d0a4863ddc1c6d006521e52c1f2e7f03c69428b18f7eb827d33f","src/ackrate.rs":"4bb882e1069a0707dc85338b75327e2910c93ee5f36575767a0d58c4c41c9d4f","src/addr_valid.rs":"03c0b2ff85254179c5d425b12acfdcc6b1ea5735aeb0f604b9b3603451b3ef0a","src/cc/classic_cc.rs":"b8007d623b07772806b676b760bcc1ccd5a37ddcd6a99f5562da39a5b03777df","src/cc/cubic.rs":"24c6913cc6346e5361007221c26e8096ece51583431fc3ab9c99e4ce4b0a9f5d","src/cc/mod.rs":"f4febe164fcbdc5881a53850eafd4011650ed394b7ec3d0977ec4803a1ec8543","src/cc/new_reno.rs":"25d0921005688e0f0666efd0a4931b4f8cd44363587d98e5b6404818c5d05dd4","src/cc/tests/cubic.rs":"87c6b95572fe5eebc54c46411f76026984d3bdaa75e356a87a16a5cc4b8dd4e1","src/cc/tests/mod.rs":"44f8df551e742ae1037cd1cdb85b2c1334c2e5ab3c23ed63d856dbc6b8743afc","src/cc/tests/new_reno.rs":"c5c39855353e99041abceba62bab2e5b58b77b92847eda1ef6f48ae91c8e4aea","src/cid.rs":"9686a3070c593cfca846d7549863728e31211b304b9fa876220f79bff5e24173","src/connection/dump.rs":"bd4fb55785fe42f5c94f7bcc14ccf4ae377d28b691fb55dbf1139ae9412b0ea9","src/connection/idle.rs":"b3bc2ad1290e54278d8703092d135eda973eb12316d1f6dffedaffdf25e2a47e","src/connection/mod.rs":"33eee67314c5f8c6dcd1085c6e39b8f0f5d1b5538b2e4bd50083dcd523a980cf","src/connection/params.rs":"38e0b47c8cc5fbe602e3174d7a70df410829bc240b42f21cebd10818e606ef7c","src/connection/saved.rs":"97eb19792be3c4d721057021a43ea50a52f89a3cfa583d3d3dcf5d9144b332f5","src/connection/state.rs":"b1d4bdda3479e7957d1949a969281ecd8a3d88f4fbaff6dcf7ebbb576759339c","src/connection/test_internal.rs":"f3ebfe97b25c9c716d41406066295e5aff4e96a3051ef4e2b5fb258282bbc14c","src/connection/tests/ackrate.rs":"4a2b835575850ae4a14209d3e51883ecb1e69afb44ef91b5e13a5e6cb7174fab","src/connection/tests/cc.rs":"d9a0f00a8f439c4ea8d4b6fa689fbde8bd283256afdd68ec4a27f6638b729704","src/connection/tests/close.rs":"5f245fd134bc0759ef0c83a6d53e0a8d5a8e58dcdf203c750ec9121940272461","src/connection/tests/datagram.rs":"908cfa6881963841d54ea2deb812f8eb217d68f149b5058036d979db1181abf0","src/connection/tests/ecn.rs":"3ff05893154fb6a895fe4453db7cc54684ba3bdf268a36b69c36c4070768d7b4","src/connection/tests/handshake.rs":"67a6f090ed89ef6c63129f7e662dc1cfff3f291711a866dff3d779caa40e51c7","src/connection/tests/idle.rs":"f3bcb12cd79cb8eabc969ce3fb0fab4eea26d6383b81a323c0e18ca9c42cfb59","src/connection/tests/keys.rs":"7c58b255e9732711e13f2a3e1daa13ac9481d8c919a32ca62e70c850845a6b38","src/connection/tests/migration.rs":"40d4feba9957de7eef7391009996016af1a3052fabc7659680b64796cf9fb8bf","src/connection/tests/mod.rs":"43b7745e9722333f7bc851c70ccdfdd1dc4da3991a4b821fac677664719e760f","src/connection/tests/null.rs":"38f76a4ea15e6b11634d4374cb0f2a68bd250e5d35831edfce0fa48deeaa420d","src/connection/tests/priority.rs":"dd3504f52d3fce7a96441624bc1c82c733e6bb556b9b79d24d0f4fb4efaf5a9e","src/connection/tests/recovery.rs":"7f28767f3cca2ff60e3dcfa803e12ef043486a222f54681a8faf2ea2fee564a1","src/connection/tests/resumption.rs":"1a0de0993cd325224fc79a3c094d22636d5b122ab1123d16265d4fafb23574bd","src/connection/tests/stream.rs":"3a6b23be63e1901ea479749d8132db86959279329121fe5d51b34c3fef4d4d05","src/connection/tests/vn.rs":"5307221790560598a7f771acf504fb153c753e005aca7370b772186f0f0c8655","src/connection/tests/zerortt.rs":"67f77721e33d9fa2691c5ea3ef4a90935987541d81f0f42fbcfca31e690b352a","src/crypto.rs":"416b73c06fcc2812cc252936bcb039fc13cf0b715e7e22a54314a3f72aee743c","src/ecn.rs":"3e9c9151dced7a1ec31e6a6d1bc5b0e4ce37cbb5cc361f5801be477c34249740","src/events.rs":"3cdd7d5496b2745626db4ceb863b5a91ae943090a43a5816a1f9bcf873fba2be","src/fc.rs":"ec9de1028286870c0adf88a92e1355acf13dede8b1e91179230df3263e3827a9","src/frame.rs":"4262717662f155e62bb29c9f0cac295bbae96076eb2d92c27052a35f979aa196","src/lib.rs":"6ed3732a020d0c9550ed295e660059b2747cbc647c24cc654cdde9de6f589a8f","src/pace.rs":"86a674ac4d086148ea297214910458c3705918bd627b996ba8bbb12f2c4bf99e","src/packet/mod.rs":"121a6dbfc3b3a9b6b5ebc94442d2d20a3148fc588dc1a1c4f9eb6c213741dca1","src/packet/retry.rs":"d5f999485f21b388a7383cd011fc6e96109c1a9fb5aef79b19017df6844271ff","src/path.rs":"8214a1683155bd0a82b53cc6bd957147286f6d4df527b5f3f640c266130122c0","src/qlog.rs":"c8ab11eca4712eba42cf26e17d07e88555e3976519a91f2d82d93afb1b420fc9","src/quic_datagrams.rs":"3d33ecb9e6e80c77b812e8260fc807352300fb2305a29b797259ae34c52b67c5","src/recovery.rs":"ad45dc9c94d716d9703601db40adc3eb4d353d5a173429d826829a2c50e52d9c","src/recv_stream.rs":"f21ae0bb786901bb7d726a94cb3352607b0057128beaa331808137f2f57a330b","src/rtt.rs":"4635dc0c401b78a1fd9d34da6f9bf7f6e7f5be3a57ed9716e0efc8f0f94f1e47","src/send_stream.rs":"ae0b6aa87649c1afee39b0e0a73bb713752f95f83199132f939e7f48ec2bbc7f","src/sender.rs":"ccde9e9a544670f403b82108ad04b5fecf8a00c1c3e26432ce0e23b2fa30436e","src/server.rs":"00b3e8d5ded84520bf30aadcb379332ad6ee2a0268a23a2893857a6c88673fd1","src/stats.rs":"257ab1242ea2e6bfac0900e6c4bdad794bc67b666930323d24e022e46b9be82b","src/stream_id.rs":"fd07cbb81709a54bdb0659f676ef851cd145c004b817044ede5b21e54fdb60e4","src/streams.rs":"062b1b61edd1a76a86914f2cc1ca007c03edd9136c0c3409d960ddb805805fc6","src/tparams.rs":"10d62ac64865e0606c3c14141f9674631c610b3f042e274e110bdcef5d388491","src/tracking.rs":"307e8cd007619e28000ce229df63ef73cfa07afb602e5399331c4624f787ad0c","src/version.rs":"182484ed9ecc2e17cab73cc61914a86a2d206936cab313825ae76fd37eeade77","tests/common/mod.rs":"7f9437d5efc38f4b9cabfece575e9168580e78e8638f46e538de58607f46ebb8","tests/conn_vectors.rs":"997702f4d8b8fa3b987b33077a0eb325e968b25b61fb4703532f8d97e1d4c98c","tests/connection.rs":"4c4d5adcde2413c105acc76567037fbeb1fe433e95e0aadf36d95e604541cb9b","tests/network.rs":"04921aa5af583e842e6d2176a898fbfea747e831bbe292b5ef8441eaf546b93a","tests/retry.rs":"a4250be3b104c754d6334784c3a6a258dd8b218645688fe3a61267ca7b773835","tests/server.rs":"e9eda467b0cb68e213f2b9ab124a35e05042f06d1aa3783deb1de1d8d1e1d7e7"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/neqo-transport/Cargo.toml b/third_party/rust/neqo-transport/Cargo.toml
index 9abdf9b984..78e551072e 100644
--- a/third_party/rust/neqo-transport/Cargo.toml
+++ b/third_party/rust/neqo-transport/Cargo.toml
@@ -11,9 +11,9 @@
[package]
edition = "2021"
-rust-version = "1.74.0"
+rust-version = "1.76.0"
name = "neqo-transport"
-version = "0.7.5"
+version = "0.7.7"
authors = ["The Neqo Authors <necko@mozilla.com>"]
homepage = "https://github.com/mozilla/neqo/"
license = "MIT OR Apache-2.0"
@@ -37,8 +37,12 @@ name = "range_tracker"
harness = false
required-features = ["bench"]
+[dependencies.enum-map]
+version = "2.7"
+default-features = false
+
[dependencies.indexmap]
-version = "1.9"
+version = "2.2"
default-features = false
[dependencies.log]
@@ -52,7 +56,7 @@ path = "../neqo-common"
path = "../neqo-crypto"
[dependencies.qlog]
-version = "0.12"
+version = "0.13"
default-features = false
[dependencies.smallvec]
@@ -64,15 +68,17 @@ version = "0.5"
features = ["html_reports"]
default-features = false
-[dev-dependencies.enum-map]
-version = "2.7"
-default-features = false
-
[dev-dependencies.test-fixture]
path = "../test-fixture"
[features]
bench = []
+build-fuzzing-corpus = [
+ "neqo-common/build-fuzzing-corpus",
+ "neqo-crypto/disable-encryption",
+ "neqo-crypto/disable-random",
+ "test-fixture/disable-random",
+]
disable-encryption = ["neqo-crypto/disable-encryption"]
[lints.clippy.pedantic]
diff --git a/third_party/rust/neqo-transport/src/cc/classic_cc.rs b/third_party/rust/neqo-transport/src/cc/classic_cc.rs
index f8bcee6722..6914e91f67 100644
--- a/third_party/rust/neqo-transport/src/cc/classic_cc.rs
+++ b/third_party/rust/neqo-transport/src/cc/classic_cc.rs
@@ -298,6 +298,14 @@ impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> {
congestion || persistent_congestion
}
+ /// Report received ECN CE mark(s) to the congestion controller as a
+ /// congestion event.
+ ///
+ /// See <https://datatracker.ietf.org/doc/html/rfc9002#section-b.7>.
+ fn on_ecn_ce_received(&mut self, largest_acked_pkt: &SentPacket) -> bool {
+ self.on_congestion_event(largest_acked_pkt)
+ }
+
fn discard(&mut self, pkt: &SentPacket) {
if pkt.cc_outstanding() {
assert!(self.bytes_in_flight >= pkt.size);
@@ -488,8 +496,8 @@ impl<T: WindowAdjustment> ClassicCongestionControl<T> {
/// Handle a congestion event.
/// Returns true if this was a true congestion event.
fn on_congestion_event(&mut self, last_packet: &SentPacket) -> bool {
- // Start a new congestion event if lost packet was sent after the start
- // of the previous congestion recovery period.
+ // Start a new congestion event if lost or ECN CE marked packet was sent
+ // after the start of the previous congestion recovery period.
if !self.after_recovery_start(last_packet) {
return false;
}
@@ -538,7 +546,7 @@ impl<T: WindowAdjustment> ClassicCongestionControl<T> {
mod tests {
use std::time::{Duration, Instant};
- use neqo_common::qinfo;
+ use neqo_common::{qinfo, IpTosEcn};
use test_fixture::now;
use super::{
@@ -582,6 +590,7 @@ mod tests {
SentPacket::new(
PacketType::Short,
pn,
+ IpTosEcn::default(),
now() + t,
ack_eliciting,
Vec::new(),
@@ -795,6 +804,7 @@ mod tests {
SentPacket::new(
PacketType::Short,
u64::try_from(i).unwrap(),
+ IpTosEcn::default(),
by_pto(t),
true,
Vec::new(),
@@ -915,6 +925,7 @@ mod tests {
lost[0] = SentPacket::new(
lost[0].pt,
lost[0].pn,
+ lost[0].ecn_mark,
lost[0].time_sent,
false,
Vec::new(),
@@ -1015,11 +1026,12 @@ mod tests {
for _ in 0..packet_burst_size {
let p = SentPacket::new(
PacketType::Short,
- next_pn, // pn
- now, // time sent
- true, // ack eliciting
- Vec::new(), // tokens
- MAX_DATAGRAM_SIZE, // size
+ next_pn,
+ IpTosEcn::default(),
+ now,
+ true,
+ Vec::new(),
+ MAX_DATAGRAM_SIZE,
);
next_pn += 1;
cc.on_packet_sent(&p);
@@ -1039,11 +1051,12 @@ mod tests {
for _ in 0..ABOVE_APP_LIMIT_PKTS {
let p = SentPacket::new(
PacketType::Short,
- next_pn, // pn
- now, // time sent
- true, // ack eliciting
- Vec::new(), // tokens
- MAX_DATAGRAM_SIZE, // size
+ next_pn,
+ IpTosEcn::default(),
+ now,
+ true,
+ Vec::new(),
+ MAX_DATAGRAM_SIZE,
);
next_pn += 1;
cc.on_packet_sent(&p);
@@ -1082,11 +1095,12 @@ mod tests {
let p_lost = SentPacket::new(
PacketType::Short,
- 1, // pn
- now, // time sent
- true, // ack eliciting
- Vec::new(), // tokens
- MAX_DATAGRAM_SIZE, // size
+ 1,
+ IpTosEcn::default(),
+ now,
+ true,
+ Vec::new(),
+ MAX_DATAGRAM_SIZE,
);
cc.on_packet_sent(&p_lost);
cwnd_is_default(&cc);
@@ -1095,11 +1109,12 @@ mod tests {
cwnd_is_halved(&cc);
let p_not_lost = SentPacket::new(
PacketType::Short,
- 2, // pn
- now, // time sent
- true, // ack eliciting
- Vec::new(), // tokens
- MAX_DATAGRAM_SIZE, // size
+ 2,
+ IpTosEcn::default(),
+ now,
+ true,
+ Vec::new(),
+ MAX_DATAGRAM_SIZE,
);
cc.on_packet_sent(&p_not_lost);
now += RTT;
@@ -1118,11 +1133,12 @@ mod tests {
for _ in 0..packet_burst_size {
let p = SentPacket::new(
PacketType::Short,
- next_pn, // pn
- now, // time sent
- true, // ack eliciting
- Vec::new(), // tokens
- MAX_DATAGRAM_SIZE, // size
+ next_pn,
+ IpTosEcn::default(),
+ now,
+ true,
+ Vec::new(),
+ MAX_DATAGRAM_SIZE,
);
next_pn += 1;
cc.on_packet_sent(&p);
@@ -1148,11 +1164,12 @@ mod tests {
for _ in 0..ABOVE_APP_LIMIT_PKTS {
let p = SentPacket::new(
PacketType::Short,
- next_pn, // pn
- now, // time sent
- true, // ack eliciting
- Vec::new(), // tokens
- MAX_DATAGRAM_SIZE, // size
+ next_pn,
+ IpTosEcn::default(),
+ now,
+ true,
+ Vec::new(),
+ MAX_DATAGRAM_SIZE,
);
next_pn += 1;
cc.on_packet_sent(&p);
@@ -1180,4 +1197,26 @@ mod tests {
last_acked_bytes = cc.acked_bytes;
}
}
+
+ #[test]
+ fn ecn_ce() {
+ let mut cc = ClassicCongestionControl::new(NewReno::default());
+ let p_ce = SentPacket::new(
+ PacketType::Short,
+ 1,
+ IpTosEcn::default(),
+ now(),
+ true,
+ Vec::new(),
+ MAX_DATAGRAM_SIZE,
+ );
+ cc.on_packet_sent(&p_ce);
+ cwnd_is_default(&cc);
+ assert_eq!(cc.state, State::SlowStart);
+
+ // Signal congestion (ECN CE) and thus change state to recovery start.
+ cc.on_ecn_ce_received(&p_ce);
+ cwnd_is_halved(&cc);
+ assert_eq!(cc.state, State::RecoveryStart);
+ }
}
diff --git a/third_party/rust/neqo-transport/src/cc/mod.rs b/third_party/rust/neqo-transport/src/cc/mod.rs
index 486d15e67e..2adffbc0c4 100644
--- a/third_party/rust/neqo-transport/src/cc/mod.rs
+++ b/third_party/rust/neqo-transport/src/cc/mod.rs
@@ -53,6 +53,9 @@ pub trait CongestionControl: Display + Debug {
lost_packets: &[SentPacket],
) -> bool;
+ /// Returns true if the congestion window was reduced.
+ fn on_ecn_ce_received(&mut self, largest_acked_pkt: &SentPacket) -> bool;
+
#[must_use]
fn recovery_packet(&self) -> bool;
diff --git a/third_party/rust/neqo-transport/src/cc/tests/cubic.rs b/third_party/rust/neqo-transport/src/cc/tests/cubic.rs
index 2e0200fd6d..8ff591cb47 100644
--- a/third_party/rust/neqo-transport/src/cc/tests/cubic.rs
+++ b/third_party/rust/neqo-transport/src/cc/tests/cubic.rs
@@ -12,6 +12,7 @@ use std::{
time::{Duration, Instant},
};
+use neqo_common::IpTosEcn;
use test_fixture::now;
use crate::{
@@ -41,11 +42,12 @@ fn fill_cwnd(cc: &mut ClassicCongestionControl<Cubic>, mut next_pn: u64, now: In
while cc.bytes_in_flight() < cc.cwnd() {
let sent = SentPacket::new(
PacketType::Short,
- next_pn, // pn
- now, // time sent
- true, // ack eliciting
- Vec::new(), // tokens
- MAX_DATAGRAM_SIZE, // size
+ next_pn,
+ IpTosEcn::default(),
+ now,
+ true,
+ Vec::new(),
+ MAX_DATAGRAM_SIZE,
);
cc.on_packet_sent(&sent);
next_pn += 1;
@@ -56,11 +58,12 @@ fn fill_cwnd(cc: &mut ClassicCongestionControl<Cubic>, mut next_pn: u64, now: In
fn ack_packet(cc: &mut ClassicCongestionControl<Cubic>, pn: u64, now: Instant) {
let acked = SentPacket::new(
PacketType::Short,
- pn, // pn
- now, // time sent
- true, // ack eliciting
- Vec::new(), // tokens
- MAX_DATAGRAM_SIZE, // size
+ pn,
+ IpTosEcn::default(),
+ now,
+ true,
+ Vec::new(),
+ MAX_DATAGRAM_SIZE,
);
cc.on_packets_acked(&[acked], &RTT_ESTIMATE, now);
}
@@ -69,11 +72,12 @@ fn packet_lost(cc: &mut ClassicCongestionControl<Cubic>, pn: u64) {
const PTO: Duration = Duration::from_millis(120);
let p_lost = SentPacket::new(
PacketType::Short,
- pn, // pn
- now(), // time sent
- true, // ack eliciting
- Vec::new(), // tokens
- MAX_DATAGRAM_SIZE, // size
+ pn,
+ IpTosEcn::default(),
+ now(),
+ true,
+ Vec::new(),
+ MAX_DATAGRAM_SIZE,
);
cc.on_packets_lost(None, None, PTO, &[p_lost]);
}
diff --git a/third_party/rust/neqo-transport/src/cc/tests/new_reno.rs b/third_party/rust/neqo-transport/src/cc/tests/new_reno.rs
index 4cc20de5a7..0cc560bf2b 100644
--- a/third_party/rust/neqo-transport/src/cc/tests/new_reno.rs
+++ b/third_party/rust/neqo-transport/src/cc/tests/new_reno.rs
@@ -8,6 +8,7 @@
use std::time::Duration;
+use neqo_common::IpTosEcn;
use test_fixture::now;
use crate::{
@@ -44,59 +45,66 @@ fn issue_876() {
let sent_packets = &[
SentPacket::new(
PacketType::Short,
- 1, // pn
- time_before, // time sent
- true, // ack eliciting
- Vec::new(), // tokens
- MAX_DATAGRAM_SIZE - 1, // size
+ 1,
+ IpTosEcn::default(),
+ time_before,
+ true,
+ Vec::new(),
+ MAX_DATAGRAM_SIZE - 1,
),
SentPacket::new(
PacketType::Short,
- 2, // pn
- time_before, // time sent
- true, // ack eliciting
- Vec::new(), // tokens
- MAX_DATAGRAM_SIZE - 2, // size
+ 2,
+ IpTosEcn::default(),
+ time_before,
+ true,
+ Vec::new(),
+ MAX_DATAGRAM_SIZE - 2,
),
SentPacket::new(
PacketType::Short,
- 3, // pn
- time_before, // time sent
- true, // ack eliciting
- Vec::new(), // tokens
- MAX_DATAGRAM_SIZE, // size
+ 3,
+ IpTosEcn::default(),
+ time_before,
+ true,
+ Vec::new(),
+ MAX_DATAGRAM_SIZE,
),
SentPacket::new(
PacketType::Short,
- 4, // pn
- time_before, // time sent
- true, // ack eliciting
- Vec::new(), // tokens
- MAX_DATAGRAM_SIZE, // size
+ 4,
+ IpTosEcn::default(),
+ time_before,
+ true,
+ Vec::new(),
+ MAX_DATAGRAM_SIZE,
),
SentPacket::new(
PacketType::Short,
- 5, // pn
- time_before, // time sent
- true, // ack eliciting
- Vec::new(), // tokens
- MAX_DATAGRAM_SIZE, // size
+ 5,
+ IpTosEcn::default(),
+ time_before,
+ true,
+ Vec::new(),
+ MAX_DATAGRAM_SIZE,
),
SentPacket::new(
PacketType::Short,
- 6, // pn
- time_before, // time sent
- true, // ack eliciting
- Vec::new(), // tokens
- MAX_DATAGRAM_SIZE, // size
+ 6,
+ IpTosEcn::default(),
+ time_before,
+ true,
+ Vec::new(),
+ MAX_DATAGRAM_SIZE,
),
SentPacket::new(
PacketType::Short,
- 7, // pn
- time_after, // time sent
- true, // ack eliciting
- Vec::new(), // tokens
- MAX_DATAGRAM_SIZE - 3, // size
+ 7,
+ IpTosEcn::default(),
+ time_after,
+ true,
+ Vec::new(),
+ MAX_DATAGRAM_SIZE - 3,
),
];
@@ -146,11 +154,12 @@ fn issue_1465() {
let mut next_packet = |now| {
let p = SentPacket::new(
PacketType::Short,
- pn, // pn
- now, // time_sent
- true, // ack eliciting
- Vec::new(), // tokens
- MAX_DATAGRAM_SIZE, // size
+ pn,
+ IpTosEcn::default(),
+ now,
+ true,
+ Vec::new(),
+ MAX_DATAGRAM_SIZE,
);
pn += 1;
p
diff --git a/third_party/rust/neqo-transport/src/connection/mod.rs b/third_party/rust/neqo-transport/src/connection/mod.rs
index 8522507a69..f955381414 100644
--- a/third_party/rust/neqo-transport/src/connection/mod.rs
+++ b/third_party/rust/neqo-transport/src/connection/mod.rs
@@ -19,7 +19,7 @@ use std::{
use neqo_common::{
event::Provider as EventProvider, hex, hex_snip_middle, hrtime, qdebug, qerror, qinfo,
- qlog::NeqoQlog, qtrace, qwarn, Datagram, Decoder, Encoder, IpTos, Role,
+ qlog::NeqoQlog, qtrace, qwarn, Datagram, Decoder, Encoder, Role,
};
use neqo_crypto::{
agent::CertificateInfo, Agent, AntiReplay, AuthenticationStatus, Cipher, Client, Group,
@@ -35,6 +35,7 @@ use crate::{
ConnectionIdRef, ConnectionIdStore, LOCAL_ACTIVE_CID_LIMIT,
},
crypto::{Crypto, CryptoDxState, CryptoSpace},
+ ecn::EcnCount,
events::{ConnectionEvent, ConnectionEvents, OutgoingDatagramOutcome},
frame::{
CloseError, Frame, FrameType, FRAME_TYPE_CONNECTION_CLOSE_APPLICATION,
@@ -46,7 +47,7 @@ use crate::{
quic_datagrams::{DatagramTracking, QuicDatagrams},
recovery::{LossRecovery, RecoveryToken, SendProfile},
recv_stream::RecvStreamStats,
- rtt::GRANULARITY,
+ rtt::{RttEstimate, GRANULARITY},
send_stream::SendStream,
stats::{Stats, StatsCell},
stream_id::StreamType,
@@ -55,9 +56,9 @@ use crate::{
self, TransportParameter, TransportParameterId, TransportParameters,
TransportParametersHandler,
},
- tracking::{AckTracker, PacketNumberSpace, SentPacket},
+ tracking::{AckTracker, PacketNumberSpace, RecvdPackets, SentPacket},
version::{Version, WireVersion},
- AppError, ConnectionError, Error, Res, StreamId,
+ AppError, CloseReason, Error, Res, StreamId,
};
mod dump;
@@ -291,7 +292,7 @@ impl Debug for Connection {
"{:?} Connection: {:?} {:?}",
self.role,
self.state,
- self.paths.primary_fallible()
+ self.paths.primary()
)
}
}
@@ -591,7 +592,11 @@ impl Connection {
fn make_resumption_token(&mut self) -> ResumptionToken {
debug_assert_eq!(self.role, Role::Client);
debug_assert!(self.crypto.has_resumption_token());
- let rtt = self.paths.primary().borrow().rtt().estimate();
+ let rtt = self.paths.primary().map_or_else(
+ || RttEstimate::default().estimate(),
+ |p| p.borrow().rtt().estimate(),
+ );
+
self.crypto
.create_resumption_token(
self.new_token.take_token(),
@@ -610,11 +615,10 @@ impl Connection {
/// a value of this approximate order. Don't use this for loss recovery,
/// only use it where a more precise value is not important.
fn pto(&self) -> Duration {
- self.paths
- .primary()
- .borrow()
- .rtt()
- .pto(PacketNumberSpace::ApplicationData)
+ self.paths.primary().map_or_else(
+ || RttEstimate::default().pto(PacketNumberSpace::ApplicationData),
+ |p| p.borrow().rtt().pto(PacketNumberSpace::ApplicationData),
+ )
}
fn create_resumption_token(&mut self, now: Instant) {
@@ -746,7 +750,12 @@ impl Connection {
if !init_token.is_empty() {
self.address_validation = AddressValidationInfo::NewToken(init_token.to_vec());
}
- self.paths.primary().borrow_mut().rtt_mut().set_initial(rtt);
+ self.paths
+ .primary()
+ .ok_or(Error::InternalError)?
+ .borrow_mut()
+ .rtt_mut()
+ .set_initial(rtt);
self.set_initial_limits();
// Start up TLS, which has the effect of setting up all the necessary
// state for 0-RTT. This only stages the CRYPTO frames.
@@ -786,7 +795,7 @@ impl Connection {
// If we are able, also send a NEW_TOKEN frame.
// This should be recording all remote addresses that are valid,
// but there are just 0 or 1 in the current implementation.
- if let Some(path) = self.paths.primary_fallible() {
+ if let Some(path) = self.paths.primary() {
if let Some(token) = self
.address_validation
.generate_new_token(path.borrow().remote_address(), now)
@@ -858,7 +867,7 @@ impl Connection {
#[must_use]
pub fn stats(&self) -> Stats {
let mut v = self.stats.borrow().clone();
- if let Some(p) = self.paths.primary_fallible() {
+ if let Some(p) = self.paths.primary() {
let p = p.borrow();
v.rtt = p.rtt().estimate();
v.rttvar = p.rtt().rttvar();
@@ -880,7 +889,7 @@ impl Connection {
let msg = format!("{v:?}");
#[cfg(not(debug_assertions))]
let msg = "";
- let error = ConnectionError::Transport(v.clone());
+ let error = CloseReason::Transport(v.clone());
match &self.state {
State::Closing { error: err, .. }
| State::Draining { error: err, .. }
@@ -895,14 +904,14 @@ impl Connection {
State::WaitInitial => {
// We don't have any state yet, so don't bother with
// the closing state, just send one CONNECTION_CLOSE.
- if let Some(path) = path.or_else(|| self.paths.primary_fallible()) {
+ if let Some(path) = path.or_else(|| self.paths.primary()) {
self.state_signaling
.close(path, error.clone(), frame_type, msg);
}
self.set_state(State::Closed(error));
}
_ => {
- if let Some(path) = path.or_else(|| self.paths.primary_fallible()) {
+ if let Some(path) = path.or_else(|| self.paths.primary()) {
self.state_signaling
.close(path, error.clone(), frame_type, msg);
if matches!(v, Error::KeysExhausted) {
@@ -951,9 +960,7 @@ impl Connection {
let pto = self.pto();
if self.idle_timeout.expired(now, pto) {
qinfo!([self], "idle timeout expired");
- self.set_state(State::Closed(ConnectionError::Transport(
- Error::IdleTimeout,
- )));
+ self.set_state(State::Closed(CloseReason::Transport(Error::IdleTimeout)));
return;
}
@@ -962,9 +969,11 @@ impl Connection {
let res = self.crypto.states.check_key_update(now);
self.absorb_error(now, res);
- let lost = self.loss_recovery.timeout(&self.paths.primary(), now);
- self.handle_lost_packets(&lost);
- qlog::packets_lost(&mut self.qlog, &lost);
+ if let Some(path) = self.paths.primary() {
+ let lost = self.loss_recovery.timeout(&path, now);
+ self.handle_lost_packets(&lost);
+ qlog::packets_lost(&mut self.qlog, &lost);
+ }
if self.release_resumption_token_timer.is_some() {
self.create_resumption_token(now);
@@ -1014,7 +1023,7 @@ impl Connection {
delays.push(ack_time);
}
- if let Some(p) = self.paths.primary_fallible() {
+ if let Some(p) = self.paths.primary() {
let path = p.borrow();
let rtt = path.rtt();
let pto = rtt.pto(PacketNumberSpace::ApplicationData);
@@ -1102,7 +1111,15 @@ impl Connection {
self.input(d, now, now);
self.process_saved(now);
}
- self.process_output(now)
+ #[allow(clippy::let_and_return)]
+ let output = self.process_output(now);
+ #[cfg(all(feature = "build-fuzzing-corpus", test))]
+ if self.test_frame_writer.is_none() {
+ if let Some(d) = output.clone().dgram() {
+ neqo_common::write_item_to_fuzzing_corpus("packet", &d);
+ }
+ }
+ output
}
fn handle_retry(&mut self, packet: &PublicPacket, now: Instant) {
@@ -1123,7 +1140,13 @@ impl Connection {
}
// At this point, we should only have the connection ID that we generated.
// Update to the one that the server prefers.
- let path = self.paths.primary();
+ let Some(path) = self.paths.primary() else {
+ self.stats
+ .borrow_mut()
+ .pkt_dropped("Retry without an existing path");
+ return;
+ };
+
path.borrow_mut().set_remote_cid(packet.scid());
let retry_scid = ConnectionId::from(packet.scid());
@@ -1151,8 +1174,9 @@ impl Connection {
fn discard_keys(&mut self, space: PacketNumberSpace, now: Instant) {
if self.crypto.discard(space) {
qdebug!([self], "Drop packet number space {}", space);
- let primary = self.paths.primary();
- self.loss_recovery.discard(&primary, space, now);
+ if let Some(path) = self.paths.primary() {
+ self.loss_recovery.discard(&path, space, now);
+ }
self.acks.drop_space(space);
}
}
@@ -1180,7 +1204,7 @@ impl Connection {
qdebug!([self], "Stateless reset: {}", hex(&d[d.len() - 16..]));
self.state_signaling.reset();
self.set_state(State::Draining {
- error: ConnectionError::Transport(Error::StatelessReset),
+ error: CloseReason::Transport(Error::StatelessReset),
timeout: self.get_closing_period_time(now),
});
Err(Error::StatelessReset)
@@ -1227,8 +1251,9 @@ impl Connection {
assert_ne!(self.version, version);
qinfo!([self], "Version negotiation: trying {:?}", version);
- let local_addr = self.paths.primary().borrow().local_address();
- let remote_addr = self.paths.primary().borrow().remote_address();
+ let path = self.paths.primary().ok_or(Error::NoAvailablePath)?;
+ let local_addr = path.borrow().local_address();
+ let remote_addr = path.borrow().remote_address();
let conn_params = self
.conn_params
.clone()
@@ -1256,7 +1281,7 @@ impl Connection {
} else {
qinfo!([self], "Version negotiation: failed with {:?}", supported);
// This error goes straight to closed.
- self.set_state(State::Closed(ConnectionError::Transport(
+ self.set_state(State::Closed(CloseReason::Transport(
Error::VersionNegotiation,
)));
Err(Error::VersionNegotiation)
@@ -1417,6 +1442,13 @@ impl Connection {
migrate: bool,
now: Instant,
) {
+ let space = PacketNumberSpace::from(packet.packet_type());
+ if let Some(space) = self.acks.get_mut(space) {
+ *space.ecn_marks() += d.tos().into();
+ } else {
+ qtrace!("Not tracking ECN for dropped packet number space");
+ }
+
if self.state == State::WaitInitial {
self.start_handshake(path, packet, now);
}
@@ -1491,6 +1523,16 @@ impl Connection {
d.tos(),
);
+ #[cfg(feature = "build-fuzzing-corpus")]
+ if packet.packet_type() == PacketType::Initial {
+ let target = if self.role == Role::Client {
+ "server_initial"
+ } else {
+ "client_initial"
+ };
+ neqo_common::write_item_to_fuzzing_corpus(target, &payload[..]);
+ }
+
qlog::packet_received(&mut self.qlog, &packet, &payload);
let space = PacketNumberSpace::from(payload.packet_type());
if self.acks.get_mut(space).unwrap().is_duplicate(payload.pn()) {
@@ -1562,7 +1604,11 @@ impl Connection {
let mut probing = true;
let mut d = Decoder::from(&packet[..]);
while d.remaining() > 0 {
+ #[cfg(feature = "build-fuzzing-corpus")]
+ let pos = d.offset();
let f = Frame::decode(&mut d)?;
+ #[cfg(feature = "build-fuzzing-corpus")]
+ neqo_common::write_item_to_fuzzing_corpus("frame", &packet[pos..d.offset()]);
ack_eliciting |= f.ack_eliciting();
probing &= f.path_probing();
let t = f.get_type();
@@ -1623,10 +1669,15 @@ impl Connection {
if let Some(cid) = self.connection_ids.next() {
self.paths.make_permanent(path, None, cid);
Ok(())
- } else if self.paths.primary().borrow().remote_cid().is_empty() {
- self.paths
- .make_permanent(path, None, ConnectionIdEntry::empty_remote());
- Ok(())
+ } else if let Some(primary) = self.paths.primary() {
+ if primary.borrow().remote_cid().is_empty() {
+ self.paths
+ .make_permanent(path, None, ConnectionIdEntry::empty_remote());
+ Ok(())
+ } else {
+ qtrace!([self], "Unable to make path permanent: {}", path.borrow());
+ Err(Error::InvalidMigration)
+ }
} else {
qtrace!([self], "Unable to make path permanent: {}", path.borrow());
Err(Error::InvalidMigration)
@@ -1719,8 +1770,10 @@ impl Connection {
// Pointless migration is pointless.
return Err(Error::InvalidMigration);
}
- let local = local.unwrap_or_else(|| self.paths.primary().borrow().local_address());
- let remote = remote.unwrap_or_else(|| self.paths.primary().borrow().remote_address());
+
+ let path = self.paths.primary().ok_or(Error::InvalidMigration)?;
+ let local = local.unwrap_or_else(|| path.borrow().local_address());
+ let remote = remote.unwrap_or_else(|| path.borrow().remote_address());
if mem::discriminant(&local.ip()) != mem::discriminant(&remote.ip()) {
// Can't mix address families.
@@ -1773,7 +1826,12 @@ impl Connection {
// has to use the existing address. So only pay attention to a preferred
// address from the same family as is currently in use. More thought will
// be needed to work out how to get addresses from a different family.
- let prev = self.paths.primary().borrow().remote_address();
+ let prev = self
+ .paths
+ .primary()
+ .ok_or(Error::NoAvailablePath)?
+ .borrow()
+ .remote_address();
let remote = match prev.ip() {
IpAddr::V4(_) => addr.ipv4().map(SocketAddr::V4),
IpAddr::V6(_) => addr.ipv6().map(SocketAddr::V6),
@@ -1937,20 +1995,15 @@ impl Connection {
}
}
- self.streams
- .write_frames(TransmissionPriority::Critical, builder, tokens, frame_stats);
- if builder.is_full() {
- return;
- }
-
- self.streams.write_frames(
+ for prio in [
+ TransmissionPriority::Critical,
TransmissionPriority::Important,
- builder,
- tokens,
- frame_stats,
- );
- if builder.is_full() {
- return;
+ ] {
+ self.streams
+ .write_frames(prio, builder, tokens, frame_stats);
+ if builder.is_full() {
+ return;
+ }
}
// NEW_CONNECTION_ID, RETIRE_CONNECTION_ID, and ACK_FREQUENCY.
@@ -1958,21 +2011,18 @@ impl Connection {
if builder.is_full() {
return;
}
- self.paths.write_frames(builder, tokens, frame_stats);
- if builder.is_full() {
- return;
- }
- self.streams
- .write_frames(TransmissionPriority::High, builder, tokens, frame_stats);
+ self.paths.write_frames(builder, tokens, frame_stats);
if builder.is_full() {
return;
}
- self.streams
- .write_frames(TransmissionPriority::Normal, builder, tokens, frame_stats);
- if builder.is_full() {
- return;
+ for prio in [TransmissionPriority::High, TransmissionPriority::Normal] {
+ self.streams
+ .write_frames(prio, builder, tokens, &mut stats.frame_tx);
+ if builder.is_full() {
+ return;
+ }
}
// Datagrams are best-effort and unreliable. Let streams starve them for now.
@@ -1981,9 +2031,9 @@ impl Connection {
return;
}
- let frame_stats = &mut stats.frame_tx;
// CRYPTO here only includes NewSessionTicket, plus NEW_TOKEN.
// Both of these are only used for resumption and so can be relatively low priority.
+ let frame_stats = &mut stats.frame_tx;
self.crypto.write_frame(
PacketNumberSpace::ApplicationData,
builder,
@@ -1993,6 +2043,7 @@ impl Connection {
if builder.is_full() {
return;
}
+
self.new_token.write_frames(builder, tokens, frame_stats);
if builder.is_full() {
return;
@@ -2002,10 +2053,8 @@ impl Connection {
.write_frames(TransmissionPriority::Low, builder, tokens, frame_stats);
#[cfg(test)]
- {
- if let Some(w) = &mut self.test_frame_writer {
- w.write_frames(builder);
- }
+ if let Some(w) = &mut self.test_frame_writer {
+ w.write_frames(builder);
}
}
@@ -2138,6 +2187,40 @@ impl Connection {
(tokens, ack_eliciting, padded)
}
+ fn write_closing_frames(
+ &mut self,
+ close: &ClosingFrame,
+ builder: &mut PacketBuilder,
+ space: PacketNumberSpace,
+ now: Instant,
+ path: &PathRef,
+ tokens: &mut Vec<RecoveryToken>,
+ ) {
+ if builder.remaining() > ClosingFrame::MIN_LENGTH + RecvdPackets::USEFUL_ACK_LEN {
+ // Include an ACK frame with the CONNECTION_CLOSE.
+ let limit = builder.limit();
+ builder.set_limit(limit - ClosingFrame::MIN_LENGTH);
+ self.acks.immediate_ack(now);
+ self.acks.write_frame(
+ space,
+ now,
+ path.borrow().rtt().estimate(),
+ builder,
+ tokens,
+ &mut self.stats.borrow_mut().frame_tx,
+ );
+ builder.set_limit(limit);
+ }
+ // CloseReason::Application is only allowed at 1RTT.
+ let sanitized = if space == PacketNumberSpace::ApplicationData {
+ None
+ } else {
+ close.sanitize()
+ };
+ sanitized.as_ref().unwrap_or(close).write_frame(builder);
+ self.stats.borrow_mut().frame_tx.connection_close += 1;
+ }
+
/// Build a datagram, possibly from multiple packets (for different PN
/// spaces) and each containing 1+ frames.
#[allow(clippy::too_many_lines)] // Yeah, that's just the way it is.
@@ -2201,17 +2284,7 @@ impl Connection {
let payload_start = builder.len();
let (mut tokens, mut ack_eliciting, mut padded) = (Vec::new(), false, false);
if let Some(ref close) = closing_frame {
- // ConnectionError::Application is only allowed at 1RTT.
- let sanitized = if *space == PacketNumberSpace::ApplicationData {
- None
- } else {
- close.sanitize()
- };
- sanitized
- .as_ref()
- .unwrap_or(close)
- .write_frame(&mut builder);
- self.stats.borrow_mut().frame_tx.connection_close += 1;
+ self.write_closing_frames(close, &mut builder, *space, now, path, &mut tokens);
} else {
(tokens, ack_eliciting, padded) =
self.write_frames(path, *space, &profile, &mut builder, now);
@@ -2229,7 +2302,7 @@ impl Connection {
pt,
pn,
&builder.as_ref()[payload_start..],
- IpTos::default(), // TODO: set from path
+ path.borrow().tos(),
);
qlog::packet_sent(
&mut self.qlog,
@@ -2251,6 +2324,7 @@ impl Connection {
let sent = SentPacket::new(
pt,
pn,
+ path.borrow().tos().into(),
now,
ack_eliciting,
tokens,
@@ -2303,7 +2377,7 @@ impl Connection {
self.loss_recovery.on_packet_sent(path, initial);
}
path.borrow_mut().add_sent(packets.len());
- Ok(SendOption::Yes(path.borrow().datagram(packets)))
+ Ok(SendOption::Yes(path.borrow_mut().datagram(packets)))
}
}
@@ -2330,7 +2404,9 @@ impl Connection {
fn client_start(&mut self, now: Instant) -> Res<()> {
qdebug!([self], "client_start");
debug_assert_eq!(self.role, Role::Client);
- qlog::client_connection_started(&mut self.qlog, &self.paths.primary());
+ if let Some(path) = self.paths.primary() {
+ qlog::client_connection_started(&mut self.qlog, &path);
+ }
qlog::client_version_information_initiated(&mut self.qlog, self.conn_params.get_versions());
self.handshake(now, self.version, PacketNumberSpace::Initial, None)?;
@@ -2351,9 +2427,9 @@ impl Connection {
/// Close the connection.
pub fn close(&mut self, now: Instant, app_error: AppError, msg: impl AsRef<str>) {
- let error = ConnectionError::Application(app_error);
+ let error = CloseReason::Application(app_error);
let timeout = self.get_closing_period_time(now);
- if let Some(path) = self.paths.primary_fallible() {
+ if let Some(path) = self.paths.primary() {
self.state_signaling.close(path, error.clone(), 0, msg);
self.set_state(State::Closing { error, timeout });
} else {
@@ -2411,10 +2487,8 @@ impl Connection {
// That's OK, they can try guessing this.
ConnectionIdEntry::random_srt()
};
- self.paths
- .primary()
- .borrow_mut()
- .set_reset_token(reset_token);
+ let path = self.paths.primary().ok_or(Error::NoAvailablePath)?;
+ path.borrow_mut().set_reset_token(reset_token);
let max_ad = Duration::from_millis(remote.get_integer(tparams::MAX_ACK_DELAY));
let min_ad = if remote.has_value(tparams::MIN_ACK_DELAY) {
@@ -2426,11 +2500,8 @@ impl Connection {
} else {
None
};
- self.paths.primary().borrow_mut().set_ack_delay(
- max_ad,
- min_ad,
- self.conn_params.get_ack_ratio(),
- );
+ path.borrow_mut()
+ .set_ack_delay(max_ad, min_ad, self.conn_params.get_ack_ratio());
let max_active_cids = remote.get_integer(tparams::ACTIVE_CONNECTION_ID_LIMIT);
self.cid_manager.set_limit(max_active_cids);
@@ -2673,10 +2744,18 @@ impl Connection {
ack_delay,
first_ack_range,
ack_ranges,
+ ecn_count,
} => {
let ranges =
Frame::decode_ack_frame(largest_acknowledged, first_ack_range, &ack_ranges)?;
- self.handle_ack(space, largest_acknowledged, ranges, ack_delay, now);
+ self.handle_ack(
+ space,
+ largest_acknowledged,
+ ranges,
+ ecn_count,
+ ack_delay,
+ now,
+ );
}
Frame::Crypto { offset, data } => {
qtrace!(
@@ -2747,7 +2826,6 @@ impl Connection {
reason_phrase,
} => {
self.stats.borrow_mut().frame_rx.connection_close += 1;
- let reason_phrase = String::from_utf8_lossy(&reason_phrase);
qinfo!(
[self],
"ConnectionClose received. Error code: {:?} frame type {:x} reason {}",
@@ -2768,7 +2846,7 @@ impl Connection {
FRAME_TYPE_CONNECTION_CLOSE_TRANSPORT,
)
};
- let error = ConnectionError::Transport(detail);
+ let error = CloseReason::Transport(detail);
self.state_signaling
.drain(Rc::clone(path), error.clone(), frame_type, "");
self.set_state(State::Draining {
@@ -2853,6 +2931,7 @@ impl Connection {
space: PacketNumberSpace,
largest_acknowledged: u64,
ack_ranges: R,
+ ack_ecn: Option<EcnCount>,
ack_delay: u64,
now: Instant,
) where
@@ -2861,11 +2940,15 @@ impl Connection {
{
qdebug!([self], "Rx ACK space={}, ranges={:?}", space, ack_ranges);
+ let Some(path) = self.paths.primary() else {
+ return;
+ };
let (acked_packets, lost_packets) = self.loss_recovery.on_ack_received(
- &self.paths.primary(),
+ &path,
space,
largest_acknowledged,
ack_ranges,
+ ack_ecn,
self.decode_ack_delay(ack_delay),
now,
);
@@ -2903,8 +2986,10 @@ impl Connection {
qdebug!([self], "0-RTT rejected");
// Tell 0-RTT packets that they were "lost".
- let dropped = self.loss_recovery.drop_0rtt(&self.paths.primary(), now);
- self.handle_lost_packets(&dropped);
+ if let Some(path) = self.paths.primary() {
+ let dropped = self.loss_recovery.drop_0rtt(&path, now);
+ self.handle_lost_packets(&dropped);
+ }
self.streams.zero_rtt_rejected();
@@ -2923,7 +3008,7 @@ impl Connection {
// Remove the randomized client CID from the list of acceptable CIDs.
self.cid_manager.remove_odcid();
// Mark the path as validated, if it isn't already.
- let path = self.paths.primary();
+ let path = self.paths.primary().ok_or(Error::NoAvailablePath)?;
path.borrow_mut().set_valid(now);
// Generate a qlog event that the server connection started.
qlog::server_connection_started(&mut self.qlog, &path);
@@ -3191,7 +3276,7 @@ impl Connection {
else {
return Err(Error::NotAvailable);
};
- let path = self.paths.primary_fallible().ok_or(Error::NotAvailable)?;
+ let path = self.paths.primary().ok_or(Error::NotAvailable)?;
let mtu = path.borrow().mtu();
let encoder = Encoder::with_capacity(mtu);
diff --git a/third_party/rust/neqo-transport/src/connection/state.rs b/third_party/rust/neqo-transport/src/connection/state.rs
index cc2f6e30d2..e76f937938 100644
--- a/third_party/rust/neqo-transport/src/connection/state.rs
+++ b/third_party/rust/neqo-transport/src/connection/state.rs
@@ -21,7 +21,7 @@ use crate::{
packet::PacketBuilder,
path::PathRef,
recovery::RecoveryToken,
- ConnectionError, Error,
+ CloseReason, Error,
};
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -42,14 +42,14 @@ pub enum State {
Connected,
Confirmed,
Closing {
- error: ConnectionError,
+ error: CloseReason,
timeout: Instant,
},
Draining {
- error: ConnectionError,
+ error: CloseReason,
timeout: Instant,
},
- Closed(ConnectionError),
+ Closed(CloseReason),
}
impl State {
@@ -67,7 +67,7 @@ impl State {
}
#[must_use]
- pub fn error(&self) -> Option<&ConnectionError> {
+ pub fn error(&self) -> Option<&CloseReason> {
if let Self::Closing { error, .. } | Self::Draining { error, .. } | Self::Closed(error) =
self
{
@@ -116,7 +116,7 @@ impl Ord for State {
#[derive(Debug, Clone)]
pub struct ClosingFrame {
path: PathRef,
- error: ConnectionError,
+ error: CloseReason,
frame_type: FrameType,
reason_phrase: Vec<u8>,
}
@@ -124,7 +124,7 @@ pub struct ClosingFrame {
impl ClosingFrame {
fn new(
path: PathRef,
- error: ConnectionError,
+ error: CloseReason,
frame_type: FrameType,
message: impl AsRef<str>,
) -> Self {
@@ -142,12 +142,12 @@ impl ClosingFrame {
}
pub fn sanitize(&self) -> Option<Self> {
- if let ConnectionError::Application(_) = self.error {
+ if let CloseReason::Application(_) = self.error {
// The default CONNECTION_CLOSE frame that is sent when an application
// error code needs to be sent in an Initial or Handshake packet.
Some(Self {
path: Rc::clone(&self.path),
- error: ConnectionError::Transport(Error::ApplicationError),
+ error: CloseReason::Transport(Error::ApplicationError),
frame_type: 0,
reason_phrase: Vec::new(),
})
@@ -156,19 +156,22 @@ impl ClosingFrame {
}
}
+ /// Length of a closing frame with a truncated `reason_length`. Allow 8 bytes for the reason
+ /// phrase to ensure that if it needs to be truncated there is still at least a few bytes of
+ /// the value.
+ pub const MIN_LENGTH: usize = 1 + 8 + 8 + 2 + 8;
+
pub fn write_frame(&self, builder: &mut PacketBuilder) {
- // Allow 8 bytes for the reason phrase to ensure that if it needs to be
- // truncated there is still at least a few bytes of the value.
- if builder.remaining() < 1 + 8 + 8 + 2 + 8 {
+ if builder.remaining() < ClosingFrame::MIN_LENGTH {
return;
}
match &self.error {
- ConnectionError::Transport(e) => {
+ CloseReason::Transport(e) => {
builder.encode_varint(FRAME_TYPE_CONNECTION_CLOSE_TRANSPORT);
builder.encode_varint(e.code());
builder.encode_varint(self.frame_type);
}
- ConnectionError::Application(code) => {
+ CloseReason::Application(code) => {
builder.encode_varint(FRAME_TYPE_CONNECTION_CLOSE_APPLICATION);
builder.encode_varint(*code);
}
@@ -209,10 +212,6 @@ pub enum StateSignaling {
impl StateSignaling {
pub fn handshake_done(&mut self) {
if !matches!(self, Self::Idle) {
- debug_assert!(
- false,
- "StateSignaling must be in Idle state but is in {self:?} state.",
- );
return;
}
*self = Self::HandshakeDone;
@@ -231,7 +230,7 @@ impl StateSignaling {
pub fn close(
&mut self,
path: PathRef,
- error: ConnectionError,
+ error: CloseReason,
frame_type: FrameType,
message: impl AsRef<str>,
) {
@@ -243,7 +242,7 @@ impl StateSignaling {
pub fn drain(
&mut self,
path: PathRef,
- error: ConnectionError,
+ error: CloseReason,
frame_type: FrameType,
message: impl AsRef<str>,
) {
diff --git a/third_party/rust/neqo-transport/src/connection/tests/cc.rs b/third_party/rust/neqo-transport/src/connection/tests/cc.rs
index b708bc421d..f21f4e184f 100644
--- a/third_party/rust/neqo-transport/src/connection/tests/cc.rs
+++ b/third_party/rust/neqo-transport/src/connection/tests/cc.rs
@@ -6,7 +6,7 @@
use std::{mem, time::Duration};
-use neqo_common::{qdebug, qinfo, Datagram};
+use neqo_common::{qdebug, qinfo, Datagram, IpTosEcn};
use super::{
super::Output, ack_bytes, assert_full_cwnd, connect_rtt_idle, cwnd, cwnd_avail, cwnd_packets,
@@ -36,9 +36,13 @@ fn cc_slow_start() {
assert!(cwnd_avail(&client) < ACK_ONLY_SIZE_LIMIT);
}
-#[test]
-/// Verify that CC moves to cong avoidance when a packet is marked lost.
-fn cc_slow_start_to_cong_avoidance_recovery_period() {
+#[derive(PartialEq, Eq, Clone, Copy)]
+enum CongestionSignal {
+ PacketLoss,
+ EcnCe,
+}
+
+fn cc_slow_start_to_cong_avoidance_recovery_period(congestion_signal: CongestionSignal) {
let mut client = default_client();
let mut server = default_server();
let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT);
@@ -78,9 +82,17 @@ fn cc_slow_start_to_cong_avoidance_recovery_period() {
assert_full_cwnd(&c_tx_dgrams, POST_HANDSHAKE_CWND * 2);
let flight2_largest = flight1_largest + u64::try_from(c_tx_dgrams.len()).unwrap();
- // Server: Receive and generate ack again, but drop first packet
+ // Server: Receive and generate ack again, but this time add congestion
+ // signal first.
now += DEFAULT_RTT / 2;
- c_tx_dgrams.remove(0);
+ match congestion_signal {
+ CongestionSignal::PacketLoss => {
+ c_tx_dgrams.remove(0);
+ }
+ CongestionSignal::EcnCe => {
+ c_tx_dgrams.last_mut().unwrap().set_tos(IpTosEcn::Ce.into());
+ }
+ }
let s_ack = ack_bytes(&mut server, stream_id, c_tx_dgrams, now);
assert_eq!(
server.stats().frame_tx.largest_acknowledged,
@@ -98,6 +110,18 @@ fn cc_slow_start_to_cong_avoidance_recovery_period() {
}
#[test]
+/// Verify that CC moves to cong avoidance when a packet is marked lost.
+fn cc_slow_start_to_cong_avoidance_recovery_period_due_to_packet_loss() {
+ cc_slow_start_to_cong_avoidance_recovery_period(CongestionSignal::PacketLoss);
+}
+
+/// Verify that CC moves to cong avoidance when ACK is marked with ECN CE.
+#[test]
+fn cc_slow_start_to_cong_avoidance_recovery_period_due_to_ecn_ce() {
+ cc_slow_start_to_cong_avoidance_recovery_period(CongestionSignal::EcnCe);
+}
+
+#[test]
/// Verify that CC stays in recovery period when packet sent before start of
/// recovery period is acked.
fn cc_cong_avoidance_recovery_period_unchanged() {
diff --git a/third_party/rust/neqo-transport/src/connection/tests/close.rs b/third_party/rust/neqo-transport/src/connection/tests/close.rs
index 5351dd0d5c..7c620de17e 100644
--- a/third_party/rust/neqo-transport/src/connection/tests/close.rs
+++ b/third_party/rust/neqo-transport/src/connection/tests/close.rs
@@ -14,13 +14,13 @@ use super::{
};
use crate::{
tparams::{self, TransportParameter},
- AppError, ConnectionError, Error, ERROR_APPLICATION_CLOSE,
+ AppError, CloseReason, Error, ERROR_APPLICATION_CLOSE,
};
fn assert_draining(c: &Connection, expected: &Error) {
assert!(c.state().closed());
if let State::Draining {
- error: ConnectionError::Transport(error),
+ error: CloseReason::Transport(error),
..
} = c.state()
{
@@ -40,7 +40,14 @@ fn connection_close() {
client.close(now, 42, "");
+ let stats_before = client.stats().frame_tx;
let out = client.process(None, now);
+ let stats_after = client.stats().frame_tx;
+ assert_eq!(
+ stats_after.connection_close,
+ stats_before.connection_close + 1
+ );
+ assert_eq!(stats_after.ack, stats_before.ack + 1);
server.process_input(&out.dgram().unwrap(), now);
assert_draining(&server, &Error::PeerApplicationError(42));
@@ -57,7 +64,14 @@ fn connection_close_with_long_reason_string() {
let long_reason = String::from_utf8([0x61; 2048].to_vec()).unwrap();
client.close(now, 42, long_reason);
+ let stats_before = client.stats().frame_tx;
let out = client.process(None, now);
+ let stats_after = client.stats().frame_tx;
+ assert_eq!(
+ stats_after.connection_close,
+ stats_before.connection_close + 1
+ );
+ assert_eq!(stats_after.ack, stats_before.ack + 1);
server.process_input(&out.dgram().unwrap(), now);
assert_draining(&server, &Error::PeerApplicationError(42));
@@ -100,7 +114,7 @@ fn bad_tls_version() {
let dgram = server.process(dgram.as_ref(), now()).dgram();
assert_eq!(
*server.state(),
- State::Closed(ConnectionError::Transport(Error::ProtocolViolation))
+ State::Closed(CloseReason::Transport(Error::ProtocolViolation))
);
assert!(dgram.is_some());
client.process_input(&dgram.unwrap(), now());
@@ -154,7 +168,6 @@ fn closing_and_draining() {
assert!(client_close.is_some());
let client_close_timer = client.process(None, now()).callback();
assert_ne!(client_close_timer, Duration::from_secs(0));
-
// The client will spit out the same packet in response to anything it receives.
let p3 = send_something(&mut server, now());
let client_close2 = client.process(Some(&p3), now()).dgram();
@@ -168,7 +181,7 @@ fn closing_and_draining() {
assert_eq!(end, Output::None);
assert_eq!(
*client.state(),
- State::Closed(ConnectionError::Application(APP_ERROR))
+ State::Closed(CloseReason::Application(APP_ERROR))
);
// When the server receives the close, it too should generate CONNECTION_CLOSE.
@@ -186,7 +199,7 @@ fn closing_and_draining() {
assert_eq!(end, Output::None);
assert_eq!(
*server.state(),
- State::Closed(ConnectionError::Transport(Error::PeerApplicationError(
+ State::Closed(CloseReason::Transport(Error::PeerApplicationError(
APP_ERROR
)))
);
diff --git a/third_party/rust/neqo-transport/src/connection/tests/datagram.rs b/third_party/rust/neqo-transport/src/connection/tests/datagram.rs
index ade8c753be..f1b64b3c8f 100644
--- a/third_party/rust/neqo-transport/src/connection/tests/datagram.rs
+++ b/third_party/rust/neqo-transport/src/connection/tests/datagram.rs
@@ -19,7 +19,7 @@ use crate::{
packet::PacketBuilder,
quic_datagrams::MAX_QUIC_DATAGRAM,
send_stream::{RetransmissionPriority, TransmissionPriority},
- Connection, ConnectionError, ConnectionParameters, Error, StreamType,
+ CloseReason, Connection, ConnectionParameters, Error, StreamType,
};
const DATAGRAM_LEN_MTU: u64 = 1310;
@@ -362,10 +362,7 @@ fn dgram_no_allowed() {
client.process_input(&out, now());
- assert_error(
- &client,
- &ConnectionError::Transport(Error::ProtocolViolation),
- );
+ assert_error(&client, &CloseReason::Transport(Error::ProtocolViolation));
}
#[test]
@@ -383,10 +380,7 @@ fn dgram_too_big() {
client.process_input(&out, now());
- assert_error(
- &client,
- &ConnectionError::Transport(Error::ProtocolViolation),
- );
+ assert_error(&client, &CloseReason::Transport(Error::ProtocolViolation));
}
#[test]
@@ -587,7 +581,7 @@ fn datagram_fill() {
// Work out how much space we have for a datagram.
let space = {
- let p = client.paths.primary();
+ let p = client.paths.primary().unwrap();
let path = p.borrow();
// Minimum overhead is connection ID length, 1 byte short header, 1 byte packet number,
// 1 byte for the DATAGRAM frame type, and 16 bytes for the AEAD.
diff --git a/third_party/rust/neqo-transport/src/connection/tests/ecn.rs b/third_party/rust/neqo-transport/src/connection/tests/ecn.rs
new file mode 100644
index 0000000000..87957297e5
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/connection/tests/ecn.rs
@@ -0,0 +1,392 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::time::Duration;
+
+use neqo_common::{Datagram, IpTos, IpTosEcn};
+use test_fixture::{
+ assertions::{assert_v4_path, assert_v6_path},
+ fixture_init, now, DEFAULT_ADDR_V4,
+};
+
+use super::send_something_with_modifier;
+use crate::{
+ connection::tests::{
+ connect_force_idle, connect_force_idle_with_modifier, default_client, default_server,
+ migration::get_cid, new_client, new_server, send_something,
+ },
+ ecn::ECN_TEST_COUNT,
+ ConnectionId, ConnectionParameters, StreamType,
+};
+
+fn assert_ecn_enabled(tos: IpTos) {
+ assert!(tos.is_ecn_marked());
+}
+
+fn assert_ecn_disabled(tos: IpTos) {
+ assert!(!tos.is_ecn_marked());
+}
+
+fn set_tos(mut d: Datagram, ecn: IpTosEcn) -> Datagram {
+ d.set_tos(ecn.into());
+ d
+}
+
+fn noop() -> fn(Datagram) -> Option<Datagram> {
+ Some
+}
+
+fn bleach() -> fn(Datagram) -> Option<Datagram> {
+ |d| Some(set_tos(d, IpTosEcn::NotEct))
+}
+
+fn remark() -> fn(Datagram) -> Option<Datagram> {
+ |d| {
+ if d.tos().is_ecn_marked() {
+ Some(set_tos(d, IpTosEcn::Ect1))
+ } else {
+ Some(d)
+ }
+ }
+}
+
+fn ce() -> fn(Datagram) -> Option<Datagram> {
+ |d| {
+ if d.tos().is_ecn_marked() {
+ Some(set_tos(d, IpTosEcn::Ce))
+ } else {
+ Some(d)
+ }
+ }
+}
+
+fn drop() -> fn(Datagram) -> Option<Datagram> {
+ |_| None
+}
+
+#[test]
+fn disables_on_loss() {
+ let now = now();
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ // Right after the handshake, the ECN validation should still be in progress.
+ let client_pkt = send_something(&mut client, now);
+ assert_ecn_enabled(client_pkt.tos());
+
+ for _ in 0..ECN_TEST_COUNT {
+ send_something(&mut client, now);
+ }
+
+ // ECN should now be disabled.
+ let client_pkt = send_something(&mut client, now);
+ assert_ecn_disabled(client_pkt.tos());
+}
+
+/// This function performs a handshake over a path that modifies packets via `orig_path_modifier`.
+/// It then sends `burst` packets on that path, and then migrates to a new path that
+/// modifies packets via `new_path_modifier`. It sends `burst` packets on the new path.
+/// The function returns the TOS value of the last packet sent on the old path and the TOS value
+/// of the last packet sent on the new path to allow for verification of correct behavior.
+pub fn migration_with_modifiers(
+ orig_path_modifier: fn(Datagram) -> Option<Datagram>,
+ new_path_modifier: fn(Datagram) -> Option<Datagram>,
+ burst: usize,
+) -> (IpTos, IpTos, bool) {
+ fixture_init();
+ let mut client = new_client(ConnectionParameters::default().max_streams(StreamType::UniDi, 64));
+ let mut server = new_server(ConnectionParameters::default().max_streams(StreamType::UniDi, 64));
+
+ connect_force_idle_with_modifier(&mut client, &mut server, orig_path_modifier);
+ let mut now = now();
+
+ // Right after the handshake, the ECN validation should still be in progress.
+ let client_pkt = send_something(&mut client, now);
+ assert_ecn_enabled(client_pkt.tos());
+ server.process_input(&orig_path_modifier(client_pkt).unwrap(), now);
+
+ // Send some data on the current path.
+ for _ in 0..burst {
+ let client_pkt = send_something_with_modifier(&mut client, now, orig_path_modifier);
+ server.process_input(&client_pkt, now);
+ }
+
+ if let Some(ack) = server.process_output(now).dgram() {
+ client.process_input(&ack, now);
+ }
+
+ let client_pkt = send_something(&mut client, now);
+ let tos_before_migration = client_pkt.tos();
+ server.process_input(&orig_path_modifier(client_pkt).unwrap(), now);
+
+ client
+ .migrate(Some(DEFAULT_ADDR_V4), Some(DEFAULT_ADDR_V4), false, now)
+ .unwrap();
+
+ let mut migrated = false;
+ let probe = new_path_modifier(client.process_output(now).dgram().unwrap());
+ if let Some(probe) = probe {
+ assert_v4_path(&probe, true); // Contains PATH_CHALLENGE.
+ assert_eq!(client.stats().frame_tx.path_challenge, 1);
+ let probe_cid = ConnectionId::from(get_cid(&probe));
+
+ let resp = new_path_modifier(server.process(Some(&probe), now).dgram().unwrap()).unwrap();
+ assert_v4_path(&resp, true);
+ assert_eq!(server.stats().frame_tx.path_response, 1);
+ assert_eq!(server.stats().frame_tx.path_challenge, 1);
+
+ // Data continues to be exchanged on the old path.
+ let client_data = send_something_with_modifier(&mut client, now, orig_path_modifier);
+ assert_ne!(get_cid(&client_data), probe_cid);
+ assert_v6_path(&client_data, false);
+ server.process_input(&client_data, now);
+ let server_data = send_something_with_modifier(&mut server, now, orig_path_modifier);
+ assert_v6_path(&server_data, false);
+ client.process_input(&server_data, now);
+
+ // Once the client receives the probe response, it migrates to the new path.
+ client.process_input(&resp, now);
+ assert_eq!(client.stats().frame_rx.path_challenge, 1);
+ migrated = true;
+
+ let migrate_client = send_something_with_modifier(&mut client, now, new_path_modifier);
+ assert_v4_path(&migrate_client, true); // Responds to server probe.
+
+ // The server now sees the migration and will switch over.
+ // However, it will probe the old path again, even though it has just
+ // received a response to its last probe, because it needs to verify
+ // that the migration is genuine.
+ server.process_input(&migrate_client, now);
+ }
+
+ let stream_before = server.stats().frame_tx.stream;
+ let probe_old_server = send_something_with_modifier(&mut server, now, orig_path_modifier);
+ // This is just the double-check probe; no STREAM frames.
+ assert_v6_path(&probe_old_server, migrated);
+ assert_eq!(
+ server.stats().frame_tx.path_challenge,
+ if migrated { 2 } else { 0 }
+ );
+ assert_eq!(
+ server.stats().frame_tx.stream,
+ if migrated { stream_before } else { 1 }
+ );
+
+ if migrated {
+ // The server then sends data on the new path.
+ let migrate_server =
+ new_path_modifier(server.process_output(now).dgram().unwrap()).unwrap();
+ assert_v4_path(&migrate_server, false);
+ assert_eq!(server.stats().frame_tx.path_challenge, 2);
+ assert_eq!(server.stats().frame_tx.stream, stream_before + 1);
+
+ // The client receives these checks and responds to the probe, but uses the new path.
+ client.process_input(&migrate_server, now);
+ client.process_input(&probe_old_server, now);
+ let old_probe_resp = send_something_with_modifier(&mut client, now, new_path_modifier);
+ assert_v6_path(&old_probe_resp, true);
+ let client_confirmation = client.process_output(now).dgram().unwrap();
+ assert_v4_path(&client_confirmation, false);
+
+ // The server has now sent 2 packets, so it is blocked on the pacer. Wait.
+ let server_pacing = server.process_output(now).callback();
+ assert_ne!(server_pacing, Duration::new(0, 0));
+ // ... then confirm that the server sends on the new path still.
+ let server_confirmation =
+ send_something_with_modifier(&mut server, now + server_pacing, new_path_modifier);
+ assert_v4_path(&server_confirmation, false);
+ client.process_input(&server_confirmation, now);
+
+ // Send some data on the new path.
+ for _ in 0..burst {
+ now += client.process_output(now).callback();
+ let client_pkt = send_something_with_modifier(&mut client, now, new_path_modifier);
+ server.process_input(&client_pkt, now);
+ }
+
+ if let Some(ack) = server.process_output(now).dgram() {
+ client.process_input(&ack, now);
+ }
+ }
+
+ now += client.process_output(now).callback();
+ let mut client_pkt = send_something(&mut client, now);
+ while !migrated && client_pkt.source() == DEFAULT_ADDR_V4 {
+ client_pkt = send_something(&mut client, now);
+ }
+ let tos_after_migration = client_pkt.tos();
+ (tos_before_migration, tos_after_migration, migrated)
+}
+
+#[test]
+fn ecn_migration_zero_burst_all_cases() {
+ for orig_path_mod in &[noop(), bleach(), remark(), ce()] {
+ for new_path_mod in &[noop(), bleach(), remark(), ce(), drop()] {
+ let (before, after, migrated) =
+ migration_with_modifiers(*orig_path_mod, *new_path_mod, 0);
+ // Too few packets sent before and after migration to conclude ECN validation.
+ assert_ecn_enabled(before);
+ assert_ecn_enabled(after);
+ // Migration succeeds except if the new path drops ECN.
+ assert!(*new_path_mod == drop() || migrated);
+ }
+ }
+}
+
+#[test]
+fn ecn_migration_noop_bleach_data() {
+ let (before, after, migrated) = migration_with_modifiers(noop(), bleach(), ECN_TEST_COUNT);
+ assert_ecn_enabled(before); // ECN validation concludes before migration.
+ assert_ecn_disabled(after); // ECN validation fails after migration due to bleaching.
+ assert!(migrated);
+}
+
+#[test]
+fn ecn_migration_noop_remark_data() {
+ let (before, after, migrated) = migration_with_modifiers(noop(), remark(), ECN_TEST_COUNT);
+ assert_ecn_enabled(before); // ECN validation concludes before migration.
+ assert_ecn_disabled(after); // ECN validation fails after migration due to remarking.
+ assert!(migrated);
+}
+
+#[test]
+fn ecn_migration_noop_ce_data() {
+ let (before, after, migrated) = migration_with_modifiers(noop(), ce(), ECN_TEST_COUNT);
+ assert_ecn_enabled(before); // ECN validation concludes before migration.
+ assert_ecn_enabled(after); // ECN validation concludes after migration, despite all CE marks.
+ assert!(migrated);
+}
+
+#[test]
+fn ecn_migration_noop_drop_data() {
+ let (before, after, migrated) = migration_with_modifiers(noop(), drop(), ECN_TEST_COUNT);
+ assert_ecn_enabled(before); // ECN validation concludes before migration.
+ assert_ecn_enabled(after); // Migration failed, ECN on original path is still validated.
+ assert!(!migrated);
+}
+
+#[test]
+fn ecn_migration_bleach_noop_data() {
+ let (before, after, migrated) = migration_with_modifiers(bleach(), noop(), ECN_TEST_COUNT);
+ assert_ecn_disabled(before); // ECN validation fails before migration due to bleaching.
+ assert_ecn_enabled(after); // ECN validation concludes after migration.
+ assert!(migrated);
+}
+
+#[test]
+fn ecn_migration_bleach_bleach_data() {
+ let (before, after, migrated) = migration_with_modifiers(bleach(), bleach(), ECN_TEST_COUNT);
+ assert_ecn_disabled(before); // ECN validation fails before migration due to bleaching.
+ assert_ecn_disabled(after); // ECN validation fails after migration due to bleaching.
+ assert!(migrated);
+}
+
+#[test]
+fn ecn_migration_bleach_remark_data() {
+ let (before, after, migrated) = migration_with_modifiers(bleach(), remark(), ECN_TEST_COUNT);
+ assert_ecn_disabled(before); // ECN validation fails before migration due to bleaching.
+ assert_ecn_disabled(after); // ECN validation fails after migration due to remarking.
+ assert!(migrated);
+}
+
+#[test]
+fn ecn_migration_bleach_ce_data() {
+ let (before, after, migrated) = migration_with_modifiers(bleach(), ce(), ECN_TEST_COUNT);
+ assert_ecn_disabled(before); // ECN validation fails before migration due to bleaching.
+ assert_ecn_enabled(after); // ECN validation concludes after migration, despite all CE marks.
+ assert!(migrated);
+}
+
+#[test]
+fn ecn_migration_bleach_drop_data() {
+ let (before, after, migrated) = migration_with_modifiers(bleach(), drop(), ECN_TEST_COUNT);
+ assert_ecn_disabled(before); // ECN validation fails before migration due to bleaching.
+ // Migration failed, ECN on original path is still disabled.
+ assert_ecn_disabled(after);
+ assert!(!migrated);
+}
+
+#[test]
+fn ecn_migration_remark_noop_data() {
+ let (before, after, migrated) = migration_with_modifiers(remark(), noop(), ECN_TEST_COUNT);
+ assert_ecn_disabled(before); // ECN validation fails before migration due to remarking.
+ assert_ecn_enabled(after); // ECN validation succeeds after migration.
+ assert!(migrated);
+}
+
+#[test]
+fn ecn_migration_remark_bleach_data() {
+ let (before, after, migrated) = migration_with_modifiers(remark(), bleach(), ECN_TEST_COUNT);
+ assert_ecn_disabled(before); // ECN validation fails before migration due to remarking.
+ assert_ecn_disabled(after); // ECN validation fails after migration due to bleaching.
+ assert!(migrated);
+}
+
+#[test]
+fn ecn_migration_remark_remark_data() {
+ let (before, after, migrated) = migration_with_modifiers(remark(), remark(), ECN_TEST_COUNT);
+ assert_ecn_disabled(before); // ECN validation fails before migration due to remarking.
+ assert_ecn_disabled(after); // ECN validation fails after migration due to remarking.
+ assert!(migrated);
+}
+
+#[test]
+fn ecn_migration_remark_ce_data() {
+ let (before, after, migrated) = migration_with_modifiers(remark(), ce(), ECN_TEST_COUNT);
+ assert_ecn_disabled(before); // ECN validation fails before migration due to remarking.
+ assert_ecn_enabled(after); // ECN validation concludes after migration, despite all CE marks.
+ assert!(migrated);
+}
+
+#[test]
+fn ecn_migration_remark_drop_data() {
+ let (before, after, migrated) = migration_with_modifiers(remark(), drop(), ECN_TEST_COUNT);
+ assert_ecn_disabled(before); // ECN validation fails before migration due to remarking.
+ assert_ecn_disabled(after); // Migration failed, ECN on original path is still disabled.
+ assert!(!migrated);
+}
+
+#[test]
+fn ecn_migration_ce_noop_data() {
+ let (before, after, migrated) = migration_with_modifiers(ce(), noop(), ECN_TEST_COUNT);
+ assert_ecn_enabled(before); // ECN validation concludes before migration, despite all CE marks.
+ assert_ecn_enabled(after); // ECN validation concludes after migration.
+ assert!(migrated);
+}
+
+#[test]
+fn ecn_migration_ce_bleach_data() {
+ let (before, after, migrated) = migration_with_modifiers(ce(), bleach(), ECN_TEST_COUNT);
+ assert_ecn_enabled(before); // ECN validation concludes before migration, despite all CE marks.
+ assert_ecn_disabled(after); // ECN validation fails after migration due to bleaching
+ assert!(migrated);
+}
+
+#[test]
+fn ecn_migration_ce_remark_data() {
+ let (before, after, migrated) = migration_with_modifiers(ce(), remark(), ECN_TEST_COUNT);
+ assert_ecn_enabled(before); // ECN validation concludes before migration, despite all CE marks.
+ assert_ecn_disabled(after); // ECN validation fails after migration due to remarking.
+ assert!(migrated);
+}
+
+#[test]
+fn ecn_migration_ce_ce_data() {
+ let (before, after, migrated) = migration_with_modifiers(ce(), ce(), ECN_TEST_COUNT);
+ assert_ecn_enabled(before); // ECN validation concludes before migration, despite all CE marks.
+ assert_ecn_enabled(after); // ECN validation concludes after migration, despite all CE marks.
+ assert!(migrated);
+}
+
+#[test]
+fn ecn_migration_ce_drop_data() {
+ let (before, after, migrated) = migration_with_modifiers(ce(), drop(), ECN_TEST_COUNT);
+ assert_ecn_enabled(before); // ECN validation concludes before migration, despite all CE marks.
+ // Migration failed, ECN on original path is still enabled.
+ assert_ecn_enabled(after);
+ assert!(!migrated);
+}
diff --git a/third_party/rust/neqo-transport/src/connection/tests/handshake.rs b/third_party/rust/neqo-transport/src/connection/tests/handshake.rs
index f2103523ec..c908340616 100644
--- a/third_party/rust/neqo-transport/src/connection/tests/handshake.rs
+++ b/third_party/rust/neqo-transport/src/connection/tests/handshake.rs
@@ -35,7 +35,7 @@ use crate::{
server::ValidateAddress,
tparams::{TransportParameter, MIN_ACK_DELAY},
tracking::DEFAULT_ACK_DELAY,
- ConnectionError, ConnectionParameters, EmptyConnectionIdGenerator, Error, StreamType, Version,
+ CloseReason, ConnectionParameters, EmptyConnectionIdGenerator, Error, StreamType, Version,
};
const ECH_CONFIG_ID: u8 = 7;
@@ -111,8 +111,8 @@ fn handshake_failed_authentication() {
qdebug!("---- server: Alert(certificate_revoked)");
let out = server.process(out.as_dgram_ref(), now());
assert!(out.as_dgram_ref().is_some());
- assert_error(&client, &ConnectionError::Transport(Error::CryptoAlert(44)));
- assert_error(&server, &ConnectionError::Transport(Error::PeerError(300)));
+ assert_error(&client, &CloseReason::Transport(Error::CryptoAlert(44)));
+ assert_error(&server, &CloseReason::Transport(Error::PeerError(300)));
}
#[test]
@@ -133,11 +133,8 @@ fn no_alpn() {
handshake(&mut client, &mut server, now(), Duration::new(0, 0));
// TODO (mt): errors are immediate, which means that we never send CONNECTION_CLOSE
// and the client never sees the server's rejection of its handshake.
- // assert_error(&client, ConnectionError::Transport(Error::CryptoAlert(120)));
- assert_error(
- &server,
- &ConnectionError::Transport(Error::CryptoAlert(120)),
- );
+ // assert_error(&client, CloseReason::Transport(Error::CryptoAlert(120)));
+ assert_error(&server, &CloseReason::Transport(Error::CryptoAlert(120)));
}
#[test]
@@ -934,10 +931,10 @@ fn ech_retry() {
server.process_input(&dgram.unwrap(), now());
assert_eq!(
server.state().error(),
- Some(&ConnectionError::Transport(Error::PeerError(0x100 + 121)))
+ Some(&CloseReason::Transport(Error::PeerError(0x100 + 121)))
);
- let Some(ConnectionError::Transport(Error::EchRetry(updated_config))) = client.state().error()
+ let Some(CloseReason::Transport(Error::EchRetry(updated_config))) = client.state().error()
else {
panic!(
"Client state should be failed with EchRetry, is {:?}",
@@ -984,7 +981,7 @@ fn ech_retry_fallback_rejected() {
client.authenticated(AuthenticationStatus::PolicyRejection, now());
assert!(client.state().error().is_some());
- if let Some(ConnectionError::Transport(Error::EchRetry(_))) = client.state().error() {
+ if let Some(CloseReason::Transport(Error::EchRetry(_))) = client.state().error() {
panic!("Client should not get EchRetry error");
}
@@ -993,14 +990,13 @@ fn ech_retry_fallback_rejected() {
server.process_input(&dgram.unwrap(), now());
assert_eq!(
server.state().error(),
- Some(&ConnectionError::Transport(Error::PeerError(298)))
+ Some(&CloseReason::Transport(Error::PeerError(298)))
); // A bad_certificate alert.
}
#[test]
fn bad_min_ack_delay() {
- const EXPECTED_ERROR: ConnectionError =
- ConnectionError::Transport(Error::TransportParameterError);
+ const EXPECTED_ERROR: CloseReason = CloseReason::Transport(Error::TransportParameterError);
let mut server = default_server();
let max_ad = u64::try_from(DEFAULT_ACK_DELAY.as_micros()).unwrap();
server
@@ -1018,7 +1014,7 @@ fn bad_min_ack_delay() {
server.process_input(&dgram.unwrap(), now());
assert_eq!(
server.state().error(),
- Some(&ConnectionError::Transport(Error::PeerError(
+ Some(&CloseReason::Transport(Error::PeerError(
Error::TransportParameterError.code()
)))
);
diff --git a/third_party/rust/neqo-transport/src/connection/tests/keys.rs b/third_party/rust/neqo-transport/src/connection/tests/keys.rs
index 847b253284..c2ae9529bf 100644
--- a/third_party/rust/neqo-transport/src/connection/tests/keys.rs
+++ b/third_party/rust/neqo-transport/src/connection/tests/keys.rs
@@ -11,7 +11,7 @@ use test_fixture::now;
use super::{
super::{
- super::{ConnectionError, ERROR_AEAD_LIMIT_REACHED},
+ super::{CloseReason, ERROR_AEAD_LIMIT_REACHED},
Connection, ConnectionParameters, Error, Output, State, StreamType,
},
connect, connect_force_idle, default_client, default_server, maybe_authenticate,
@@ -269,7 +269,7 @@ fn exhaust_write_keys() {
assert!(dgram.is_none());
assert!(matches!(
client.state(),
- State::Closed(ConnectionError::Transport(Error::KeysExhausted))
+ State::Closed(CloseReason::Transport(Error::KeysExhausted))
));
}
@@ -285,14 +285,14 @@ fn exhaust_read_keys() {
let dgram = server.process(Some(&dgram), now()).dgram();
assert!(matches!(
server.state(),
- State::Closed(ConnectionError::Transport(Error::KeysExhausted))
+ State::Closed(CloseReason::Transport(Error::KeysExhausted))
));
client.process_input(&dgram.unwrap(), now());
assert!(matches!(
client.state(),
State::Draining {
- error: ConnectionError::Transport(Error::PeerError(ERROR_AEAD_LIMIT_REACHED)),
+ error: CloseReason::Transport(Error::PeerError(ERROR_AEAD_LIMIT_REACHED)),
..
}
));
@@ -341,6 +341,6 @@ fn automatic_update_write_keys_blocked() {
assert!(dgram.is_none());
assert!(matches!(
client.state(),
- State::Closed(ConnectionError::Transport(Error::KeysExhausted))
+ State::Closed(CloseReason::Transport(Error::KeysExhausted))
));
}
diff --git a/third_party/rust/neqo-transport/src/connection/tests/migration.rs b/third_party/rust/neqo-transport/src/connection/tests/migration.rs
index 405ae161a4..779cc78c53 100644
--- a/third_party/rust/neqo-transport/src/connection/tests/migration.rs
+++ b/third_party/rust/neqo-transport/src/connection/tests/migration.rs
@@ -30,7 +30,7 @@ use crate::{
packet::PacketBuilder,
path::{PATH_MTU_V4, PATH_MTU_V6},
tparams::{self, PreferredAddress, TransportParameter},
- ConnectionError, ConnectionId, ConnectionIdDecoder, ConnectionIdGenerator, ConnectionIdRef,
+ CloseReason, ConnectionId, ConnectionIdDecoder, ConnectionIdGenerator, ConnectionIdRef,
ConnectionParameters, EmptyConnectionIdGenerator, Error,
};
@@ -357,13 +357,13 @@ fn migrate_same_fail() {
assert!(matches!(res, Output::None));
assert!(matches!(
client.state(),
- State::Closed(ConnectionError::Transport(Error::NoAvailablePath))
+ State::Closed(CloseReason::Transport(Error::NoAvailablePath))
));
}
/// This gets the connection ID from a datagram using the default
/// connection ID generator/decoder.
-fn get_cid(d: &Datagram) -> ConnectionIdRef {
+pub fn get_cid(d: &Datagram) -> ConnectionIdRef {
let gen = CountingConnectionIdGenerator::default();
assert_eq!(d[0] & 0x80, 0); // Only support short packets for now.
gen.decode_cid(&mut Decoder::from(&d[1..])).unwrap()
@@ -894,7 +894,7 @@ fn retire_prior_to_migration_failure() {
assert!(matches!(
client.state(),
State::Closing {
- error: ConnectionError::Transport(Error::InvalidMigration),
+ error: CloseReason::Transport(Error::InvalidMigration),
..
}
));
diff --git a/third_party/rust/neqo-transport/src/connection/tests/mod.rs b/third_party/rust/neqo-transport/src/connection/tests/mod.rs
index c8c87a0df0..65283b8eb8 100644
--- a/third_party/rust/neqo-transport/src/connection/tests/mod.rs
+++ b/third_party/rust/neqo-transport/src/connection/tests/mod.rs
@@ -17,7 +17,7 @@ use neqo_common::{event::Provider, qdebug, qtrace, Datagram, Decoder, Role};
use neqo_crypto::{random, AllowZeroRtt, AuthenticationStatus, ResumptionToken};
use test_fixture::{fixture_init, new_neqo_qlog, now, DEFAULT_ADDR};
-use super::{Connection, ConnectionError, ConnectionId, Output, State};
+use super::{CloseReason, Connection, ConnectionId, Output, State};
use crate::{
addr_valid::{AddressValidation, ValidateAddress},
cc::{CWND_INITIAL_PKTS, CWND_MIN},
@@ -37,6 +37,7 @@ mod ackrate;
mod cc;
mod close;
mod datagram;
+mod ecn;
mod handshake;
mod idle;
mod keys;
@@ -170,17 +171,13 @@ impl crate::connection::test_internal::FrameWriter for PingWriter {
}
}
-trait DatagramModifier: FnMut(Datagram) -> Option<Datagram> {}
-
-impl<T> DatagramModifier for T where T: FnMut(Datagram) -> Option<Datagram> {}
-
/// Drive the handshake between the client and server.
fn handshake_with_modifier(
client: &mut Connection,
server: &mut Connection,
now: Instant,
rtt: Duration,
- mut modifier: impl DatagramModifier,
+ modifier: fn(Datagram) -> Option<Datagram>,
) -> Instant {
let mut a = client;
let mut b = server;
@@ -248,8 +245,8 @@ fn connect_fail(
server_error: Error,
) {
handshake(client, server, now(), Duration::new(0, 0));
- assert_error(client, &ConnectionError::Transport(client_error));
- assert_error(server, &ConnectionError::Transport(server_error));
+ assert_error(client, &CloseReason::Transport(client_error));
+ assert_error(server, &CloseReason::Transport(server_error));
}
fn connect_with_rtt_and_modifier(
@@ -257,7 +254,7 @@ fn connect_with_rtt_and_modifier(
server: &mut Connection,
now: Instant,
rtt: Duration,
- modifier: impl DatagramModifier,
+ modifier: fn(Datagram) -> Option<Datagram>,
) -> Instant {
fn check_rtt(stats: &Stats, rtt: Duration) {
assert_eq!(stats.rtt, rtt);
@@ -287,7 +284,7 @@ fn connect(client: &mut Connection, server: &mut Connection) {
connect_with_rtt(client, server, now(), Duration::new(0, 0));
}
-fn assert_error(c: &Connection, expected: &ConnectionError) {
+fn assert_error(c: &Connection, expected: &CloseReason) {
match c.state() {
State::Closing { error, .. } | State::Draining { error, .. } | State::Closed(error) => {
assert_eq!(*error, *expected, "{c} error mismatch");
@@ -333,7 +330,7 @@ fn connect_rtt_idle_with_modifier(
client: &mut Connection,
server: &mut Connection,
rtt: Duration,
- modifier: impl DatagramModifier,
+ modifier: fn(Datagram) -> Option<Datagram>,
) -> Instant {
let now = connect_with_rtt_and_modifier(client, server, now(), rtt, modifier);
assert_idle(client, server, rtt, now);
@@ -351,7 +348,7 @@ fn connect_rtt_idle(client: &mut Connection, server: &mut Connection, rtt: Durat
fn connect_force_idle_with_modifier(
client: &mut Connection,
server: &mut Connection,
- modifier: impl DatagramModifier,
+ modifier: fn(Datagram) -> Option<Datagram>,
) {
connect_rtt_idle_with_modifier(client, server, Duration::new(0, 0), modifier);
}
@@ -380,7 +377,7 @@ fn fill_stream(c: &mut Connection, stream: StreamId) {
fn fill_cwnd(c: &mut Connection, stream: StreamId, mut now: Instant) -> (Vec<Datagram>, Instant) {
// Train wreck function to get the remaining congestion window on the primary path.
fn cwnd(c: &Connection) -> usize {
- c.paths.primary().borrow().sender().cwnd_avail()
+ c.paths.primary().unwrap().borrow().sender().cwnd_avail()
}
qtrace!("fill_cwnd starting cwnd: {}", cwnd(c));
@@ -478,10 +475,10 @@ where
// Get the current congestion window for the connection.
fn cwnd(c: &Connection) -> usize {
- c.paths.primary().borrow().sender().cwnd()
+ c.paths.primary().unwrap().borrow().sender().cwnd()
}
fn cwnd_avail(c: &Connection) -> usize {
- c.paths.primary().borrow().sender().cwnd_avail()
+ c.paths.primary().unwrap().borrow().sender().cwnd_avail()
}
fn induce_persistent_congestion(
@@ -576,7 +573,7 @@ fn send_something_paced_with_modifier(
sender: &mut Connection,
mut now: Instant,
allow_pacing: bool,
- mut modifier: impl DatagramModifier,
+ modifier: fn(Datagram) -> Option<Datagram>,
) -> (Datagram, Instant) {
let stream_id = sender.stream_create(StreamType::UniDi).unwrap();
assert!(sender.stream_send(stream_id, DEFAULT_STREAM_DATA).is_ok());
@@ -608,7 +605,7 @@ fn send_something_paced(
fn send_something_with_modifier(
sender: &mut Connection,
now: Instant,
- modifier: impl DatagramModifier,
+ modifier: fn(Datagram) -> Option<Datagram>,
) -> Datagram {
send_something_paced_with_modifier(sender, now, false, modifier).0
}
diff --git a/third_party/rust/neqo-transport/src/connection/tests/stream.rs b/third_party/rust/neqo-transport/src/connection/tests/stream.rs
index 66d3bf32f3..f7472d917f 100644
--- a/third_party/rust/neqo-transport/src/connection/tests/stream.rs
+++ b/third_party/rust/neqo-transport/src/connection/tests/stream.rs
@@ -19,9 +19,9 @@ use crate::{
send_stream::{OrderGroup, SendStreamState, SEND_BUFFER_SIZE},
streams::{SendOrder, StreamOrder},
tparams::{self, TransportParameter},
+ CloseReason,
// tracking::DEFAULT_ACK_PACKET_TOLERANCE,
Connection,
- ConnectionError,
ConnectionParameters,
Error,
StreamId,
@@ -494,12 +494,9 @@ fn exceed_max_data() {
assert_error(
&client,
- &ConnectionError::Transport(Error::PeerError(Error::FlowControlError.code())),
- );
- assert_error(
- &server,
- &ConnectionError::Transport(Error::FlowControlError),
+ &CloseReason::Transport(Error::PeerError(Error::FlowControlError.code())),
);
+ assert_error(&server, &CloseReason::Transport(Error::FlowControlError));
}
#[test]
diff --git a/third_party/rust/neqo-transport/src/connection/tests/vn.rs b/third_party/rust/neqo-transport/src/connection/tests/vn.rs
index 93872a94f4..815868d78d 100644
--- a/third_party/rust/neqo-transport/src/connection/tests/vn.rs
+++ b/third_party/rust/neqo-transport/src/connection/tests/vn.rs
@@ -10,7 +10,7 @@ use neqo_common::{event::Provider, Decoder, Encoder};
use test_fixture::{assertions, datagram, now};
use super::{
- super::{ConnectionError, ConnectionEvent, Output, State, ZeroRttState},
+ super::{CloseReason, ConnectionEvent, Output, State, ZeroRttState},
connect, connect_fail, default_client, default_server, exchange_ticket, new_client, new_server,
send_something,
};
@@ -124,7 +124,7 @@ fn version_negotiation_only_reserved() {
assert_eq!(client.process(Some(&dgram), now()), Output::None);
match client.state() {
State::Closed(err) => {
- assert_eq!(*err, ConnectionError::Transport(Error::VersionNegotiation));
+ assert_eq!(*err, CloseReason::Transport(Error::VersionNegotiation));
}
_ => panic!("Invalid client state"),
}
@@ -183,7 +183,7 @@ fn version_negotiation_not_supported() {
assert_eq!(client.process(Some(&dgram), now()), Output::None);
match client.state() {
State::Closed(err) => {
- assert_eq!(*err, ConnectionError::Transport(Error::VersionNegotiation));
+ assert_eq!(*err, CloseReason::Transport(Error::VersionNegotiation));
}
_ => panic!("Invalid client state"),
}
@@ -338,7 +338,7 @@ fn invalid_server_version() {
// The server effectively hasn't reacted here.
match server.state() {
State::Closed(err) => {
- assert_eq!(*err, ConnectionError::Transport(Error::CryptoAlert(47)));
+ assert_eq!(*err, CloseReason::Transport(Error::CryptoAlert(47)));
}
_ => panic!("invalid server state"),
}
diff --git a/third_party/rust/neqo-transport/src/ecn.rs b/third_party/rust/neqo-transport/src/ecn.rs
new file mode 100644
index 0000000000..20eb4da003
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/ecn.rs
@@ -0,0 +1,225 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::ops::{AddAssign, Deref, DerefMut, Sub};
+
+use enum_map::EnumMap;
+use neqo_common::{qdebug, qinfo, qwarn, IpTosEcn};
+
+use crate::{packet::PacketNumber, tracking::SentPacket};
+
+/// The number of packets to use for testing a path for ECN capability.
+pub const ECN_TEST_COUNT: usize = 10;
+
+/// The state information related to testing a path for ECN capability.
+/// See RFC9000, Appendix A.4.
+#[derive(Debug, PartialEq, Clone)]
+enum EcnValidationState {
+ /// The path is currently being tested for ECN capability, with the number of probes sent so
+ /// far on the path during the ECN validation.
+ Testing(usize),
+ /// The validation test has concluded but the path's ECN capability is not yet known.
+ Unknown,
+ /// The path is known to **not** be ECN capable.
+ Failed,
+ /// The path is known to be ECN capable.
+ Capable,
+}
+
+impl Default for EcnValidationState {
+ fn default() -> Self {
+ EcnValidationState::Testing(0)
+ }
+}
+
+/// The counts for different ECN marks.
+#[derive(PartialEq, Eq, Debug, Clone, Copy, Default)]
+pub struct EcnCount(EnumMap<IpTosEcn, u64>);
+
+impl Deref for EcnCount {
+ type Target = EnumMap<IpTosEcn, u64>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl DerefMut for EcnCount {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+impl EcnCount {
+ pub fn new(not_ect: u64, ect0: u64, ect1: u64, ce: u64) -> Self {
+ // Yes, the enum array order is different from the argument order.
+ Self(EnumMap::from_array([not_ect, ect1, ect0, ce]))
+ }
+
+ /// Whether any of the ECN counts are non-zero.
+ pub fn is_some(&self) -> bool {
+ self[IpTosEcn::Ect0] > 0 || self[IpTosEcn::Ect1] > 0 || self[IpTosEcn::Ce] > 0
+ }
+}
+
+impl Sub<EcnCount> for EcnCount {
+ type Output = EcnCount;
+
+ /// Subtract the ECN counts in `other` from `self`.
+ fn sub(self, other: EcnCount) -> EcnCount {
+ let mut diff = EcnCount::default();
+ for (ecn, count) in &mut *diff {
+ *count = self[ecn].saturating_sub(other[ecn]);
+ }
+ diff
+ }
+}
+
+impl AddAssign<IpTosEcn> for EcnCount {
+ fn add_assign(&mut self, ecn: IpTosEcn) {
+ self[ecn] += 1;
+ }
+}
+
+#[derive(Debug, Default)]
+pub struct EcnInfo {
+ /// The current state of ECN validation on this path.
+ state: EcnValidationState,
+
+ /// The largest ACK seen so far.
+ largest_acked: PacketNumber,
+
+ /// The ECN counts from the last ACK frame that increased `largest_acked`.
+ baseline: EcnCount,
+}
+
+impl EcnInfo {
+ /// Set the baseline (= the ECN counts from the last ACK Frame).
+ pub fn set_baseline(&mut self, baseline: EcnCount) {
+ self.baseline = baseline;
+ }
+
+ /// Expose the current baseline.
+ pub fn baseline(&self) -> EcnCount {
+ self.baseline
+ }
+
+ /// Count the number of packets sent out on this path during ECN validation.
+ /// Exit ECN validation if the number of packets sent exceeds `ECN_TEST_COUNT`.
+ /// We do not implement the part of the RFC that says to exit ECN validation if the time since
+ /// the start of ECN validation exceeds 3 * PTO, since this seems to happen much too quickly.
+ pub fn on_packet_sent(&mut self) {
+ if let EcnValidationState::Testing(ref mut probes_sent) = &mut self.state {
+ *probes_sent += 1;
+ qdebug!("ECN probing: sent {} probes", probes_sent);
+ if *probes_sent == ECN_TEST_COUNT {
+ qdebug!("ECN probing concluded with {} probes sent", probes_sent);
+ self.state = EcnValidationState::Unknown;
+ }
+ }
+ }
+
+ /// Process ECN counts from an ACK frame.
+ ///
+ /// Returns whether ECN counts contain new valid ECN CE marks.
+ pub fn on_packets_acked(
+ &mut self,
+ acked_packets: &[SentPacket],
+ ack_ecn: Option<EcnCount>,
+ ) -> bool {
+ let prev_baseline = self.baseline;
+
+ self.validate_ack_ecn_and_update(acked_packets, ack_ecn);
+
+ matches!(self.state, EcnValidationState::Capable)
+ && (self.baseline - prev_baseline)[IpTosEcn::Ce] > 0
+ }
+
+ /// After the ECN validation test has ended, check if the path is ECN capable.
+ pub fn validate_ack_ecn_and_update(
+ &mut self,
+ acked_packets: &[SentPacket],
+ ack_ecn: Option<EcnCount>,
+ ) {
+ // RFC 9000, Appendix A.4:
+ //
+ // > From the "unknown" state, successful validation of the ECN counts in an ACK frame
+ // > (see Section 13.4.2.1) causes the ECN state for the path to become "capable", unless
+ // > no marked packet has been acknowledged.
+ match self.state {
+ EcnValidationState::Testing { .. } | EcnValidationState::Failed => return,
+ EcnValidationState::Unknown | EcnValidationState::Capable => {}
+ }
+
+ // RFC 9000, Section 13.4.2.1:
+ //
+ // > Validating ECN counts from reordered ACK frames can result in failure. An endpoint MUST
+ // > NOT fail ECN validation as a result of processing an ACK frame that does not increase
+ // > the largest acknowledged packet number.
+ let largest_acked = acked_packets.first().expect("must be there").pn;
+ if largest_acked <= self.largest_acked {
+ return;
+ }
+
+ // RFC 9000, Section 13.4.2.1:
+ //
+ // > An endpoint that receives an ACK frame with ECN counts therefore validates
+ // > the counts before using them. It performs this validation by comparing newly
+ // > received counts against those from the last successfully processed ACK frame.
+ //
+ // > If an ACK frame newly acknowledges a packet that the endpoint sent with
+ // > either the ECT(0) or ECT(1) codepoint set, ECN validation fails if the
+ // > corresponding ECN counts are not present in the ACK frame.
+ let Some(ack_ecn) = ack_ecn else {
+ qwarn!("ECN validation failed, no ECN counts in ACK frame");
+ self.state = EcnValidationState::Failed;
+ return;
+ };
+
+ // We always mark with ECT(0) - if at all - so we only need to check for that.
+ //
+ // > ECN validation also fails if the sum of the increase in ECT(0) and ECN-CE counts is
+ // > less than the number of newly acknowledged packets that were originally sent with an
+ // > ECT(0) marking.
+ let newly_acked_sent_with_ect0: u64 = acked_packets
+ .iter()
+ .filter(|p| p.ecn_mark == IpTosEcn::Ect0)
+ .count()
+ .try_into()
+ .unwrap();
+ if newly_acked_sent_with_ect0 == 0 {
+ qwarn!("ECN validation failed, no ECT(0) packets were newly acked");
+ self.state = EcnValidationState::Failed;
+ return;
+ }
+ let ecn_diff = ack_ecn - self.baseline;
+ let sum_inc = ecn_diff[IpTosEcn::Ect0] + ecn_diff[IpTosEcn::Ce];
+ if sum_inc < newly_acked_sent_with_ect0 {
+ qwarn!(
+ "ECN validation failed, ACK counted {} new marks, but {} of newly acked packets were sent with ECT(0)",
+ sum_inc,
+ newly_acked_sent_with_ect0
+ );
+ self.state = EcnValidationState::Failed;
+ } else if ecn_diff[IpTosEcn::Ect1] > 0 {
+ qwarn!("ECN validation failed, ACK counted ECT(1) marks that were never sent");
+ self.state = EcnValidationState::Failed;
+ } else {
+ qinfo!("ECN validation succeeded, path is capable",);
+ self.state = EcnValidationState::Capable;
+ }
+ self.baseline = ack_ecn;
+ self.largest_acked = largest_acked;
+ }
+
+ /// The ECN mark to use for packets sent on this path.
+ pub fn ecn_mark(&self) -> IpTosEcn {
+ match self.state {
+ EcnValidationState::Testing { .. } | EcnValidationState::Capable => IpTosEcn::Ect0,
+ EcnValidationState::Failed | EcnValidationState::Unknown => IpTosEcn::NotEct,
+ }
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/events.rs b/third_party/rust/neqo-transport/src/events.rs
index a892e384b9..68ef0d6798 100644
--- a/third_party/rust/neqo-transport/src/events.rs
+++ b/third_party/rust/neqo-transport/src/events.rs
@@ -256,7 +256,7 @@ impl EventProvider for ConnectionEvents {
mod tests {
use neqo_common::event::Provider;
- use crate::{ConnectionError, ConnectionEvent, ConnectionEvents, Error, State, StreamId};
+ use crate::{CloseReason, ConnectionEvent, ConnectionEvents, Error, State, StreamId};
#[test]
fn event_culling() {
@@ -314,7 +314,7 @@ mod tests {
evts.send_stream_writable(9.into());
evts.send_stream_stop_sending(10.into(), 55);
- evts.connection_state_change(State::Closed(ConnectionError::Transport(
+ evts.connection_state_change(State::Closed(CloseReason::Transport(
Error::StreamStateError,
)));
assert_eq!(evts.events().count(), 1);
diff --git a/third_party/rust/neqo-transport/src/frame.rs b/third_party/rust/neqo-transport/src/frame.rs
index d84eb61ce8..7d009f3b46 100644
--- a/third_party/rust/neqo-transport/src/frame.rs
+++ b/third_party/rust/neqo-transport/src/frame.rs
@@ -8,13 +8,14 @@
use std::ops::RangeInclusive;
-use neqo_common::{qtrace, Decoder};
+use neqo_common::{qtrace, Decoder, Encoder};
use crate::{
cid::MAX_CONNECTION_ID_LEN,
+ ecn::EcnCount,
packet::PacketType,
stream_id::{StreamId, StreamType},
- AppError, ConnectionError, Error, Res, TransportError,
+ AppError, CloseReason, Error, Res, TransportError,
};
#[allow(clippy::module_name_repetitions)]
@@ -23,7 +24,7 @@ pub type FrameType = u64;
pub const FRAME_TYPE_PADDING: FrameType = 0x0;
pub const FRAME_TYPE_PING: FrameType = 0x1;
pub const FRAME_TYPE_ACK: FrameType = 0x2;
-const FRAME_TYPE_ACK_ECN: FrameType = 0x3;
+pub const FRAME_TYPE_ACK_ECN: FrameType = 0x3;
pub const FRAME_TYPE_RESET_STREAM: FrameType = 0x4;
pub const FRAME_TYPE_STOP_SENDING: FrameType = 0x5;
pub const FRAME_TYPE_CRYPTO: FrameType = 0x6;
@@ -86,11 +87,11 @@ impl CloseError {
}
}
-impl From<ConnectionError> for CloseError {
- fn from(err: ConnectionError) -> Self {
+impl From<CloseReason> for CloseError {
+ fn from(err: CloseReason) -> Self {
match err {
- ConnectionError::Transport(c) => Self::Transport(c.code()),
- ConnectionError::Application(c) => Self::Application(c),
+ CloseReason::Transport(c) => Self::Transport(c.code()),
+ CloseReason::Application(c) => Self::Application(c),
}
}
}
@@ -116,6 +117,7 @@ pub enum Frame<'a> {
ack_delay: u64,
first_ack_range: u64,
ack_ranges: Vec<AckRange>,
+ ecn_count: Option<EcnCount>,
},
ResetStream {
stream_id: StreamId,
@@ -182,7 +184,7 @@ pub enum Frame<'a> {
frame_type: u64,
// Not a reference as we use this to hold the value.
// This is not used in optimized builds anyway.
- reason_phrase: Vec<u8>,
+ reason_phrase: String,
},
HandshakeDone,
AckFrequency {
@@ -224,7 +226,7 @@ impl<'a> Frame<'a> {
match self {
Self::Padding { .. } => FRAME_TYPE_PADDING,
Self::Ping => FRAME_TYPE_PING,
- Self::Ack { .. } => FRAME_TYPE_ACK, // We don't do ACK ECN.
+ Self::Ack { .. } => FRAME_TYPE_ACK,
Self::ResetStream { .. } => FRAME_TYPE_RESET_STREAM,
Self::StopSending { .. } => FRAME_TYPE_STOP_SENDING,
Self::Crypto { .. } => FRAME_TYPE_CRYPTO,
@@ -426,8 +428,54 @@ impl<'a> Frame<'a> {
d(dec.decode_varint())
}
- // TODO(ekr@rtfm.com): check for minimal encoding
+ fn decode_ack<'a>(dec: &mut Decoder<'a>, ecn: bool) -> Res<Frame<'a>> {
+ let la = dv(dec)?;
+ let ad = dv(dec)?;
+ let nr = dv(dec).and_then(|nr| {
+ if nr < MAX_ACK_RANGE_COUNT {
+ Ok(nr)
+ } else {
+ Err(Error::TooMuchData)
+ }
+ })?;
+ let fa = dv(dec)?;
+ let mut arr: Vec<AckRange> = Vec::with_capacity(usize::try_from(nr)?);
+ for _ in 0..nr {
+ let ar = AckRange {
+ gap: dv(dec)?,
+ range: dv(dec)?,
+ };
+ arr.push(ar);
+ }
+
+ // Now check for the values for ACK_ECN.
+ let ecn_count = if ecn {
+ Some(EcnCount::new(0, dv(dec)?, dv(dec)?, dv(dec)?))
+ } else {
+ None
+ };
+
+ Ok(Frame::Ack {
+ largest_acknowledged: la,
+ ack_delay: ad,
+ first_ack_range: fa,
+ ack_ranges: arr,
+ ecn_count,
+ })
+ }
+
+ // Check for minimal encoding of frame type.
+ let pos = dec.offset();
let t = dv(dec)?;
+ // RFC 9000, Section 12.4:
+ //
+ // The Frame Type field uses a variable-length integer encoding [...],
+ // with one exception. To ensure simple and efficient implementations of
+ // frame parsing, a frame type MUST use the shortest possible encoding.
+ if Encoder::varint_len(t) != dec.offset() - pos {
+ return Err(Error::ProtocolViolation);
+ }
+
match t {
FRAME_TYPE_PADDING => {
let mut length: u16 = 1;
@@ -449,40 +497,8 @@ impl<'a> Frame<'a> {
_ => return Err(Error::NoMoreData),
},
}),
- FRAME_TYPE_ACK | FRAME_TYPE_ACK_ECN => {
- let la = dv(dec)?;
- let ad = dv(dec)?;
- let nr = dv(dec).and_then(|nr| {
- if nr < MAX_ACK_RANGE_COUNT {
- Ok(nr)
- } else {
- Err(Error::TooMuchData)
- }
- })?;
- let fa = dv(dec)?;
- let mut arr: Vec<AckRange> = Vec::with_capacity(usize::try_from(nr)?);
- for _ in 0..nr {
- let ar = AckRange {
- gap: dv(dec)?,
- range: dv(dec)?,
- };
- arr.push(ar);
- }
-
- // Now check for the values for ACK_ECN.
- if t == FRAME_TYPE_ACK_ECN {
- dv(dec)?;
- dv(dec)?;
- dv(dec)?;
- }
-
- Ok(Self::Ack {
- largest_acknowledged: la,
- ack_delay: ad,
- first_ack_range: fa,
- ack_ranges: arr,
- })
- }
+ FRAME_TYPE_ACK => decode_ack(dec, false),
+ FRAME_TYPE_ACK_ECN => decode_ack(dec, true),
FRAME_TYPE_STOP_SENDING => Ok(Self::StopSending {
stream_id: StreamId::from(dv(dec)?),
application_error_code: dv(dec)?,
@@ -598,7 +614,7 @@ impl<'a> Frame<'a> {
0
};
// We can tolerate this copy for now.
- let reason_phrase = d(dec.decode_vvec())?.to_vec();
+ let reason_phrase = String::from_utf8_lossy(d(dec.decode_vvec())?).to_string();
Ok(Self::ConnectionClose {
error_code,
frame_type,
@@ -647,13 +663,14 @@ mod tests {
use crate::{
cid::MAX_CONNECTION_ID_LEN,
+ ecn::EcnCount,
frame::{AckRange, Frame, FRAME_TYPE_ACK},
CloseError, Error, StreamId, StreamType,
};
fn just_dec(f: &Frame, s: &str) {
let encoded = Encoder::from_hex(s);
- let decoded = Frame::decode(&mut encoded.as_decoder()).unwrap();
+ let decoded = Frame::decode(&mut encoded.as_decoder()).expect("Failed to decode frame");
assert_eq!(*f, decoded);
}
@@ -679,7 +696,8 @@ mod tests {
largest_acknowledged: 0x1234,
ack_delay: 0x1235,
first_ack_range: 0x1236,
- ack_ranges: ar,
+ ack_ranges: ar.clone(),
+ ecn_count: None,
};
just_dec(&f, "025234523502523601020304");
@@ -689,10 +707,18 @@ mod tests {
let mut dec = enc.as_decoder();
assert_eq!(Frame::decode(&mut dec).unwrap_err(), Error::NoMoreData);
- // Try to parse ACK_ECN without ECN values
+ // Try to parse ACK_ECN with ECN values
+ let ecn_count = Some(EcnCount::new(0, 1, 2, 3));
+ let fe = Frame::Ack {
+ largest_acknowledged: 0x1234,
+ ack_delay: 0x1235,
+ first_ack_range: 0x1236,
+ ack_ranges: ar,
+ ecn_count,
+ };
let enc = Encoder::from_hex("035234523502523601020304010203");
let mut dec = enc.as_decoder();
- assert_eq!(Frame::decode(&mut dec).unwrap(), f);
+ assert_eq!(Frame::decode(&mut dec).unwrap(), fe);
}
#[test]
@@ -899,7 +925,7 @@ mod tests {
let f = Frame::ConnectionClose {
error_code: CloseError::Transport(0x5678),
frame_type: 0x1234,
- reason_phrase: vec![0x01, 0x02, 0x03],
+ reason_phrase: String::from("\x01\x02\x03"),
};
just_dec(&f, "1c80005678523403010203");
@@ -910,7 +936,7 @@ mod tests {
let f = Frame::ConnectionClose {
error_code: CloseError::Application(0x5678),
frame_type: 0,
- reason_phrase: vec![0x01, 0x02, 0x03],
+ reason_phrase: String::from("\x01\x02\x03"),
};
just_dec(&f, "1d8000567803010203");
@@ -989,14 +1015,14 @@ mod tests {
fill: true,
};
- just_dec(&f, "4030010203");
+ just_dec(&f, "30010203");
// With the length bit.
let f = Frame::Datagram {
data: &[1, 2, 3],
fill: false,
};
- just_dec(&f, "403103010203");
+ just_dec(&f, "3103010203");
}
#[test]
@@ -1010,4 +1036,15 @@ mod tests {
assert_eq!(Err(Error::TooMuchData), Frame::decode(&mut e.as_decoder()));
}
+
+ #[test]
+ #[should_panic(expected = "Failed to decode frame")]
+ fn invalid_frame_type_len() {
+ let f = Frame::Datagram {
+ data: &[1, 2, 3],
+ fill: true,
+ };
+
+ just_dec(&f, "4030010203");
+ }
}
diff --git a/third_party/rust/neqo-transport/src/lib.rs b/third_party/rust/neqo-transport/src/lib.rs
index 5488472b58..723a86980e 100644
--- a/third_party/rust/neqo-transport/src/lib.rs
+++ b/third_party/rust/neqo-transport/src/lib.rs
@@ -15,10 +15,17 @@ mod cc;
mod cid;
mod connection;
mod crypto;
+mod ecn;
mod events;
mod fc;
+#[cfg(fuzzing)]
+pub mod frame;
+#[cfg(not(fuzzing))]
mod frame;
mod pace;
+#[cfg(fuzzing)]
+pub mod packet;
+#[cfg(not(fuzzing))]
mod packet;
mod path;
mod qlog;
@@ -202,13 +209,17 @@ impl ::std::fmt::Display for Error {
pub type AppError = u64;
+#[deprecated(note = "use `CloseReason` instead")]
+pub type ConnectionError = CloseReason;
+
+/// Reason why a connection closed.
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)]
-pub enum ConnectionError {
+pub enum CloseReason {
Transport(Error),
Application(AppError),
}
-impl ConnectionError {
+impl CloseReason {
#[must_use]
pub fn app_code(&self) -> Option<AppError> {
match self {
@@ -216,9 +227,19 @@ impl ConnectionError {
Self::Transport(_) => None,
}
}
+
+ /// Checks enclosed error for [`Error::NoError`] and
+ /// [`CloseReason::Application(0)`].
+ #[must_use]
+ pub fn is_error(&self) -> bool {
+ !matches!(
+ self,
+ CloseReason::Transport(Error::NoError) | CloseReason::Application(0),
+ )
+ }
}
-impl From<CloseError> for ConnectionError {
+impl From<CloseError> for CloseReason {
fn from(err: CloseError) -> Self {
match err {
CloseError::Transport(c) => Self::Transport(Error::PeerError(c)),
diff --git a/third_party/rust/neqo-transport/src/packet/mod.rs b/third_party/rust/neqo-transport/src/packet/mod.rs
index ce611a9664..10d9b13208 100644
--- a/third_party/rust/neqo-transport/src/packet/mod.rs
+++ b/third_party/rust/neqo-transport/src/packet/mod.rs
@@ -740,6 +740,7 @@ impl<'a> PublicPacket<'a> {
}
#[must_use]
+ #[allow(clippy::len_without_is_empty)] // is_empty() would always return false in this case
pub fn len(&self) -> usize {
self.data.len()
}
diff --git a/third_party/rust/neqo-transport/src/path.rs b/third_party/rust/neqo-transport/src/path.rs
index 50e458ff36..0e4c82b1ca 100644
--- a/third_party/rust/neqo-transport/src/path.rs
+++ b/third_party/rust/neqo-transport/src/path.rs
@@ -22,6 +22,7 @@ use crate::{
ackrate::{AckRate, PeerAckDelay},
cc::CongestionControlAlgorithm,
cid::{ConnectionId, ConnectionIdRef, ConnectionIdStore, RemoteConnectionIdEntry},
+ ecn::{EcnCount, EcnInfo},
frame::{FRAME_TYPE_PATH_CHALLENGE, FRAME_TYPE_PATH_RESPONSE, FRAME_TYPE_RETIRE_CONNECTION_ID},
packet::PacketBuilder,
recovery::RecoveryToken,
@@ -145,15 +146,8 @@ impl Paths {
})
}
- /// Get a reference to the primary path. This will assert if there is no primary
- /// path, which happens at a server prior to receiving a valid Initial packet
- /// from a client. So be careful using this method.
- pub fn primary(&self) -> PathRef {
- self.primary_fallible().unwrap()
- }
-
- /// Get a reference to the primary path. Use this prior to handshake completion.
- pub fn primary_fallible(&self) -> Option<PathRef> {
+ /// Get a reference to the primary path, if one exists.
+ pub fn primary(&self) -> Option<PathRef> {
self.primary.clone()
}
@@ -242,6 +236,11 @@ impl Paths {
/// Returns `true` if the path was migrated.
pub fn migrate(&mut self, path: &PathRef, force: bool, now: Instant) -> bool {
debug_assert!(!self.is_temporary(path));
+ let baseline = self.primary().map_or_else(
+ || EcnInfo::default().baseline(),
+ |p| p.borrow().ecn_info.baseline(),
+ );
+ path.borrow_mut().set_ecn_baseline(baseline);
if force || path.borrow().is_valid() {
path.borrow_mut().set_valid(now);
mem::drop(self.select_primary(path));
@@ -307,7 +306,6 @@ impl Paths {
/// Set the identified path to be primary.
/// This panics if `make_permanent` hasn't been called.
pub fn handle_migration(&mut self, path: &PathRef, remote: SocketAddr, now: Instant) {
- qtrace!([self.primary().borrow()], "handle_migration");
// The update here needs to match the checks in `Path::received_on`.
// Here, we update the remote port number to match the source port on the
// datagram that was received. This ensures that we send subsequent
@@ -425,10 +423,10 @@ impl Paths {
stats.retire_connection_id += 1;
}
- // Write out any ACK_FREQUENCY frames.
- self.primary()
- .borrow_mut()
- .write_cc_frames(builder, tokens, stats);
+ if let Some(path) = self.primary() {
+ // Write out any ACK_FREQUENCY frames.
+ path.borrow_mut().write_cc_frames(builder, tokens, stats);
+ }
}
pub fn lost_retire_cid(&mut self, lost: u64) {
@@ -440,11 +438,15 @@ impl Paths {
}
pub fn lost_ack_frequency(&mut self, lost: &AckRate) {
- self.primary().borrow_mut().lost_ack_frequency(lost);
+ if let Some(path) = self.primary() {
+ path.borrow_mut().lost_ack_frequency(lost);
+ }
}
pub fn acked_ack_frequency(&mut self, acked: &AckRate) {
- self.primary().borrow_mut().acked_ack_frequency(acked);
+ if let Some(path) = self.primary() {
+ path.borrow_mut().acked_ack_frequency(acked);
+ }
}
/// Get an estimate of the RTT on the primary path.
@@ -454,7 +456,7 @@ impl Paths {
// make a new RTT esimate and interrogate that.
// That is more expensive, but it should be rare and breaking encapsulation
// is worse, especially as this is only used in tests.
- self.primary_fallible()
+ self.primary()
.map_or(RttEstimate::default().estimate(), |p| {
p.borrow().rtt().estimate()
})
@@ -532,8 +534,6 @@ pub struct Path {
rtt: RttEstimate,
/// A packet sender for the path, which includes congestion control and a pacer.
sender: PacketSender,
- /// The DSCP/ECN marking to use for outgoing packets on this path.
- tos: IpTos,
/// The IP TTL to use for outgoing packets on this path.
ttl: u8,
@@ -543,7 +543,8 @@ pub struct Path {
received_bytes: usize,
/// The number of bytes sent on this path.
sent_bytes: usize,
-
+ /// The ECN-related state for this path (see RFC9000, Section 13.4 and Appendix A.4)
+ ecn_info: EcnInfo,
/// For logging of events.
qlog: NeqoQlog,
}
@@ -572,14 +573,23 @@ impl Path {
challenge: None,
rtt: RttEstimate::default(),
sender,
- tos: IpTos::default(), // TODO: Default to Ect0 when ECN is supported.
- ttl: 64, // This is the default TTL on many OSes.
+ ttl: 64, // This is the default TTL on many OSes.
received_bytes: 0,
sent_bytes: 0,
+ ecn_info: EcnInfo::default(),
qlog,
}
}
+ pub fn set_ecn_baseline(&mut self, baseline: EcnCount) {
+ self.ecn_info.set_baseline(baseline);
+ }
+
+ /// Return the DSCP/ECN marking to use for outgoing packets on this path.
+ pub fn tos(&self) -> IpTos {
+ self.ecn_info.ecn_mark().into()
+ }
+
/// Whether this path is the primary or current path for the connection.
pub fn is_primary(&self) -> bool {
self.primary
@@ -695,8 +705,9 @@ impl Path {
}
/// Make a datagram.
- pub fn datagram<V: Into<Vec<u8>>>(&self, payload: V) -> Datagram {
- Datagram::new(self.local, self.remote, self.tos, Some(self.ttl), payload)
+ pub fn datagram<V: Into<Vec<u8>>>(&mut self, payload: V) -> Datagram {
+ self.ecn_info.on_packet_sent();
+ Datagram::new(self.local, self.remote, self.tos(), Some(self.ttl), payload)
}
/// Get local address as `SocketAddr`
@@ -959,8 +970,24 @@ impl Path {
}
/// Record packets as acknowledged with the sender.
- pub fn on_packets_acked(&mut self, acked_pkts: &[SentPacket], now: Instant) {
+ pub fn on_packets_acked(
+ &mut self,
+ acked_pkts: &[SentPacket],
+ ack_ecn: Option<EcnCount>,
+ now: Instant,
+ ) {
debug_assert!(self.is_primary());
+
+ let ecn_ce_received = self.ecn_info.on_packets_acked(acked_pkts, ack_ecn);
+ if ecn_ce_received {
+ let cwnd_reduced = self
+ .sender
+ .on_ecn_ce_received(acked_pkts.first().expect("must be there"));
+ if cwnd_reduced {
+ self.rtt.update_ack_delay(self.sender.cwnd(), self.mtu());
+ }
+ }
+
self.sender.on_packets_acked(acked_pkts, &self.rtt, now);
}
diff --git a/third_party/rust/neqo-transport/src/qlog.rs b/third_party/rust/neqo-transport/src/qlog.rs
index a8ad986d2a..715ba85e81 100644
--- a/third_party/rust/neqo-transport/src/qlog.rs
+++ b/third_party/rust/neqo-transport/src/qlog.rs
@@ -11,7 +11,7 @@ use std::{
time::Duration,
};
-use neqo_common::{hex, qinfo, qlog::NeqoQlog, Decoder};
+use neqo_common::{hex, qinfo, qlog::NeqoQlog, Decoder, IpTosEcn};
use qlog::events::{
connectivity::{ConnectionStarted, ConnectionState, ConnectionStateUpdated},
quic::{
@@ -205,7 +205,7 @@ pub fn packet_sent(
let mut frames = SmallVec::new();
while d.remaining() > 0 {
if let Ok(f) = Frame::decode(&mut d) {
- frames.push(QuicFrame::from(&f));
+ frames.push(QuicFrame::from(f));
} else {
qinfo!("qlog: invalid frame");
break;
@@ -293,7 +293,7 @@ pub fn packet_received(
while d.remaining() > 0 {
if let Ok(f) = Frame::decode(&mut d) {
- frames.push(QuicFrame::from(&f));
+ frames.push(QuicFrame::from(f));
} else {
qinfo!("qlog: invalid frame");
break;
@@ -387,21 +387,26 @@ pub fn metrics_updated(qlog: &mut NeqoQlog, updated_metrics: &[QlogMetric]) {
#[allow(clippy::too_many_lines)] // Yeah, but it's a nice match.
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)] // No choice here.
-impl From<&Frame<'_>> for QuicFrame {
- fn from(frame: &Frame) -> Self {
+impl From<Frame<'_>> for QuicFrame {
+ fn from(frame: Frame) -> Self {
match frame {
- // TODO: Add payload length to `QuicFrame::Padding` once
- // https://github.com/cloudflare/quiche/pull/1745 is available via the qlog crate.
- Frame::Padding { .. } => QuicFrame::Padding,
- Frame::Ping => QuicFrame::Ping,
+ Frame::Padding(len) => QuicFrame::Padding {
+ length: None,
+ payload_length: u32::from(len),
+ },
+ Frame::Ping => QuicFrame::Ping {
+ length: None,
+ payload_length: None,
+ },
Frame::Ack {
largest_acknowledged,
ack_delay,
first_ack_range,
ack_ranges,
+ ecn_count,
} => {
let ranges =
- Frame::decode_ack_frame(*largest_acknowledged, *first_ack_range, ack_ranges)
+ Frame::decode_ack_frame(largest_acknowledged, first_ack_range, &ack_ranges)
.ok();
let acked_ranges = ranges.map(|all| {
@@ -413,11 +418,13 @@ impl From<&Frame<'_>> for QuicFrame {
});
QuicFrame::Ack {
- ack_delay: Some(*ack_delay as f32 / 1000.0),
+ ack_delay: Some(ack_delay as f32 / 1000.0),
acked_ranges,
- ect1: None,
- ect0: None,
- ce: None,
+ ect1: ecn_count.map(|c| c[IpTosEcn::Ect1]),
+ ect0: ecn_count.map(|c| c[IpTosEcn::Ect0]),
+ ce: ecn_count.map(|c| c[IpTosEcn::Ce]),
+ length: None,
+ payload_length: None,
}
}
Frame::ResetStream {
@@ -426,18 +433,22 @@ impl From<&Frame<'_>> for QuicFrame {
final_size,
} => QuicFrame::ResetStream {
stream_id: stream_id.as_u64(),
- error_code: *application_error_code,
- final_size: *final_size,
+ error_code: application_error_code,
+ final_size,
+ length: None,
+ payload_length: None,
},
Frame::StopSending {
stream_id,
application_error_code,
} => QuicFrame::StopSending {
stream_id: stream_id.as_u64(),
- error_code: *application_error_code,
+ error_code: application_error_code,
+ length: None,
+ payload_length: None,
},
Frame::Crypto { offset, data } => QuicFrame::Crypto {
- offset: *offset,
+ offset,
length: data.len() as u64,
},
Frame::NewToken { token } => QuicFrame::NewToken {
@@ -459,20 +470,20 @@ impl From<&Frame<'_>> for QuicFrame {
..
} => QuicFrame::Stream {
stream_id: stream_id.as_u64(),
- offset: *offset,
+ offset,
length: data.len() as u64,
- fin: Some(*fin),
+ fin: Some(fin),
raw: None,
},
Frame::MaxData { maximum_data } => QuicFrame::MaxData {
- maximum: *maximum_data,
+ maximum: maximum_data,
},
Frame::MaxStreamData {
stream_id,
maximum_stream_data,
} => QuicFrame::MaxStreamData {
stream_id: stream_id.as_u64(),
- maximum: *maximum_stream_data,
+ maximum: maximum_stream_data,
},
Frame::MaxStreams {
stream_type,
@@ -482,15 +493,15 @@ impl From<&Frame<'_>> for QuicFrame {
NeqoStreamType::BiDi => StreamType::Bidirectional,
NeqoStreamType::UniDi => StreamType::Unidirectional,
},
- maximum: *maximum_streams,
+ maximum: maximum_streams,
},
- Frame::DataBlocked { data_limit } => QuicFrame::DataBlocked { limit: *data_limit },
+ Frame::DataBlocked { data_limit } => QuicFrame::DataBlocked { limit: data_limit },
Frame::StreamDataBlocked {
stream_id,
stream_data_limit,
} => QuicFrame::StreamDataBlocked {
stream_id: stream_id.as_u64(),
- limit: *stream_data_limit,
+ limit: stream_data_limit,
},
Frame::StreamsBlocked {
stream_type,
@@ -500,7 +511,7 @@ impl From<&Frame<'_>> for QuicFrame {
NeqoStreamType::BiDi => StreamType::Bidirectional,
NeqoStreamType::UniDi => StreamType::Unidirectional,
},
- limit: *stream_limit,
+ limit: stream_limit,
},
Frame::NewConnectionId {
sequence_number,
@@ -508,14 +519,14 @@ impl From<&Frame<'_>> for QuicFrame {
connection_id,
stateless_reset_token,
} => QuicFrame::NewConnectionId {
- sequence_number: *sequence_number as u32,
- retire_prior_to: *retire_prior as u32,
+ sequence_number: sequence_number as u32,
+ retire_prior_to: retire_prior as u32,
connection_id_length: Some(connection_id.len() as u8),
connection_id: hex(connection_id),
stateless_reset_token: Some(hex(stateless_reset_token)),
},
Frame::RetireConnectionId { sequence_number } => QuicFrame::RetireConnectionId {
- sequence_number: *sequence_number as u32,
+ sequence_number: sequence_number as u32,
},
Frame::PathChallenge { data } => QuicFrame::PathChallenge {
data: Some(hex(data)),
@@ -534,8 +545,8 @@ impl From<&Frame<'_>> for QuicFrame {
},
error_code: Some(error_code.code()),
error_code_value: Some(0),
- reason: Some(String::from_utf8_lossy(reason_phrase).to_string()),
- trigger_frame_type: Some(*frame_type),
+ reason: Some(reason_phrase),
+ trigger_frame_type: Some(frame_type),
},
Frame::HandshakeDone => QuicFrame::HandshakeDone,
Frame::AckFrequency { .. } => QuicFrame::Unknown {
diff --git a/third_party/rust/neqo-transport/src/recovery.rs b/third_party/rust/neqo-transport/src/recovery.rs
index dbea3aaf57..22a635d9f3 100644
--- a/third_party/rust/neqo-transport/src/recovery.rs
+++ b/third_party/rust/neqo-transport/src/recovery.rs
@@ -21,6 +21,7 @@ use crate::{
ackrate::AckRate,
cid::ConnectionIdEntry,
crypto::CryptoRecoveryToken,
+ ecn::EcnCount,
packet::PacketNumber,
path::{Path, PathRef},
qlog::{self, QlogMetric},
@@ -665,12 +666,14 @@ impl LossRecovery {
}
/// Returns (acked packets, lost packets)
+ #[allow(clippy::too_many_arguments)]
pub fn on_ack_received<R>(
&mut self,
primary_path: &PathRef,
pn_space: PacketNumberSpace,
largest_acked: u64,
acked_ranges: R,
+ ack_ecn: Option<EcnCount>,
ack_delay: Duration,
now: Instant,
) -> (Vec<SentPacket>, Vec<SentPacket>)
@@ -692,10 +695,10 @@ impl LossRecovery {
let (acked_packets, any_ack_eliciting) =
space.remove_acked(acked_ranges, &mut self.stats.borrow_mut());
- if acked_packets.is_empty() {
+ let Some(largest_acked_pkt) = acked_packets.first() else {
// No new information.
return (Vec::new(), Vec::new());
- }
+ };
// Track largest PN acked per space
let prev_largest_acked = space.largest_acked_sent_time;
@@ -704,7 +707,6 @@ impl LossRecovery {
// If the largest acknowledged is newly acked and any newly acked
// packet was ack-eliciting, update the RTT. (-recovery 5.1)
- let largest_acked_pkt = acked_packets.first().expect("must be there");
space.largest_acked_sent_time = Some(largest_acked_pkt.time_sent);
if any_ack_eliciting && largest_acked_pkt.on_primary_path() {
self.rtt_sample(
@@ -744,7 +746,7 @@ impl LossRecovery {
// when it shouldn't.
primary_path
.borrow_mut()
- .on_packets_acked(&acked_packets, now);
+ .on_packets_acked(&acked_packets, ack_ecn, now);
self.pto_state = None;
@@ -1022,7 +1024,7 @@ mod tests {
time::{Duration, Instant},
};
- use neqo_common::qlog::NeqoQlog;
+ use neqo_common::{qlog::NeqoQlog, IpTosEcn};
use test_fixture::{now, DEFAULT_ADDR};
use super::{
@@ -1031,6 +1033,7 @@ mod tests {
use crate::{
cc::CongestionControlAlgorithm,
cid::{ConnectionId, ConnectionIdEntry},
+ ecn::EcnCount,
packet::PacketType,
path::{Path, PathRef},
rtt::RttEstimate,
@@ -1060,6 +1063,7 @@ mod tests {
pn_space: PacketNumberSpace,
largest_acked: u64,
acked_ranges: Vec<RangeInclusive<u64>>,
+ ack_ecn: Option<EcnCount>,
ack_delay: Duration,
now: Instant,
) -> (Vec<SentPacket>, Vec<SentPacket>) {
@@ -1068,6 +1072,7 @@ mod tests {
pn_space,
largest_acked,
acked_ranges,
+ ack_ecn,
ack_delay,
now,
)
@@ -1208,6 +1213,7 @@ mod tests {
lr.on_packet_sent(SentPacket::new(
PacketType::Short,
pn,
+ IpTosEcn::default(),
pn_time(pn),
true,
Vec::new(),
@@ -1223,6 +1229,7 @@ mod tests {
PacketNumberSpace::ApplicationData,
pn,
vec![pn..=pn],
+ None,
ACK_DELAY,
pn_time(pn) + delay,
);
@@ -1233,6 +1240,7 @@ mod tests {
lrs.on_packet_sent(SentPacket::new(
PacketType::Short,
pn,
+ IpTosEcn::default(),
pn_time(pn),
true,
Vec::new(),
@@ -1353,6 +1361,7 @@ mod tests {
lr.on_packet_sent(SentPacket::new(
PacketType::Short,
0,
+ IpTosEcn::default(),
pn_time(0),
true,
Vec::new(),
@@ -1361,6 +1370,7 @@ mod tests {
lr.on_packet_sent(SentPacket::new(
PacketType::Short,
1,
+ IpTosEcn::default(),
pn_time(0) + TEST_RTT / 4,
true,
Vec::new(),
@@ -1370,6 +1380,7 @@ mod tests {
PacketNumberSpace::ApplicationData,
1,
vec![1..=1],
+ None,
ACK_DELAY,
pn_time(0) + (TEST_RTT * 5 / 4),
);
@@ -1393,6 +1404,7 @@ mod tests {
PacketNumberSpace::ApplicationData,
2,
vec![2..=2],
+ None,
ACK_DELAY,
pn2_ack_time,
);
@@ -1422,6 +1434,7 @@ mod tests {
PacketNumberSpace::ApplicationData,
4,
vec![2..=4],
+ None,
ACK_DELAY,
pn_time(4),
);
@@ -1450,6 +1463,7 @@ mod tests {
PacketNumberSpace::Initial,
0,
vec![],
+ None,
Duration::from_millis(0),
pn_time(0),
);
@@ -1463,6 +1477,7 @@ mod tests {
lr.on_packet_sent(SentPacket::new(
PacketType::Initial,
0,
+ IpTosEcn::default(),
pn_time(0),
true,
Vec::new(),
@@ -1471,6 +1486,7 @@ mod tests {
lr.on_packet_sent(SentPacket::new(
PacketType::Handshake,
0,
+ IpTosEcn::default(),
pn_time(1),
true,
Vec::new(),
@@ -1479,6 +1495,7 @@ mod tests {
lr.on_packet_sent(SentPacket::new(
PacketType::Short,
0,
+ IpTosEcn::default(),
pn_time(2),
true,
Vec::new(),
@@ -1491,10 +1508,25 @@ mod tests {
PacketType::Handshake,
PacketType::Short,
] {
- let sent_pkt = SentPacket::new(*sp, 1, pn_time(3), true, Vec::new(), ON_SENT_SIZE);
+ let sent_pkt = SentPacket::new(
+ *sp,
+ 1,
+ IpTosEcn::default(),
+ pn_time(3),
+ true,
+ Vec::new(),
+ ON_SENT_SIZE,
+ );
let pn_space = PacketNumberSpace::from(sent_pkt.pt);
lr.on_packet_sent(sent_pkt);
- lr.on_ack_received(pn_space, 1, vec![1..=1], Duration::from_secs(0), pn_time(3));
+ lr.on_ack_received(
+ pn_space,
+ 1,
+ vec![1..=1],
+ None,
+ Duration::from_secs(0),
+ pn_time(3),
+ );
let mut lost = Vec::new();
lr.spaces.get_mut(pn_space).unwrap().detect_lost_packets(
pn_time(3),
@@ -1516,6 +1548,7 @@ mod tests {
lr.on_packet_sent(SentPacket::new(
PacketType::Initial,
0,
+ IpTosEcn::default(),
pn_time(3),
true,
Vec::new(),
@@ -1530,6 +1563,7 @@ mod tests {
lr.on_packet_sent(SentPacket::new(
PacketType::Initial,
0,
+ IpTosEcn::default(),
now(),
true,
Vec::new(),
@@ -1542,6 +1576,7 @@ mod tests {
PacketNumberSpace::Initial,
0,
vec![0..=0],
+ None,
Duration::new(0, 0),
now() + rtt,
);
@@ -1549,6 +1584,7 @@ mod tests {
lr.on_packet_sent(SentPacket::new(
PacketType::Handshake,
0,
+ IpTosEcn::default(),
now(),
true,
Vec::new(),
@@ -1557,6 +1593,7 @@ mod tests {
lr.on_packet_sent(SentPacket::new(
PacketType::Short,
0,
+ IpTosEcn::default(),
now(),
true,
Vec::new(),
@@ -1594,6 +1631,7 @@ mod tests {
lr.on_packet_sent(SentPacket::new(
PacketType::Initial,
1,
+ IpTosEcn::default(),
now(),
true,
Vec::new(),
diff --git a/third_party/rust/neqo-transport/src/send_stream.rs b/third_party/rust/neqo-transport/src/send_stream.rs
index 8771ec7765..98476e9d18 100644
--- a/third_party/rust/neqo-transport/src/send_stream.rs
+++ b/third_party/rust/neqo-transport/src/send_stream.rs
@@ -1269,7 +1269,7 @@ impl SendStream {
return Err(Error::FinalSizeError);
}
- let buf = if buf.is_empty() || (self.avail() == 0) {
+ let buf = if self.avail() == 0 {
return Ok(0);
} else if self.avail() < buf.len() {
if atomic {
@@ -1634,20 +1634,16 @@ impl SendStreams {
}
pub fn remove_terminal(&mut self) {
- let map: &mut IndexMap<StreamId, SendStream> = &mut self.map;
- let regular: &mut OrderGroup = &mut self.regular;
- let sendordered: &mut BTreeMap<SendOrder, OrderGroup> = &mut self.sendordered;
-
- // Take refs to all the items we need to modify instead of &mut
- // self to keep the compiler happy (if we use self.map.retain it
- // gets upset due to borrows)
- map.retain(|stream_id, stream| {
+ self.map.retain(|stream_id, stream| {
if stream.is_terminal() {
if stream.is_fair() {
match stream.sendorder() {
- None => regular.remove(*stream_id),
+ None => self.regular.remove(*stream_id),
Some(sendorder) => {
- sendordered.get_mut(&sendorder).unwrap().remove(*stream_id);
+ self.sendordered
+ .get_mut(&sendorder)
+ .unwrap()
+ .remove(*stream_id);
}
};
}
diff --git a/third_party/rust/neqo-transport/src/sender.rs b/third_party/rust/neqo-transport/src/sender.rs
index 3a54851533..abb14d0a25 100644
--- a/third_party/rust/neqo-transport/src/sender.rs
+++ b/third_party/rust/neqo-transport/src/sender.rs
@@ -97,6 +97,11 @@ impl PacketSender {
)
}
+ /// Called when ECN CE mark received. Returns true if the congestion window was reduced.
+ pub fn on_ecn_ce_received(&mut self, largest_acked_pkt: &SentPacket) -> bool {
+ self.cc.on_ecn_ce_received(largest_acked_pkt)
+ }
+
pub fn discard(&mut self, pkt: &SentPacket) {
self.cc.discard(pkt);
}
diff --git a/third_party/rust/neqo-transport/src/server.rs b/third_party/rust/neqo-transport/src/server.rs
index 96a6244ef1..60909d71e1 100644
--- a/third_party/rust/neqo-transport/src/server.rs
+++ b/third_party/rust/neqo-transport/src/server.rs
@@ -689,6 +689,13 @@ impl Server {
mem::take(&mut self.active).into_iter().collect()
}
+ /// Whether any connections have received new events as a result of calling
+ /// `process()`.
+ #[must_use]
+ pub fn has_active_connections(&self) -> bool {
+ !self.active.is_empty()
+ }
+
pub fn add_to_waiting(&mut self, c: &ActiveConnectionRef) {
self.waiting.push_back(c.connection());
}
diff --git a/third_party/rust/neqo-transport/src/tracking.rs b/third_party/rust/neqo-transport/src/tracking.rs
index bdd0f250c7..6643d516e3 100644
--- a/third_party/rust/neqo-transport/src/tracking.rs
+++ b/third_party/rust/neqo-transport/src/tracking.rs
@@ -13,18 +13,21 @@ use std::{
time::{Duration, Instant},
};
-use neqo_common::{qdebug, qinfo, qtrace, qwarn};
+use enum_map::Enum;
+use neqo_common::{qdebug, qinfo, qtrace, qwarn, IpTosEcn};
use neqo_crypto::{Epoch, TLS_EPOCH_HANDSHAKE, TLS_EPOCH_INITIAL};
use smallvec::{smallvec, SmallVec};
use crate::{
+ ecn::EcnCount,
+ frame::{FRAME_TYPE_ACK, FRAME_TYPE_ACK_ECN},
packet::{PacketBuilder, PacketNumber, PacketType},
recovery::RecoveryToken,
stats::FrameStats,
};
// TODO(mt) look at enabling EnumMap for this: https://stackoverflow.com/a/44905797/1375574
-#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq)]
+#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Enum)]
pub enum PacketNumberSpace {
Initial,
Handshake,
@@ -134,6 +137,7 @@ impl std::fmt::Debug for PacketNumberSpaceSet {
pub struct SentPacket {
pub pt: PacketType,
pub pn: PacketNumber,
+ pub ecn_mark: IpTosEcn,
ack_eliciting: bool,
pub time_sent: Instant,
primary_path: bool,
@@ -150,6 +154,7 @@ impl SentPacket {
pub fn new(
pt: PacketType,
pn: PacketNumber,
+ ecn_mark: IpTosEcn,
time_sent: Instant,
ack_eliciting: bool,
tokens: Vec<RecoveryToken>,
@@ -158,6 +163,7 @@ impl SentPacket {
Self {
pt,
pn,
+ ecn_mark,
time_sent,
ack_eliciting,
primary_path: true,
@@ -377,6 +383,8 @@ pub struct RecvdPackets {
/// Whether we are ignoring packets that arrive out of order
/// for the purposes of generating immediate acknowledgment.
ignore_order: bool,
+ // The counts of different ECN marks that have been received.
+ ecn_count: EcnCount,
}
impl RecvdPackets {
@@ -394,9 +402,15 @@ impl RecvdPackets {
unacknowledged_count: 0,
unacknowledged_tolerance: DEFAULT_ACK_PACKET_TOLERANCE,
ignore_order: false,
+ ecn_count: EcnCount::default(),
}
}
+ /// Get the ECN counts.
+ pub fn ecn_marks(&mut self) -> &mut EcnCount {
+ &mut self.ecn_count
+ }
+
/// Get the time at which the next ACK should be sent.
pub fn ack_time(&self) -> Option<Instant> {
self.ack_time
@@ -545,6 +559,10 @@ impl RecvdPackets {
}
}
+ /// Length of the worst possible ACK frame, assuming only one range and ECN counts.
+ /// Note that this assumes one byte for the type and count of extra ranges.
+ pub const USEFUL_ACK_LEN: usize = 1 + 8 + 8 + 1 + 8 + 3 * 8;
+
/// Generate an ACK frame for this packet number space.
///
/// Unlike other frame generators this doesn't modify the underlying instance
@@ -563,10 +581,6 @@ impl RecvdPackets {
tokens: &mut Vec<RecoveryToken>,
stats: &mut FrameStats,
) {
- // The worst possible ACK frame, assuming only one range.
- // Note that this assumes one byte for the type and count of extra ranges.
- const LONGEST_ACK_HEADER: usize = 1 + 8 + 8 + 1 + 8;
-
// Check that we aren't delaying ACKs.
if !self.ack_now(now, rtt) {
return;
@@ -578,7 +592,10 @@ impl RecvdPackets {
// When congestion limited, ACK-only packets are 255 bytes at most
// (`recovery::ACK_ONLY_SIZE_LIMIT - 1`). This results in limiting the
// ranges to 13 here.
- let max_ranges = if let Some(avail) = builder.remaining().checked_sub(LONGEST_ACK_HEADER) {
+ let max_ranges = if let Some(avail) = builder
+ .remaining()
+ .checked_sub(RecvdPackets::USEFUL_ACK_LEN)
+ {
// Apply a hard maximum to keep plenty of space for other stuff.
min(1 + (avail / 16), MAX_ACKS_PER_FRAME)
} else {
@@ -593,7 +610,11 @@ impl RecvdPackets {
.cloned()
.collect::<Vec<_>>();
- builder.encode_varint(crate::frame::FRAME_TYPE_ACK);
+ builder.encode_varint(if self.ecn_count.is_some() {
+ FRAME_TYPE_ACK_ECN
+ } else {
+ FRAME_TYPE_ACK
+ });
let mut iter = ranges.iter();
let Some(first) = iter.next() else { return };
builder.encode_varint(first.largest);
@@ -617,6 +638,12 @@ impl RecvdPackets {
last = r.smallest;
}
+ if self.ecn_count.is_some() {
+ builder.encode_varint(self.ecn_count[IpTosEcn::Ect0]);
+ builder.encode_varint(self.ecn_count[IpTosEcn::Ect1]);
+ builder.encode_varint(self.ecn_count[IpTosEcn::Ce]);
+ }
+
// We've sent an ACK, reset the timer.
self.ack_time = None;
self.last_ack_time = Some(now);
@@ -1134,7 +1161,9 @@ mod tests {
.is_some());
let mut builder = PacketBuilder::short(Encoder::new(), false, []);
- builder.set_limit(32);
+ // The code pessimistically assumes that each range needs 16 bytes to express.
+ // So this won't be enough for a second range.
+ builder.set_limit(RecvdPackets::USEFUL_ACK_LEN + 8);
let mut stats = FrameStats::default();
tracker.write_frame(
diff --git a/third_party/rust/neqo-transport/tests/common/mod.rs b/third_party/rust/neqo-transport/tests/common/mod.rs
index e36e66f753..ecbbe1c3ce 100644
--- a/third_party/rust/neqo-transport/tests/common/mod.rs
+++ b/third_party/rust/neqo-transport/tests/common/mod.rs
@@ -84,114 +84,6 @@ pub fn connect(client: &mut Connection, server: &mut Server) -> ActiveConnection
connected_server(server)
}
-// Decode the header of a client Initial packet, returning three values:
-// * the entire header short of the packet number,
-// * just the DCID,
-// * just the SCID, and
-// * the protected payload including the packet number.
-// Any token is thrown away.
-#[must_use]
-pub fn decode_initial_header(dgram: &Datagram, role: Role) -> (&[u8], &[u8], &[u8], &[u8]) {
- let mut dec = Decoder::new(&dgram[..]);
- let type_and_ver = dec.decode(5).unwrap().to_vec();
- // The client sets the QUIC bit, the server might not.
- match role {
- Role::Client => assert_eq!(type_and_ver[0] & 0xf0, 0xc0),
- Role::Server => assert_eq!(type_and_ver[0] & 0xb0, 0x80),
- }
- let dest_cid = dec.decode_vec(1).unwrap();
- let src_cid = dec.decode_vec(1).unwrap();
- dec.skip_vvec(); // Ignore any the token.
-
- // Need to read of the length separately so that we can find the packet number.
- let payload_len = usize::try_from(dec.decode_varint().unwrap()).unwrap();
- let pn_offset = dgram.len() - dec.remaining();
- (
- &dgram[..pn_offset],
- dest_cid,
- src_cid,
- dec.decode(payload_len).unwrap(),
- )
-}
-
-/// Generate an AEAD and header protection object for a client Initial.
-/// Note that this works for QUIC version 1 only.
-#[must_use]
-pub fn initial_aead_and_hp(dcid: &[u8], role: Role) -> (Aead, HpKey) {
- const INITIAL_SALT: &[u8] = &[
- 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c,
- 0xad, 0xcc, 0xbb, 0x7f, 0x0a,
- ];
- let initial_secret = hkdf::extract(
- TLS_VERSION_1_3,
- TLS_AES_128_GCM_SHA256,
- Some(
- hkdf::import_key(TLS_VERSION_1_3, INITIAL_SALT)
- .as_ref()
- .unwrap(),
- ),
- hkdf::import_key(TLS_VERSION_1_3, dcid).as_ref().unwrap(),
- )
- .unwrap();
-
- let secret = hkdf::expand_label(
- TLS_VERSION_1_3,
- TLS_AES_128_GCM_SHA256,
- &initial_secret,
- &[],
- match role {
- Role::Client => "client in",
- Role::Server => "server in",
- },
- )
- .unwrap();
- (
- Aead::new(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256, &secret, "quic ").unwrap(),
- HpKey::extract(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256, &secret, "quic hp").unwrap(),
- )
-}
-
-// Remove header protection, returning the unmasked header and the packet number.
-#[must_use]
-pub fn remove_header_protection(hp: &HpKey, header: &[u8], payload: &[u8]) -> (Vec<u8>, u64) {
- // Make a copy of the header that can be modified.
- let mut fixed_header = header.to_vec();
- let pn_offset = header.len();
- // Save 4 extra in case the packet number is that long.
- fixed_header.extend_from_slice(&payload[..4]);
-
- // Sample for masking and apply the mask.
- let mask = hp.mask(&payload[4..20]).unwrap();
- fixed_header[0] ^= mask[0] & 0xf;
- let pn_len = 1 + usize::from(fixed_header[0] & 0x3);
- for i in 0..pn_len {
- fixed_header[pn_offset + i] ^= mask[1 + i];
- }
- // Trim down to size.
- fixed_header.truncate(pn_offset + pn_len);
- // The packet number should be 1.
- let pn = Decoder::new(&fixed_header[pn_offset..])
- .decode_uint(pn_len)
- .unwrap();
-
- (fixed_header, pn)
-}
-
-pub fn apply_header_protection(hp: &HpKey, packet: &mut [u8], pn_bytes: Range<usize>) {
- let sample_start = pn_bytes.start + 4;
- let sample_end = sample_start + 16;
- let mask = hp.mask(&packet[sample_start..sample_end]).unwrap();
- qtrace!(
- "sample={} mask={}",
- hex_with_len(&packet[sample_start..sample_end]),
- hex_with_len(&mask)
- );
- packet[0] ^= mask[0] & 0xf;
- for i in 0..(pn_bytes.end - pn_bytes.start) {
- packet[pn_bytes.start + i] ^= mask[1 + i];
- }
-}
-
/// Scrub through client events to find a resumption token.
pub fn find_ticket(client: &mut Connection) -> ResumptionToken {
client
diff --git a/third_party/rust/neqo-transport/tests/connection.rs b/third_party/rust/neqo-transport/tests/connection.rs
index b8877b946d..3cc711f80b 100644
--- a/third_party/rust/neqo-transport/tests/connection.rs
+++ b/third_party/rust/neqo-transport/tests/connection.rs
@@ -6,12 +6,16 @@
mod common;
-use common::{
- apply_header_protection, decode_initial_header, initial_aead_and_hp, remove_header_protection,
-};
use neqo_common::{Datagram, Decoder, Encoder, Role};
-use neqo_transport::{ConnectionError, ConnectionParameters, Error, State, Version};
-use test_fixture::{default_client, default_server, new_client, now, split_datagram};
+use neqo_transport::{CloseReason, ConnectionParameters, Error, State, Version};
+use test_fixture::{
+ default_client, default_server,
+ header_protection::{
+ apply_header_protection, decode_initial_header, initial_aead_and_hp,
+ remove_header_protection,
+ },
+ new_client, now, split_datagram,
+};
#[test]
fn connect() {
@@ -58,8 +62,8 @@ fn truncate_long_packet() {
/// Test that reordering parts of the server Initial doesn't change things.
#[test]
fn reorder_server_initial() {
- // A simple ACK frame for a single packet with packet number 0.
- const ACK_FRAME: &[u8] = &[0x02, 0x00, 0x00, 0x00, 0x00];
+ // A simple ACK_ECN frame for a single packet with packet number 0 with a single ECT(0) mark.
+ const ACK_FRAME: &[u8] = &[0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00];
let mut client = new_client(
ConnectionParameters::default().versions(Version::Version1, vec![Version::Version1]),
@@ -68,12 +72,13 @@ fn reorder_server_initial() {
let client_initial = client.process_output(now());
let (_, client_dcid, _, _) =
- decode_initial_header(client_initial.as_dgram_ref().unwrap(), Role::Client);
+ decode_initial_header(client_initial.as_dgram_ref().unwrap(), Role::Client).unwrap();
let client_dcid = client_dcid.to_owned();
let server_packet = server.process(client_initial.as_dgram_ref(), now()).dgram();
let (server_initial, server_hs) = split_datagram(server_packet.as_ref().unwrap());
- let (protected_header, _, _, payload) = decode_initial_header(&server_initial, Role::Server);
+ let (protected_header, _, _, payload) =
+ decode_initial_header(&server_initial, Role::Server).unwrap();
// Now decrypt the packet.
let (aead, hp) = initial_aead_and_hp(&client_dcid, Role::Server);
@@ -130,7 +135,7 @@ fn reorder_server_initial() {
fn set_payload(server_packet: &Option<Datagram>, client_dcid: &[u8], payload: &[u8]) -> Datagram {
let (server_initial, _server_hs) = split_datagram(server_packet.as_ref().unwrap());
let (protected_header, _, _, orig_payload) =
- decode_initial_header(&server_initial, Role::Server);
+ decode_initial_header(&server_initial, Role::Server).unwrap();
// Now decrypt the packet.
let (aead, hp) = initial_aead_and_hp(client_dcid, Role::Server);
@@ -168,14 +173,14 @@ fn packet_without_frames() {
let client_initial = client.process_output(now());
let (_, client_dcid, _, _) =
- decode_initial_header(client_initial.as_dgram_ref().unwrap(), Role::Client);
+ decode_initial_header(client_initial.as_dgram_ref().unwrap(), Role::Client).unwrap();
let server_packet = server.process(client_initial.as_dgram_ref(), now()).dgram();
let modified = set_payload(&server_packet, client_dcid, &[]);
client.process_input(&modified, now());
assert_eq!(
client.state(),
- &State::Closed(ConnectionError::Transport(Error::ProtocolViolation))
+ &State::Closed(CloseReason::Transport(Error::ProtocolViolation))
);
}
@@ -189,7 +194,7 @@ fn packet_with_only_padding() {
let client_initial = client.process_output(now());
let (_, client_dcid, _, _) =
- decode_initial_header(client_initial.as_dgram_ref().unwrap(), Role::Client);
+ decode_initial_header(client_initial.as_dgram_ref().unwrap(), Role::Client).unwrap();
let server_packet = server.process(client_initial.as_dgram_ref(), now()).dgram();
let modified = set_payload(&server_packet, client_dcid, &[0]);
@@ -208,7 +213,7 @@ fn overflow_crypto() {
let client_initial = client.process_output(now()).dgram();
let (_, client_dcid, _, _) =
- decode_initial_header(client_initial.as_ref().unwrap(), Role::Client);
+ decode_initial_header(client_initial.as_ref().unwrap(), Role::Client).unwrap();
let client_dcid = client_dcid.to_owned();
let server_packet = server.process(client_initial.as_ref(), now()).dgram();
@@ -217,7 +222,8 @@ fn overflow_crypto() {
// Now decrypt the server packet to get AEAD and HP instances.
// We won't be using the packet, but making new ones.
let (aead, hp) = initial_aead_and_hp(&client_dcid, Role::Server);
- let (_, server_dcid, server_scid, _) = decode_initial_header(&server_initial, Role::Server);
+ let (_, server_dcid, server_scid, _) =
+ decode_initial_header(&server_initial, Role::Server).unwrap();
// Send in 100 packets, each with 1000 bytes of crypto frame data each,
// eventually this will overrun the buffer we keep for crypto data.
@@ -260,10 +266,7 @@ fn overflow_crypto() {
client.process_input(&dgram, now());
if let State::Closing { error, .. } = client.state() {
assert!(
- matches!(
- error,
- ConnectionError::Transport(Error::CryptoBufferExceeded),
- ),
+ matches!(error, CloseReason::Transport(Error::CryptoBufferExceeded),),
"the connection need to abort on crypto buffer"
);
assert!(pn > 64, "at least 64000 bytes of data is buffered");
diff --git a/third_party/rust/neqo-transport/tests/network.rs b/third_party/rust/neqo-transport/tests/network.rs
index 27e5a83cd6..68a835a436 100644
--- a/third_party/rust/neqo-transport/tests/network.rs
+++ b/third_party/rust/neqo-transport/tests/network.rs
@@ -6,7 +6,7 @@
use std::{ops::Range, time::Duration};
-use neqo_transport::{ConnectionError, ConnectionParameters, Error, State};
+use neqo_transport::{CloseReason, ConnectionParameters, Error, State};
use test_fixture::{
boxed,
sim::{
@@ -48,10 +48,10 @@ simulate!(
idle_timeout,
[
ConnectionNode::default_client(boxed![ReachState::new(State::Closed(
- ConnectionError::Transport(Error::IdleTimeout)
+ CloseReason::Transport(Error::IdleTimeout)
))]),
ConnectionNode::default_server(boxed![ReachState::new(State::Closed(
- ConnectionError::Transport(Error::IdleTimeout)
+ CloseReason::Transport(Error::IdleTimeout)
))]),
]
);
@@ -62,7 +62,7 @@ simulate!(
ConnectionNode::new_client(
ConnectionParameters::default().idle_timeout(weeks(1000)),
boxed![ReachState::new(State::Confirmed),],
- boxed![ReachState::new(State::Closed(ConnectionError::Transport(
+ boxed![ReachState::new(State::Closed(CloseReason::Transport(
Error::IdleTimeout
)))]
),
@@ -71,7 +71,7 @@ simulate!(
ConnectionNode::new_server(
ConnectionParameters::default().idle_timeout(weeks(1000)),
boxed![ReachState::new(State::Confirmed),],
- boxed![ReachState::new(State::Closed(ConnectionError::Transport(
+ boxed![ReachState::new(State::Closed(CloseReason::Transport(
Error::IdleTimeout
)))]
),
diff --git a/third_party/rust/neqo-transport/tests/retry.rs b/third_party/rust/neqo-transport/tests/retry.rs
index 36eff71e7b..3f95511c3e 100644
--- a/third_party/rust/neqo-transport/tests/retry.rs
+++ b/third_party/rust/neqo-transport/tests/retry.rs
@@ -14,14 +14,18 @@ use std::{
time::Duration,
};
-use common::{
- apply_header_protection, connected_server, decode_initial_header, default_server,
- generate_ticket, initial_aead_and_hp, remove_header_protection,
-};
+use common::{connected_server, default_server, generate_ticket};
use neqo_common::{hex_with_len, qdebug, qtrace, Datagram, Encoder, Role};
use neqo_crypto::AuthenticationStatus;
-use neqo_transport::{server::ValidateAddress, ConnectionError, Error, State, StreamType};
-use test_fixture::{assertions, datagram, default_client, now, split_datagram};
+use neqo_transport::{server::ValidateAddress, CloseReason, Error, State, StreamType};
+use test_fixture::{
+ assertions, datagram, default_client,
+ header_protection::{
+ apply_header_protection, decode_initial_header, initial_aead_and_hp,
+ remove_header_protection,
+ },
+ now, split_datagram,
+};
#[test]
fn retry_basic() {
@@ -400,7 +404,7 @@ fn mitm_retry() {
// rewriting the header to remove the token, and then re-encrypting.
let client_initial2 = client_initial2.unwrap();
let (protected_header, d_cid, s_cid, payload) =
- decode_initial_header(&client_initial2, Role::Client);
+ decode_initial_header(&client_initial2, Role::Client).unwrap();
// Now we have enough information to make keys.
let (aead, hp) = initial_aead_and_hp(d_cid, Role::Client);
@@ -465,7 +469,7 @@ fn mitm_retry() {
assert!(matches!(
*client.state(),
State::Closing {
- error: ConnectionError::Transport(Error::ProtocolViolation),
+ error: CloseReason::Transport(Error::ProtocolViolation),
..
}
));
diff --git a/third_party/rust/neqo-transport/tests/server.rs b/third_party/rust/neqo-transport/tests/server.rs
index 7388e0fee7..4740d26ded 100644
--- a/third_party/rust/neqo-transport/tests/server.rs
+++ b/third_party/rust/neqo-transport/tests/server.rs
@@ -8,21 +8,22 @@ mod common;
use std::{cell::RefCell, mem, net::SocketAddr, rc::Rc, time::Duration};
-use common::{
- apply_header_protection, connect, connected_server, decode_initial_header, default_server,
- find_ticket, generate_ticket, initial_aead_and_hp, new_server, remove_header_protection,
-};
+use common::{connect, connected_server, default_server, find_ticket, generate_ticket, new_server};
use neqo_common::{qtrace, Datagram, Decoder, Encoder, Role};
use neqo_crypto::{
generate_ech_keys, AllowZeroRtt, AuthenticationStatus, ZeroRttCheckResult, ZeroRttChecker,
};
use neqo_transport::{
server::{ActiveConnectionRef, Server, ValidateAddress},
- Connection, ConnectionError, ConnectionParameters, Error, Output, State, StreamType, Version,
+ CloseReason, Connection, ConnectionParameters, Error, Output, State, StreamType, Version,
};
use test_fixture::{
- assertions, datagram, default_client, new_client, now, split_datagram,
- CountingConnectionIdGenerator,
+ assertions, datagram, default_client,
+ header_protection::{
+ apply_header_protection, decode_initial_header, initial_aead_and_hp,
+ remove_header_protection,
+ },
+ new_client, now, split_datagram, CountingConnectionIdGenerator,
};
/// Take a pair of connections in any state and complete the handshake.
@@ -389,7 +390,7 @@ fn bad_client_initial() {
let mut server = default_server();
let dgram = client.process(None, now()).dgram().expect("a datagram");
- let (header, d_cid, s_cid, payload) = decode_initial_header(&dgram, Role::Client);
+ let (header, d_cid, s_cid, payload) = decode_initial_header(&dgram, Role::Client).unwrap();
let (aead, hp) = initial_aead_and_hp(d_cid, Role::Client);
let (fixed_header, pn) = remove_header_protection(&hp, header, payload);
let payload = &payload[(fixed_header.len() - header.len())..];
@@ -462,13 +463,13 @@ fn bad_client_initial() {
assert_ne!(delay, Duration::from_secs(0));
assert!(matches!(
*client.state(),
- State::Draining { error: ConnectionError::Transport(Error::PeerError(code)), .. } if code == Error::ProtocolViolation.code()
+ State::Draining { error: CloseReason::Transport(Error::PeerError(code)), .. } if code == Error::ProtocolViolation.code()
));
for server in server.active_connections() {
assert_eq!(
*server.borrow().state(),
- State::Closed(ConnectionError::Transport(Error::ProtocolViolation))
+ State::Closed(CloseReason::Transport(Error::ProtocolViolation))
);
}
@@ -478,6 +479,65 @@ fn bad_client_initial() {
}
#[test]
+fn bad_client_initial_connection_close() {
+ let mut client = default_client();
+ let mut server = default_server();
+
+ let dgram = client.process(None, now()).dgram().expect("a datagram");
+ let (header, d_cid, s_cid, payload) = decode_initial_header(&dgram, Role::Client).unwrap();
+ let (aead, hp) = initial_aead_and_hp(d_cid, Role::Client);
+ let (_, pn) = remove_header_protection(&hp, header, payload);
+
+ let mut payload_enc = Encoder::with_capacity(1200);
+ payload_enc.encode(&[0x1c, 0x01, 0x00, 0x00]); // Add a CONNECTION_CLOSE frame.
+
+ // Make a new header with a 1 byte packet number length.
+ let mut header_enc = Encoder::new();
+ header_enc
+ .encode_byte(0xc0) // Initial with 1 byte packet number.
+ .encode_uint(4, Version::default().wire_version())
+ .encode_vec(1, d_cid)
+ .encode_vec(1, s_cid)
+ .encode_vvec(&[])
+ .encode_varint(u64::try_from(payload_enc.len() + aead.expansion() + 1).unwrap())
+ .encode_byte(u8::try_from(pn).unwrap());
+
+ let mut ciphertext = header_enc.as_ref().to_vec();
+ ciphertext.resize(header_enc.len() + payload_enc.len() + aead.expansion(), 0);
+ let v = aead
+ .encrypt(
+ pn,
+ header_enc.as_ref(),
+ payload_enc.as_ref(),
+ &mut ciphertext[header_enc.len()..],
+ )
+ .unwrap();
+ assert_eq!(header_enc.len() + v.len(), ciphertext.len());
+ // Pad with zero to get up to 1200.
+ ciphertext.resize(1200, 0);
+
+ apply_header_protection(
+ &hp,
+ &mut ciphertext,
+ (header_enc.len() - 1)..header_enc.len(),
+ );
+ let bad_dgram = Datagram::new(
+ dgram.source(),
+ dgram.destination(),
+ dgram.tos(),
+ dgram.ttl(),
+ ciphertext,
+ );
+
+ // The server should ignore this and go to Draining.
+ let mut now = now();
+ let response = server.process(Some(&bad_dgram), now);
+ now += response.callback();
+ let response = server.process(None, now);
+ assert_eq!(response, Output::None);
+}
+
+#[test]
fn version_negotiation_ignored() {
let mut server = default_server();
let mut client = default_client();
@@ -774,3 +834,16 @@ fn ech() {
.ech_accepted()
.unwrap());
}
+
+#[test]
+fn has_active_connections() {
+ let mut server = default_server();
+ let mut client = default_client();
+
+ assert!(!server.has_active_connections());
+
+ let initial = client.process(None, now());
+ let _ = server.process(initial.as_dgram_ref(), now()).dgram();
+
+ assert!(server.has_active_connections());
+}