diff --git a/Cargo.lock b/Cargo.lock index 27fc342..592519e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,16 +18,83 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "autocfg" -version = "1.1.0" +name = "aho-corasick" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -40,9 +107,29 @@ dependencies = [ [[package]] name = "bitflags" -version = "1.3.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bytemuck" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] [[package]] name = "byteorder" @@ -52,18 +139,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" [[package]] name = "cfg-if" @@ -77,6 +161,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + [[package]] name = "crossbeam-utils" version = "0.7.2" @@ -88,6 +178,35 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "either" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" + +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "futures" version = "0.1.31" @@ -95,10 +214,99 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] -name = "getrandom" -version = "0.2.12" +name = "futures" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if 1.0.0", "libc", @@ -113,9 +321,30 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "hermit-abi" -version = "0.3.4" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] [[package]] name = "lazy_static" @@ -125,25 +354,31 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.152" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] -name = "memchr" -version = "2.7.1" +name = "log" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "minimal-lexical" @@ -153,22 +388,22 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -224,9 +459,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -234,22 +469,28 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.5", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "ppv-lite86" @@ -275,23 +516,23 @@ checksum = "90d78755a79b5711d5e2140c36e10dac2f06f555b1a9995141b54f72ae129515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.65", ] [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -328,18 +569,53 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + [[package]] name = "rust-bfd" version = "0.1.0" dependencies = [ + "atomic", + "bytemuck", "byteorder", + "env_logger", + "futures 0.3.30", + "itertools", + "log", "nom", "nom-derive", "proc-bitfield", @@ -350,15 +626,15 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "scopeguard" @@ -368,9 +644,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -386,18 +662,18 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -419,9 +695,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" dependencies = [ "proc-macro2", "quote", @@ -430,9 +706,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.35.1" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -444,7 +720,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -454,7 +730,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" dependencies = [ "crossbeam-utils", - "futures", + "futures 0.1.31", ] [[package]] @@ -465,7 +741,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.65", ] [[package]] @@ -475,7 +751,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93044f2d313c95ff1cb7809ce9a7a05735b012288a888b62d4434fd58c94f296" dependencies = [ "crossbeam-utils", - "futures", + "futures 0.1.31", "slab", "tokio-executor", ] @@ -486,6 +762,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -498,7 +780,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", ] [[package]] @@ -507,13 +798,29 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -522,38 +829,86 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/Cargo.toml b/Cargo.toml index 881c193..3ec1a3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,13 @@ version = "0.1.0" edition = "2021" [dependencies] +atomic = "0.6.0" +bytemuck = { version = "1.14.1", features = ["derive"] } byteorder = "1.5.0" +env_logger = "0.11.1" +futures = "0.3.30" +itertools = "0.12.1" +log = "0.4.20" nom = "7.1.3" nom-derive = "0.10.1" proc-bitfield = "0.3.1" diff --git a/src/main.rs b/src/main.rs index d4b32b3..591222a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,51 @@ +use futures::future; use std::{ - collections::HashMap, error::Error, fmt::Display, fs::read, io::Cursor, net::{IpAddr, SocketAddr}, str::FromStr, sync::Arc + collections::HashMap, + error::Error, + fmt::{self, Display}, + fs::read, + io::Cursor, + mem::swap, + net::{IpAddr, SocketAddr}, + num::NonZeroU32, + pin::Pin, + str::FromStr, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::SystemTime, }; +use tokio::{ + sync::{oneshot, Mutex}, + time::{interval_at, Timeout}, +}; + +use byteorder::{BigEndian, WriteBytesExt}; +use env_logger::Env; +use itertools::Itertools; +use log::{debug, error, info, warn}; use nom::{bytes::complete::take, multi::many_m_n, number::complete::be_u8, IResult}; use nom_derive::{NomBE, Parse}; use proc_bitfield::*; use rand::prelude::*; -use tokio::task; use tokio::time; use tokio::{io, join, task::JoinHandle}; use tokio::{net::UdpSocket, sync::RwLock}; use tokio::{sync::mpsc, time::Instant}; -use byteorder::{BigEndian, WriteBytesExt}; +use tokio::{task, time::Interval}; + +use atomic::Atomic; +use bytemuck::{NoUninit, Pod}; const CONTROL_PORT: u16 = 3784; const ECHO_PORT: u16 = 3785; +const ORDERING: Ordering = Ordering::Relaxed; + #[repr(u8)] -#[derive(ConvRaw, Debug, NomBE, PartialEq, Eq, Clone, Copy)] +#[derive(ConvRaw, Debug, NomBE, PartialEq, Eq, Clone, Copy, NoUninit)] pub enum BfdDiagnostic { None = 0, TimeExpired = 1, @@ -30,8 +58,24 @@ pub enum BfdDiagnostic { RevConcatPathDown = 8, Reserved, } +impl Display for BfdDiagnostic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::None => "None", + Self::TimeExpired => "TimeExpired", + Self::EchoFailed => "EchoFailed", + Self::NeighborDown => "NeighborDown", + Self::FwdPlaneReset => "FwdPlaneReset", + Self::PathDown => "PathDown", + Self::ConcatPathDown => "ConcatPathDown", + Self::AdminDown => "AdminDown", + Self::RevConcatPathDown => "RevConcatPathDown", + Self::Reserved => "Reserved", + }) + } +} #[repr(u8)] -#[derive(ConvRaw, Debug, NomBE, PartialEq, Eq, Default, Clone, Copy)] +#[derive(ConvRaw, Debug, NomBE, PartialEq, Eq, Default, Clone, Copy, NoUninit)] pub enum BfdState { AdminDown = 0, #[default] @@ -51,7 +95,7 @@ impl Display for BfdState { } #[repr(u8)] -#[derive(ConvRaw, Debug, NomBE, PartialEq, Eq, Clone, Copy)] +#[derive(ConvRaw, Debug, NomBE, PartialEq, Eq, Clone, Copy, NoUninit)] pub enum BfdAuthType { None = 0, SimplePassword = 1, @@ -78,7 +122,8 @@ impl Display for BfdError { } impl Error for BfdError {} -#[derive(Debug, NomBE, PartialEq, Eq, Clone, Copy)] +#[repr(transparent)] +#[derive(Debug, NomBE, PartialEq, Eq, Clone, Copy, NoUninit, Hash)] pub struct BfdDiscriminator(u32); impl Display for BfdDiscriminator { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -86,14 +131,20 @@ impl Display for BfdDiscriminator { } } -#[derive(Debug, NomBE, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] +#[repr(transparent)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, NoUninit, NomBE)] +/// All intervals in BFD are specified in microseconds pub struct BfdInterval(u32); impl From for time::Duration { fn from(value: BfdInterval) -> Self { time::Duration::from_micros(value.0 as u64) } } - +impl From for BfdInterval { + fn from(value: u32) -> Self { + BfdInterval(value) + } +} bitfield! { #[derive(NomBE)] pub struct BfdFlags(pub u32): Debug { @@ -199,213 +250,560 @@ impl BfdPacket { } } -#[derive(Debug, Clone)] +/// Data structure to store the state of the Bfd machine. The impl does *not* +/// implement any Bfd logic, it is merely a thread-safe data structure +#[derive(Debug)] struct BfdSessionState { - control_sock: Arc, - peer_addr: IpAddr, - session_state: BfdState, - remote_session_state: BfdState, - local_discr: BfdDiscriminator, - remote_discr: BfdDiscriminator, - local_diag: BfdDiagnostic, - desired_min_tx_interval: BfdInterval, - required_min_rx_interval: BfdInterval, - remote_min_rx_interval: BfdInterval, - demand_mode: bool, - remote_demand_mode: bool, - detect_mult: u8, - auth_type: BfdAuthType, - rcv_auth_seq: u32, - xmit_auth_seq: u32, - auth_seq_known: bool, + control_sock: Arc, // immutable + peer_addr: IpAddr, // immutable and Copy + session_state: Atomic, // mutable by any thread + remote_session_state: Atomic, // mutable by rx thread + local_discr: BfdDiscriminator, // immutable and Copy + remote_discr: Atomic, // mutable by rx thread + local_diag: Atomic, // mutable by any thread + desired_min_tx_interval: Atomic, // mutable by any thread + required_min_rx_interval: Atomic, // mutable by any thread + remote_min_rx_interval: Atomic, // mutable by rx thread + demand_mode: Atomic, // mutable by rx thread + remote_demand_mode: Atomic, // mutable by rx thread + detect_mult: Atomic, // mutable by control thread + auth_type: Atomic, // mutable by control thread + rcv_auth_seq: Atomic, // mutable by rx thread + xmit_auth_seq: Atomic, // mutable by tx thread? + auth_seq_known: Atomic, // mutable by rx thread? - periodic_cmd_channel: mpsc::Sender, - detection_time: time::Duration, - poll_mode: bool, + detection_time: Atomic, // mutable by rx thread + poll_mode: Atomic, // mutable by rx thread + + control_packets_rx: Atomic, + control_packets_tx: Atomic, + last_state_change: Mutex, + last_remote_diag: Atomic, +} + +// TODO: hide the internal data structure behind a trait +impl BfdSessionState { + fn control_sock(&self) -> Arc { + self.control_sock.clone() + } + fn peer_addr(&self) -> IpAddr { + self.peer_addr + } + + fn session_state(&self) -> BfdState { + self.session_state.load(ORDERING) + } + // Note: This does not handle state transition + fn set_session_state(&self, value: BfdState) { + self.session_state.store(value, ORDERING) + } + fn remote_session_state(&self) -> BfdState { + self.remote_session_state.load(ORDERING) + } + fn set_remote_session_state(&self, value: BfdState) { + self.remote_session_state.store(value, ORDERING); + } + fn local_discr(&self) -> BfdDiscriminator { + self.local_discr + } + fn remote_discr(&self) -> BfdDiscriminator { + self.remote_discr.load(ORDERING) + } + fn set_remote_discr(&self, value: BfdDiscriminator) { + self.remote_discr.store(value, ORDERING); + } + fn demand_mode(&self) -> bool { + self.demand_mode.load(ORDERING) + } + fn set_demand_mode(&self, value: bool) { + self.demand_mode.store(value, ORDERING) + } + fn remote_demand_mode(&self) -> bool { + self.remote_demand_mode.load(ORDERING) + } + fn set_remote_demand_mode(&self, value: bool) { + self.remote_demand_mode.store(value, ORDERING); + } + fn remote_min_rx_interval(&self) -> BfdInterval { + self.remote_min_rx_interval.load(ORDERING) + } + fn set_remote_min_rx_interval(&self, value: BfdInterval) { + self.remote_min_rx_interval.store(value, ORDERING); + } + fn local_diag(&self) -> BfdDiagnostic { + self.local_diag.load(ORDERING) + } + fn set_local_diag(&self, value: BfdDiagnostic) { + self.local_diag.store(value, ORDERING); + } + fn auth_type(&self) -> BfdAuthType { + self.auth_type.load(ORDERING) + } + fn detect_mult(&self) -> u8 { + self.detect_mult.load(ORDERING) + } + fn set_detect_mult(&self, value: u8) { + self.detect_mult.store(value, ORDERING) + } + fn poll_mode(&self) -> bool { + self.poll_mode.load(ORDERING) + } + fn set_poll_mode(&self, value: bool) { + self.poll_mode.store(value, ORDERING) + } + fn desired_min_tx_interval(&self) -> BfdInterval { + self.desired_min_tx_interval.load(ORDERING) + } + fn set_desired_min_tx_interval(&self, value: BfdInterval) { + self.desired_min_tx_interval.store(value, ORDERING) + } + fn required_min_rx_interval(&self) -> BfdInterval { + self.required_min_rx_interval.load(ORDERING) + } + fn set_required_min_rx_interval(&self, value: BfdInterval) { + self.required_min_rx_interval.store(value, ORDERING) + } + fn detection_time(&self) -> BfdInterval { + self.detection_time.load(ORDERING) + } + fn set_detection_time(&self, value: BfdInterval) { + self.detection_time.store(value, ORDERING) + } + fn control_packets_rx(&self) -> u64 { + self.control_packets_rx.load(ORDERING) + } + fn control_packets_tx(&self) -> u64 { + self.control_packets_tx.load(ORDERING) + } + fn last_state_change(&self) -> Instant { + *self.last_state_change.try_lock().unwrap() + } + fn last_remote_diag(&self) -> BfdDiagnostic { + self.last_remote_diag.load(ORDERING) + } + fn set_last_remote_diag(&self, value: BfdDiagnostic) { + self.last_remote_diag.store(value, ORDERING) + } +} + +enum WatchdogReset { + ReceivedPacket, + Terminate, } struct BfdSession { - state: Arc>, + state: BfdSessionState, + periodic: BfdPeriodicSenderHandle, + rx: mpsc::Receiver, + tx: mpsc::Sender, + rx_watchdog: Option>, + cur_interval: BfdInterval, } +#[derive(Debug)] +struct BfdSessionStats { + local_ip: IpAddr, + remote_ip: IpAddr, + local_discr: BfdDiscriminator, + remote_discr: BfdDiscriminator, + state: BfdState, + last_diag: BfdDiagnostic, + control_packets_rx: u64, + control_packets_tx: u64, + last_change: Instant, +} +impl Display for BfdSessionStats { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!( + f, + "{:<20} {:<9} {:>6} {:>6}", + self.remote_ip, self.state, self.control_packets_rx, self.control_packets_tx + )?; + write!(f, " LD:{} RD:{} Diag:{} Last: {}s", self.local_discr, self.remote_discr, self.last_diag, Instant::now().duration_since(self.last_change).as_secs()) + } +} +impl From<&BfdSessionState> for BfdSessionStats { + fn from(state: &BfdSessionState) -> Self { + Self { + local_ip: state.control_sock().local_addr().unwrap().ip(), + remote_ip: state.peer_addr(), + local_discr: state.local_discr(), + remote_discr: state.remote_discr(), + state: state.session_state(), + last_diag: state.last_remote_diag(), + control_packets_rx: state.control_packets_rx(), + control_packets_tx: state.control_packets_tx(), + last_change: state.last_state_change(), + } + } +} enum PeriodicControlCommand { Stop, Start, Quit, SetMinInterval(BfdInterval), + SendPollResponse, } enum SessionControlCommand { - RxPacket(Vec), + RxPacket(BfdPacket), + TxControlPacket { + poll_response: bool, + }, + GetSessionStats { + respond_to: oneshot::Sender, + }, + ReceiveTimeout, Quit, } +#[derive(Clone)] +struct BfdSessionHandle { + tx: mpsc::Sender, +} + +impl BfdSessionHandle { + async fn new(local_addr: IpAddr, remote_addr: IpAddr, local_discr: BfdDiscriminator) -> Self { + let (tx, rx) = mpsc::channel(32); + let new_self = Self { tx }; + let periodic = BfdPeriodicSenderHandle::new(new_self.clone(), BfdInterval(1_000_000)); + + let mut session = BfdSession::new( + rx, + new_self.tx.clone(), + periodic, + local_addr, + remote_addr, + local_discr, + ) + .await; + tokio::spawn(async move { session.run().await }); + + new_self + } + async fn transmit_control_packet( + &self, + poll_response: bool, + ) -> Result<(), mpsc::error::SendError> { + self.tx + .send(SessionControlCommand::TxControlPacket { poll_response }) + .await + } + async fn get_stats( + &self, + ) -> Result> { + let (send, recv) = oneshot::channel(); + self.tx + .send(SessionControlCommand::GetSessionStats { respond_to: send }) + .await?; + Ok(recv.await.expect("Unable to receive stats from session")) + } +} + +#[derive(Clone, Debug)] +struct BfdPeriodicSenderHandle { + tx: mpsc::Sender, +} + +impl BfdPeriodicSenderHandle { + fn new(bfd: BfdSessionHandle, interval: BfdInterval) -> Self { + let (tx, rx) = mpsc::channel(32); + let mut periodic_sender = BfdPeriodicSender::new(rx, bfd, interval); + tokio::spawn(async move { periodic_sender.run().await }); + + Self { tx } + } +} + +struct BfdPeriodicSender { + rx: mpsc::Receiver, + bfd: BfdSessionHandle, + cur_interval: BfdInterval, +} + +impl BfdPeriodicSender { + fn new( + rx: mpsc::Receiver, + bfd: BfdSessionHandle, + interval: BfdInterval, + ) -> Self { + Self { + rx, + bfd, + cur_interval: interval, + } + } + async fn run(&mut self) { + let base_interval = time::Duration::from_micros(self.cur_interval.0 as u64 * 3 / 4); + let mut clock = time::interval(base_interval); + let mut running = true; + + loop { + tokio::select! { + _ = clock.tick(), if running == true => { + let jitter = time::Duration::from_micros( + rand::thread_rng() + .gen_range(0..clock.period().as_micros() / 3) + .try_into() + .unwrap(), + ); + time::sleep(jitter).await; + // debug!("tick! sent periodic control packet after {}ms", (base_interval + jitter).as_millis()); + self.bfd.transmit_control_packet(false).await.unwrap(); + }, + Some(cmd) = self.rx.recv() => { + match cmd { + PeriodicControlCommand::Quit => { debug!("Closing periodic packet sender"); return}, + PeriodicControlCommand::Stop => { + debug!("Stopping periodic packets"); + running= false} + , + PeriodicControlCommand::Start => { debug!("Starting periodic packets"); running = true }, + PeriodicControlCommand::SetMinInterval(i) => { + let base_interval = time::Duration::from_micros(i.0 as u64 * 3 / 4); + debug!("Updating base interval to {}ms (jittering {} to {}ms)", base_interval.as_millis(), base_interval.as_millis(), base_interval.as_millis() + base_interval.as_millis() / 3); + clock = time::interval_at( + time::Instant::now() + base_interval.into(), + base_interval.into(), + ); + }, + PeriodicControlCommand::SendPollResponse => { + debug!("Sending poll response"); + self.bfd.transmit_control_packet(true).await.unwrap(); + } + } + } + }; + } + } +} + impl BfdSession { async fn new( + rx: mpsc::Receiver, + tx: mpsc::Sender, + periodic: BfdPeriodicSenderHandle, local_addr: IpAddr, remote_addr: IpAddr, - ) -> Result> { + local_discr: BfdDiscriminator, + ) -> Self { let mut rng = rand::thread_rng(); //TODO: select a random unused port instead of pure random let source_port: u16 = rng.gen_range(49152..=65535); - let control_sock = UdpSocket::bind(SocketAddr::new(local_addr, source_port)).await?; + let control_sock = UdpSocket::bind(SocketAddr::new(local_addr, source_port)) + .await + .unwrap(); + control_sock.set_ttl(255).unwrap(); // control_sock // .connect(SocketAddr::new(remote_addr, CONTROL_PORT)) // .await?; // Incoming packets will come over the channel from the mux, since they don't send to the reciprocal port - - Ok(Self { - state: Arc::new(RwLock::new(BfdSessionState { + Self { + rx, + tx, + periodic, + rx_watchdog: None, + cur_interval: BfdInterval(1_000_000), + state: BfdSessionState { control_sock: Arc::new(control_sock), peer_addr: remote_addr, - session_state: BfdState::default(), - remote_session_state: BfdState::default(), - local_discr: BfdDiscriminator(rng.gen()), - remote_discr: BfdDiscriminator(0), - local_diag: BfdDiagnostic::None, - desired_min_tx_interval: BfdInterval(1_000_000), - required_min_rx_interval: BfdInterval(300_000), - remote_min_rx_interval: BfdInterval(1), - demand_mode: false, - remote_demand_mode: false, - detect_mult: 3, - auth_type: BfdAuthType::None, - rcv_auth_seq: 0, - xmit_auth_seq: rng.gen(), - auth_seq_known: false, - periodic_cmd_channel: mpsc::channel(1).0, - detection_time: time::Duration::ZERO, - poll_mode: false, - })), - }) + session_state: Atomic::new(BfdState::default()), + remote_session_state: Atomic::new(BfdState::default()), + local_discr, + remote_discr: Atomic::new(BfdDiscriminator(0)), + local_diag: Atomic::new(BfdDiagnostic::None), + desired_min_tx_interval: Atomic::new(BfdInterval(1_000_000)), + required_min_rx_interval: Atomic::new(BfdInterval(300_000)), + remote_min_rx_interval: Atomic::new(BfdInterval(1)), + demand_mode: Atomic::new(false), + remote_demand_mode: Atomic::new(false), + detect_mult: Atomic::new(3), + auth_type: Atomic::new(BfdAuthType::None), + rcv_auth_seq: Atomic::new(0), + xmit_auth_seq: Atomic::new(rng.gen()), + auth_seq_known: Atomic::new(false), + detection_time: Atomic::new(BfdInterval(0)), + poll_mode: Atomic::new(false), + + control_packets_rx: Atomic::new(0), + control_packets_tx: Atomic::new(0), + last_state_change: Mutex::new(Instant::now()), + last_remote_diag: Atomic::new(BfdDiagnostic::None), + }, + } } - async fn spawn_control_thread( - self: Arc, - mut rx: mpsc::Receiver, - ) -> JoinHandle<()> { - task::spawn(async move { - while let Some(cmd) = rx.recv().await { - match cmd { - SessionControlCommand::Quit => return, - SessionControlCommand::RxPacket(buf) => { - if let Ok((_leftover, packet)) = BfdPacket::parse(buf.as_slice()) { - println!("packet: {:?}", packet); - self.clone().receive_control_packet(&packet).await - } else { - eprintln!("Failed to parse packet"); - } - } - } - } - }) - } - - async fn transmit_periodic_packet(self: Arc) { - let read_guard = self.state.read().await; - let packet = BfdPacket { - flags: BfdFlags(0).with_vers(1).with_diag(read_guard.local_diag.into()).with_state(read_guard.session_state.into()).with_poll(read_guard.poll_mode).with_cpi(true).with_demand(read_guard.session_state == BfdState::Up && read_guard.remote_session_state == BfdState::Up).with_detect_mult(read_guard.detect_mult).with_length(24), - my_disc: read_guard.local_discr, - your_disc: read_guard.remote_discr, - desired_min_tx: read_guard.desired_min_tx_interval, - required_min_rx: read_guard.required_min_rx_interval, - required_min_echo_rx: BfdInterval(0), - auth: None + async fn transition(&mut self, new_state: BfdState) { + if self.state.session_state() == new_state { + return; }; - let socket = read_guard.control_sock.clone(); - let dest = read_guard.peer_addr; - drop(read_guard); - socket.send_to(packet.serialize().unwrap().as_ref(), SocketAddr::new(dest, CONTROL_PORT)).await.unwrap(); + info!( + "Peer {} state change {} -> {}", + self.state.peer_addr(), + self.state.session_state(), + new_state + ); + *self.state.last_state_change.lock().await = Instant::now(); + match (self.state.session_state(), new_state) { + (BfdState::Up, BfdState::AdminDown | BfdState::Down | BfdState::Init) => { + if self.rx_watchdog.is_some() { + self.rx_watchdog + .as_mut() + .unwrap() + .send(WatchdogReset::Terminate) + .await + .unwrap(); + } + } + (BfdState::AdminDown | BfdState::Down | BfdState::Init, BfdState::Up) => { + self.start_watchdog().await; + } + _ => {} + } + self.state.set_session_state(new_state); } - async fn spawn_periodic_thread( - self: Arc, - mut rx: mpsc::Receiver, - interval: BfdInterval, - ) -> JoinHandle<()> { - task::spawn(async move { - let mut running = true; - let base_interval = time::Duration::from_micros(interval.0 as u64 * 3 / 4); - let mut clock = time::interval(base_interval); - 'MAIN: loop { - if running { - // Get and action all pending commands then wait for interval to tick - while let Ok(cmd) = rx.try_recv() { - match cmd { - PeriodicControlCommand::Quit => return, - PeriodicControlCommand::Stop => { - running = false; - continue 'MAIN; - } - PeriodicControlCommand::Start => running = true, - PeriodicControlCommand::SetMinInterval(i) => { - running = true; - let base_interval = time::Duration::from_micros(i.0 as u64 * 3 / 4); - clock = time::interval_at( - time::Instant::now() + base_interval.into(), - base_interval.into(), - ); - } - } - } - // The periodic transmission of BFD Control packets MUST be jittered on a per-packet basis by up to - // 25%, that is, the interval MUST be reduced by a random value of 0 to 25% - // - // We do the equivalent inverse, we wait 75%, then add an additional 0-25%. - let jitter = time::Duration::from_micros( - rand::thread_rng() - .gen_range(0..clock.period().as_micros() / 3) - .try_into() - .unwrap(), - ); - clock.tick().await; - time::sleep(jitter).await; - self.clone().transmit_periodic_packet().await; - // + async fn run(&mut self) { + while let Some(cmd) = self.rx.recv().await { + match cmd { + SessionControlCommand::Quit => return, + SessionControlCommand::RxPacket(packet) => { + self.receive_control_packet(&packet).await; + } + SessionControlCommand::GetSessionStats { respond_to } => { + respond_to.send(BfdSessionStats::from(&self.state)).unwrap() + } + SessionControlCommand::TxControlPacket { poll_response } => { + self.transmit_control_packet(poll_response).await + } + SessionControlCommand::ReceiveTimeout => { + self.state.set_local_diag(BfdDiagnostic::TimeExpired); + self.transition(BfdState::Down).await; + } + } + } + println!("Session command channel closed"); + } + + async fn transmit_control_packet(&self, poll_response: bool) { + let packet = BfdPacket { + flags: BfdFlags(0) + .with_vers(1) + .with_diag(self.state.local_diag().into()) + .with_state(self.state.session_state().into()) + .with_poll(if poll_response { + false } else { - // Instead we block on incoming commands - if let Some(cmd) = rx.recv().await { - match cmd { - PeriodicControlCommand::Start => { - running = true; - clock.reset_after(clock.period()) - } - PeriodicControlCommand::SetMinInterval(i) => { - running = true; - let base_interval = time::Duration::from_micros(i.0 as u64 * 3 / 4); - clock = time::interval_at( - time::Instant::now() + base_interval.into(), - base_interval.into(), - ); - } - _ => {} // Other commands don't mutate state or start the clock - } + self.state.poll_mode() + }) + .with_final_(poll_response) + .with_cpi(true) + .with_demand( + self.state.session_state() == BfdState::Up + && self.state.remote_session_state() == BfdState::Up, + ) + .with_detect_mult(self.state.detect_mult()) + .with_length(24), + my_disc: self.state.local_discr(), + your_disc: self.state.remote_discr(), + desired_min_tx: self.state.desired_min_tx_interval(), + required_min_rx: self.state.required_min_rx_interval(), + required_min_echo_rx: BfdInterval(0), + auth: None, + }; + let socket = self.state.control_sock.clone(); + let dest = self.state.peer_addr; + self.state.control_packets_tx.fetch_add(1, ORDERING); + debug!("tx packet: {:?}", packet); + socket + .send_to( + packet.serialize().unwrap().as_ref(), + SocketAddr::new(dest, CONTROL_PORT), + ) + .await + .unwrap(); + } + + async fn start_watchdog(&mut self) { + let (tx, mut rx) = mpsc::channel(1); + let cmd_tx = self.tx.clone(); + let duration = self.state.detection_time().into(); + let peer_ip = self.state.peer_addr(); + + tokio::spawn(async move { + let mut running = true; + while running { + match tokio::time::timeout(duration, async { rx.recv().await }).await { + Ok(Some(WatchdogReset::ReceivedPacket)) => { + debug!("Packet received, watchdog ok") + } + Ok(Some(WatchdogReset::Terminate)) | Ok(None) => { + debug!("Watchdog terminating"); + running = false + } + Err(_) => { + info!( + "Peer {} timed out after {}ms", + peer_ip, + duration.as_millis() + ); + cmd_tx + .send(SessionControlCommand::ReceiveTimeout) + .await + .unwrap() } } } - }) + }); + let mut temporary = Some(tx); + swap(&mut temporary, &mut self.rx_watchdog); + if temporary.is_some() { + debug!("Updating old watchdog with new detection time"); + temporary + .as_mut() + .unwrap() + .send(WatchdogReset::Terminate) + .await + .unwrap_or(()) + } } // https://datatracker.ietf.org/doc/html/rfc5880#section-6.8.6 - async fn receive_control_packet(self: Arc, p: &BfdPacket) { + async fn receive_control_packet(&mut self, p: &BfdPacket) { + // https://datatracker.ietf.org/doc/html/rfc5880#section-6.8.4 + // + // If Demand mode is not active, and a period of time equal to the + // Detection Time passes without receiving a BFD Control packet from the + // remote system, and bfd.SessionState is Init or Up, the session has + // gone down -- the local system MUST set bfd.SessionState to Down and + // bfd.LocalDiag to 1 (Control Detection Time Expired). + if self.rx_watchdog.is_some() { + let _ = self + .rx_watchdog + .as_ref() + .unwrap() + .send(WatchdogReset::ReceivedPacket) + .await; + } + self.state.control_packets_rx.fetch_add(1, ORDERING); let received_state = match p.flags.state() { Err(_) => { - eprintln!("Invalid state, discarding"); + warn!("Invalid state, discarding"); return; } Ok(v) => v, }; // If the version number is not correct (1), the packet MUST be discarded. if p.flags.vers() != 1 { - eprintln!("Invalid version {}, discarding", p.flags.vers()); + warn!("Invalid version {}, discarding", p.flags.vers()); return; } // If the Length field is less than the minimum correct value (24 if the A bit is clear, or 26 if the A bit is // set), the packet MUST be discarded. if p.flags.length() < 24 || (p.flags.length() < 26 && p.flags.auth_present()) { - eprintln!("Invalid packet length {}, discarding", p.flags.length()); + warn!("Invalid packet length {}, discarding", p.flags.length()); return; } // TODO: If the Length field is greater than the payload of the encapsulating protocol, the packet MUST be @@ -413,30 +811,26 @@ impl BfdSession { // If the Detect Mult field is zero, the packet MUST be discarded. if p.flags.detect_mult() == 0 { - eprintln!("Invalid detect mult {}, discarding", p.flags.detect_mult()); + warn!("Invalid detect mult {}, discarding", p.flags.detect_mult()); return; } //If the Multipoint (M) bit is nonzero, the packet MUST be discarded. if p.flags.multipoint() { - eprintln!("Invalid multipoint enabled, discarding"); + warn!("Invalid multipoint enabled, discarding"); return; } // If the My Discriminator field is zero, the packet MUST be discarded. if p.my_disc == BfdDiscriminator(0) { - eprintln!("Invalid my discriminator {:?}, discarding", p.my_disc); + warn!("Invalid my discriminator {:?}, discarding", p.my_disc); return; } - let state_read = self.state.read().await; - // If the Your Discriminator field is nonzero, it MUST be used to select the session with which this BFD packet // is associated. If no session is found, the packet MUST be discarded. - // - // TODO: actually implement multiplexing - if p.your_disc != BfdDiscriminator(0) && p.your_disc != state_read.local_discr { - eprintln!( + if p.your_disc != BfdDiscriminator(0) && p.your_disc != self.state.local_discr() { + warn!( "Received unexpected discriminator {:?}, discarding", p.your_disc ); @@ -448,7 +842,7 @@ impl BfdSession { if p.your_disc == BfdDiscriminator(0) && (received_state != BfdState::Down && received_state != BfdState::AdminDown) { - eprintln!( + warn!( "Got packet with zero discriminator and invalid state {:?}, discarding", received_state ); @@ -456,14 +850,14 @@ impl BfdSession { } // If the A bit is set and no authentication is in use (bfd.AuthType is zero), the packet MUST be discarded. - if p.flags.auth_present() && state_read.auth_type == BfdAuthType::None { - eprintln!("Got packet with auth enabled when we disagree, discarding"); + if p.flags.auth_present() && self.state.auth_type() == BfdAuthType::None { + warn!("Got packet with auth enabled when we disagree, discarding"); return; } // If the A bit is clear and authentication is in use (bfd.AuthType is nonzero), the packet MUST be discarded. - if !p.flags.auth_present() && state_read.auth_type != BfdAuthType::None { - eprintln!("Got packet without auth when we expect it, discarding"); + if !p.flags.auth_present() && self.state.auth_type() != BfdAuthType::None { + warn!("Got packet without auth when we expect it, discarding"); return; } @@ -473,17 +867,17 @@ impl BfdSession { unimplemented!("Authentication is not implemented"); } - drop(state_read); - let mut state_write = self.state.write().await; // Set bfd.RemoteDiscr to the value of My Discriminator. - state_write.remote_discr = p.my_disc; + self.state.set_remote_discr(p.my_disc); // Set bfd.RemoteState to the value of the State (Sta) field. - state_write.remote_session_state = received_state; + self.state.set_remote_session_state(received_state); // Set bfd.RemoteDemandMode to the value of the Demand (D) bit. - state_write.remote_demand_mode = p.flags.demand(); + self.state.set_remote_demand_mode(p.flags.demand()); // Set bfd.RemoteMinRxInterval to the value of Required Min RX Interval. - state_write.remote_min_rx_interval = p.required_min_rx; - drop(state_write); + self.state.set_remote_min_rx_interval(p.required_min_rx); + // Not in spec: store the received diagnostic + self.state.set_last_remote_diag(p.flags.diag().unwrap()); + // If the Required Min Echo RX Interval field is zero, the transmission of Echo packets, if any, MUST cease. if p.required_min_echo_rx == BfdInterval(0) { // TODO: implement echo thread @@ -494,141 +888,154 @@ impl BfdSession { // TODO: poll stuff // Update the transmit interval as described in section 6.8.2. - self.clone().update_transmit_interval().await; + self.update_transmit_interval().await; // Update the Detection Time as described in section 6.8.4. - self.clone().update_detection_time(&p).await; + self.update_detection_time(&p).await; - // There's not much actual work to do here so just hold a write lock for all of it - let mut state_write = self.state.write().await; // If bfd.SessionState is AdminDown // Discard the packet - if state_write.session_state == BfdState::AdminDown { + if self.state.session_state() == BfdState::AdminDown { return; } // If received state is AdminDown if received_state == BfdState::AdminDown { // If bfd.SessionState is not Down - if state_write.session_state != BfdState::Down { + if self.state.session_state() != BfdState::Down { // Set bfd.LocalDiag to 3 (Neighbor signaled session down) - state_write.local_diag = BfdDiagnostic::NeighborDown; + self.state.set_local_diag(BfdDiagnostic::NeighborDown); // Set bfd.SessionState to Down - state_write.session_state = BfdState::Down; + self.transition(BfdState::Down).await; } } else { // If bfd.SessionState ... - match state_write.session_state { + match self.state.session_state() { BfdState::Down => { // If received State is Down if received_state == BfdState::Down { // Set bfd.SessionState to Init - state_write.session_state = BfdState::Init; + self.transition(BfdState::Init).await; // Else if received State is Init } else if received_state == BfdState::Init { // Set bfd.SessionState to Up - state_write.session_state = BfdState::Up; + self.transition(BfdState::Up).await; } } BfdState::Init => { // If received State is Init or Up if received_state == BfdState::Init || received_state == BfdState::Up { // Set bfd.SessionState to Up - state_write.session_state = BfdState::Up; + self.transition(BfdState::Up).await; } } BfdState::Up => { // If received State is Down if received_state == BfdState::Down { // Set bfd.LocalDiag to 3 (Neighbor signaled session down) - state_write.local_diag = BfdDiagnostic::NeighborDown; + self.state.set_local_diag(BfdDiagnostic::NeighborDown); // Set bfd.SessionState to Down - state_write.session_state = BfdState::Down; + self.transition(BfdState::Down).await; } } BfdState::AdminDown => unreachable!("unexpected AdminDown"), // AdminDown is discarded earlier } - drop(state_write); - // Check to see if Demand mode should become active or not (see section 6.6). + // - we don't use demand mode // If bfd.RemoteDemandMode is 1, bfd.SessionState is Up, and bfd.RemoteSessionState is Up, Demand mode is - // active on the remote system and the local system MUST cease the periodic transmission of BFD Control - // packets (see section 6.8.7). - - // TODO: implement ceasing/restarting of control packets due to demand mode - if p.flags.demand() { - eprintln!("WARNING: Demand mode requested but not implemented"); + // active on the remote system and + if self.state.remote_demand_mode() + && self.state.session_state() == BfdState::Up + && self.state.remote_session_state() == BfdState::Up + { + // the local system MUST cease the periodic transmission of BFD Control packets (see section 6.8.7). + self.periodic + .tx + .send(PeriodicControlCommand::Stop) + .await + .unwrap(); } // If the Poll (P) bit is set, send a BFD Control packet to the remote system with the Poll (P) bit clear, // and the Final (F) bit set (see section 6.8.7). - if p.flags.poll() { - - // TODO: Implement sending stuff - } + if p.flags.poll() {} // If the packet was not discarded, it has been received for purposes of the Detection Time expiration rules // in section 6.8.4. } } // https://datatracker.ietf.org/doc/html/rfc5880#section-6.8.2 - async fn update_transmit_interval(self: Arc) { - let state = self.state.read().await; - state - .periodic_cmd_channel - .send(PeriodicControlCommand::SetMinInterval(std::cmp::max( - state.desired_min_tx_interval, - state.remote_min_rx_interval, - ))) - .await - .unwrap() + async fn update_transmit_interval(&mut self) { + let new_base_interval = std::cmp::max( + self.state.desired_min_tx_interval(), + self.state.remote_min_rx_interval(), + ); + if new_base_interval != self.cur_interval { + self.periodic + .tx + .send(PeriodicControlCommand::SetMinInterval(new_base_interval)) + .await + .unwrap() + } + self.cur_interval = new_base_interval; } // https://datatracker.ietf.org/doc/html/rfc5880#section-6.8.4 - async fn update_detection_time(self: Arc, p: &BfdPacket) { - let mut state = self.state.write().await; - state.detection_time = if !state.demand_mode { - time::Duration::from_micros( - p.flags.detect_mult() as u64 - * std::cmp::max(state.required_min_rx_interval, p.desired_min_tx).0 as u64, - ) + async fn update_detection_time(&mut self, p: &BfdPacket) { + let old = self.state.detection_time(); + self.state.set_detection_time(if !self.state.demand_mode() { + (p.flags.detect_mult() as u32 + * std::cmp::max(self.state.required_min_rx_interval(), p.desired_min_tx).0) + .into() } else { - time::Duration::from_micros( - state.detect_mult as u64 - * std::cmp::max(state.desired_min_tx_interval, state.remote_min_rx_interval).0 - as u64, - ) + (self.state.detect_mult() as u32 + * std::cmp::max( + self.state.desired_min_tx_interval(), + self.state.remote_min_rx_interval(), + ) + .0) + .into() + }); + if old != self.state.detection_time() { + self.start_watchdog().await } } - - async fn run(self: Arc, rx: mpsc::Receiver) { - let (cmd_tx, cmd_rx) = mpsc::channel(32); - self.state.write().await.periodic_cmd_channel = cmd_tx; - - let rxt = self.clone().spawn_control_thread(rx).await; - let pxt = self - .clone() - .spawn_periodic_thread(cmd_rx, self.state.read().await.desired_min_tx_interval) - .await; - join!(rxt, pxt).0.unwrap(); - } } #[tokio::main] async fn main() -> io::Result<()> { - let local = SocketAddr::from_str("192.168.65.224:3784").unwrap(); - let peers = vec![SocketAddr::from_str("127.0.0.1:3784").unwrap()]; - let mut sessions = HashMap::new(); + env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); + + info!("rust-bfd starting up"); + + let local = SocketAddr::from_str("192.168.122.1:3784").unwrap(); + let peers = vec![SocketAddr::from_str("192.168.122.132:3784").unwrap()]; + let mut rng = rand::thread_rng(); + let mut sessions_by_ip = HashMap::new(); + let mut sessions_by_discr = HashMap::new(); for peer in peers { - let (tx, rx) = mpsc::channel(32); - let session = Arc::new(BfdSession::new(local.ip(), peer.ip()).await.unwrap()); - let handle = task::spawn(session.clone().run(rx)); + let mut local_discr = BfdDiscriminator(0); + while local_discr.0 == 0 || sessions_by_discr.get(&local_discr).is_some() { + local_discr = BfdDiscriminator(rng.gen()); + } + let session = BfdSessionHandle::new(local.ip(), peer.ip(), local_discr).await; - sessions.insert((local.ip(), peer.ip()), (tx, session, handle)); + sessions_by_ip.insert((local.ip(), peer.ip()), local_discr); + sessions_by_discr.insert(local_discr, session); } + debug!( + "configured sessions: {}", + future::join_all( + sessions_by_discr + .values() + .map(|session| async { session.get_stats().await.unwrap().to_string() }) + ) + .await + .join("\n\t") + ); let control_sock = Arc::new(UdpSocket::bind(local).await?); // If BFD authentication is not in use on a session, all BFD Control packets for the session MUST be sent with a @@ -640,19 +1047,51 @@ async fn main() -> io::Result<()> { let mut buf = [0; 1024]; loop { // TODO: All received BFD Control packets that are demultiplexed to the session MUST be discarded if the - // received TTL or Hop Limit is not equal to 255. + // received TTL or Hop Limit is not equal to 255. Need raw sockets for this. let (len, addr) = control_sock.recv_from(&mut buf).await.unwrap(); // TODO: fallibility? - println!("{:?} bytes received from {:?}", len, addr); - if let Some(session) = - sessions.get(&(control_sock.local_addr().unwrap().ip(), addr.ip())) - { - println!("matched to session"); - session - .0 - .send(SessionControlCommand::RxPacket(buf[0..len].to_vec())) - .await - .unwrap(); + if let Ok((_leftover, packet)) = BfdPacket::parse(buf.as_slice()) { + debug!("rx packet: {:?}", packet); + let our_disc = if packet.your_disc == BfdDiscriminator(0) { + if let Some(discr) = + sessions_by_ip.get(&(control_sock.local_addr().unwrap().ip(), addr.ip())) + { + debug!( + "Found session for unknown discriminator from {}: {}", + addr.ip(), + discr + ); + discr + } else { + warn!("Unable to match packet to session with {}", addr.ip()); + continue; + } + } else { + &packet.your_disc + }; + if let Some(session) = sessions_by_discr.get(our_disc) { + session + .tx + .send(SessionControlCommand::RxPacket(packet)) + .await + .unwrap(); + } else { + warn!("Unable to match packet to session with {}", our_disc); + continue; + } + } else { + warn!("Failed to parse packet"); } + + debug!( + "configured sessions: {}", + future::join_all( + sessions_by_discr + .values() + .map(|session| async { session.get_stats().await.unwrap().to_string() }) + ) + .await + .join("\n\t") + ); } });