diff --git a/.cargo/config.toml b/.cargo/config.toml index d7899423..09928bdd 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -6,3 +6,6 @@ LIBZFS_CORE_LOOKUP_WITH = "link" # reqwest OPENSSL_LIB_DIR = "/usr/lib" OPENSSL_INCLUDE_DIR = "/usr/include/openssl-1.1" + +[net] +git-fetch-with-cli = true diff --git a/.gitignore b/.gitignore index eb5a316c..7d0d0d09 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ target +*.sw* diff --git a/Cargo.lock b/Cargo.lock index 941382d8..ee1762e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,12 @@ version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf" +[[package]] +name = "arc-swap" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6df5aef5c5830360ce5218cecb8f018af3438af5686ae945094affc86fdec63" + [[package]] name = "async-trait" version = "0.1.51" @@ -37,6 +43,12 @@ dependencies = [ "syn", ] +[[package]] +name = "atomic-option" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0db678acb667b525ac40a324fc5f7d3390e29239b31c7327bb8157f5b4fff593" + [[package]] name = "atty" version = "0.2.14" @@ -101,18 +113,24 @@ dependencies = [ "wyz", ] -[[package]] -name = "build-env" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1522ac6ee801a11bf9ef3f80403f4ede6eb41291fac3dde3de09989679305f25" - [[package]] name = "bumpalo" version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" +[[package]] +name = "bus" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e66e1779f5b1440f1a58220ba3b3ded4427175f0a9fb8d7066521f8b4e8f2b" +dependencies = [ + "atomic-option", + "crossbeam-channel 0.4.4", + "num_cpus", + "parking_lot_core 0.7.2", +] + [[package]] name = "bytes" version = "1.1.0" @@ -217,6 +235,26 @@ dependencies = [ "syn", ] +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + [[package]] name = "core-foundation" version = "0.9.1" @@ -233,6 +271,16 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" +[[package]] +name = "crossbeam-channel" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" +dependencies = [ + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + [[package]] name = "crossbeam-channel" version = "0.5.1" @@ -240,27 +288,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils", + "crossbeam-utils 0.8.5", ] [[package]] name = "crossbeam-utils" -version = "0.8.5" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ - "cfg-if 1.0.0", + "autocfg", + "cfg-if 0.1.10", "lazy_static", ] [[package]] -name = "cstr-argument" -version = "0.1.1" +name = "crossbeam-utils" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20bd4e8067c20c7c3a4dea759ef91d4b18418ddb5bd8837ef6e2f2f93ca7ccbb" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" dependencies = [ - "cfg-if 0.1.10", - "memchr", + "cfg-if 1.0.0", + "lazy_static", ] [[package]] @@ -284,12 +333,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - [[package]] name = "dropshot" version = "0.5.2-dev" @@ -388,28 +431,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared 0.1.1", -] - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared 0.3.0", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63f713f8b2aa9e24fec85b0e290c56caee12e3b6ae0aeeda238a75b28251afd6" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "foreign-types-shared", ] [[package]] @@ -418,12 +440,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -[[package]] -name = "foreign-types-shared" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7684cf33bb7f28497939e8c7cf17e3e4e3b8d9a0080ffa4f8ae2f515442ee855" - [[package]] name = "form_urlencoded" version = "1.0.1" @@ -437,6 +453,7 @@ dependencies = [ [[package]] name = "fs2" version = "0.4.3" +source = "git+https://github.com/pfmooney/fs2-rs?branch=illumos-target#c6daa1e0a6bd46dab09049072768eaf31d654638" dependencies = [ "libc", "winapi", @@ -542,17 +559,6 @@ dependencies = [ "slab", ] -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.3" @@ -561,7 +567,7 @@ checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -720,24 +726,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "illumos" -version = "0.1.0" -dependencies = [ - "bindgen", - "dropshot", - "icmpv6", - "platform", - "reqwest", - "rift", - "rift_protocol", - "slog", - "slog-async", - "slog-term", - "socket2", - "tokio", -] - [[package]] name = "indexmap" version = "1.7.0" @@ -794,7 +782,6 @@ version = "0.1.0" dependencies = [ "anyhow", "libfalcon", - "zfs-core", ] [[package]] @@ -822,11 +809,10 @@ dependencies = [ "anyhow", "bindgen", "fs2", - "nvpair", + "netadm-sys 0.1.0 (git+ssh://git@github.com/oxidecomputer/netadm-sys)", "regex", "smf", "thiserror", - "zfs-core", "zone", ] @@ -876,12 +862,39 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + [[package]] name = "memchr" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +[[package]] +name = "mg-illumos" +version = "0.1.0" +dependencies = [ + "bindgen", + "clap 3.0.0-beta.4", + "dropshot", + "icmpv6", + "netadm-sys 0.1.0", + "platform", + "reqwest", + "rift", + "rift_protocol", + "slog", + "slog-async", + "slog-envlogger", + "slog-term", + "socket2", + "tokio", +] + [[package]] name = "mime" version = "0.3.16" @@ -928,6 +941,29 @@ dependencies = [ "tempfile", ] +[[package]] +name = "netadm-sys" +version = "0.1.0" +dependencies = [ + "bindgen", + "colored", + "rusty-doors", + "thiserror", + "tracing", +] + +[[package]] +name = "netadm-sys" +version = "0.1.0" +source = "git+ssh://git@github.com/oxidecomputer/netadm-sys#79b01cb15ca3019281761eb5d7ad9ffd2622ea46" +dependencies = [ + "bindgen", + "colored", + "rusty-doors", + "thiserror", + "tracing", +] + [[package]] name = "nom" version = "6.1.2" @@ -978,21 +1014,6 @@ dependencies = [ "libc", ] -[[package]] -name = "nvpair" -version = "0.5.0" -source = "git+https://github.com/oxidecomputer/rust-libzfs#e79f612c116eb752827d0a7f581b99eab288d709" -dependencies = [ - "cstr-argument", - "foreign-types 0.5.0", - "nvpair-sys", -] - -[[package]] -name = "nvpair-sys" -version = "0.4.0" -source = "git+https://github.com/oxidecomputer/rust-libzfs#e79f612c116eb752827d0a7f581b99eab288d709" - [[package]] name = "once_cell" version = "1.8.0" @@ -1019,7 +1040,7 @@ checksum = "8d9facdb76fec0b73c406f125d44d86fdad818d66fef0531eec9233ca425ff4a" dependencies = [ "bitflags", "cfg-if 1.0.0", - "foreign-types 0.3.2", + "foreign-types", "libc", "once_cell", "openssl-sys", @@ -1058,7 +1079,21 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core", + "parking_lot_core 0.8.5", +] + +[[package]] +name = "parking_lot_core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" +dependencies = [ + "cfg-if 0.1.10", + "cloudabi", + "libc", + "redox_syscall 0.1.57", + "smallvec", + "winapi", ] [[package]] @@ -1070,7 +1105,7 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.10", "smallvec", "winapi", ] @@ -1117,7 +1152,10 @@ version = "0.1.0" dependencies = [ "icmpv6", "rift_protocol", + "schemars", + "serde", "thiserror", + "tokio", ] [[package]] @@ -1186,19 +1224,6 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc 0.2.0", -] - [[package]] name = "rand" version = "0.8.4" @@ -1206,19 +1231,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.3", - "rand_hc 0.3.1", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "rand_chacha", + "rand_core", + "rand_hc", ] [[package]] @@ -1228,16 +1243,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", + "rand_core", ] [[package]] @@ -1246,16 +1252,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.3", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", + "getrandom", ] [[package]] @@ -1264,7 +1261,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" dependencies = [ - "rand_core 0.6.3", + "rand_core", ] [[package]] @@ -1281,6 +1278,12 @@ dependencies = [ "socket2", ] +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + [[package]] name = "redox_syscall" version = "0.2.10" @@ -1296,8 +1299,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ - "getrandom 0.2.3", - "redox_syscall", + "getrandom", + "redox_syscall 0.2.10", ] [[package]] @@ -1365,7 +1368,9 @@ dependencies = [ name = "rift" version = "0.1.0" dependencies = [ + "bus", "dropshot", + "hostname", "icmpv6", "platform", "rift_protocol", @@ -1386,6 +1391,19 @@ dependencies = [ "serde", ] +[[package]] +name = "riftadm" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap 3.0.0-beta.4", + "colored", + "platform", + "reqwest", + "rift", + "tabwriter", +] + [[package]] name = "ron" version = "0.6.4" @@ -1409,6 +1427,24 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" +[[package]] +name = "rusty-doors" +version = "0.1.0" +source = "git+ssh://git@github.com/oxidecomputer/rusty-doors#49b8f847b979bc769ec86808c0dd51a83538fd5f" +dependencies = [ + "bindgen", + "rusty-doors-macros", +] + +[[package]] +name = "rusty-doors-macros" +version = "0.1.0" +source = "git+ssh://git@github.com/oxidecomputer/rusty-doors#49b8f847b979bc769ec86808c0dd51a83538fd5f" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "ryu" version = "1.0.5" @@ -1589,7 +1625,7 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "766c59b252e62a34651412870ff55d8c4e6d04df19b43eecb2703e417b097ffe" dependencies = [ - "crossbeam-channel", + "crossbeam-channel 0.5.1", "slog", "take_mut", "thread_local", @@ -1607,6 +1643,21 @@ dependencies = [ "slog-json", ] +[[package]] +name = "slog-envlogger" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "906a1a0bc43fed692df4b82a5e2fbfc3733db8dad8bb514ab27a4f23ad04f5c0" +dependencies = [ + "log", + "regex", + "slog", + "slog-async", + "slog-scope", + "slog-stdlog", + "slog-term", +] + [[package]] name = "slog-json" version = "2.4.0" @@ -1619,6 +1670,28 @@ dependencies = [ "slog", ] +[[package]] +name = "slog-scope" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f95a4b4c3274cd2869549da82b57ccc930859bdbf5bcea0424bc5f140b3c786" +dependencies = [ + "arc-swap", + "lazy_static", + "slog", +] + +[[package]] +name = "slog-stdlog" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8228ab7302adbf4fcb37e66f3cda78003feb521e7fd9e3847ec117a7784d0f5a" +dependencies = [ + "log", + "slog", + "slog-scope", +] + [[package]] name = "slog-term" version = "2.8.0" @@ -1647,27 +1720,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "snafu" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7" -dependencies = [ - "doc-comment", - "snafu-derive", -] - -[[package]] -name = "snafu-derive" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "socket2" version = "0.4.1" @@ -1701,6 +1753,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tabwriter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36205cfc997faadcc4b0b87aaef3fbedafe20d38d4959a7ca6ff803564051111" +dependencies = [ + "lazy_static", + "regex", + "unicode-width", +] + [[package]] name = "take_mut" version = "0.2.2" @@ -1721,8 +1784,8 @@ checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ "cfg-if 1.0.0", "libc", - "rand 0.8.4", - "redox_syscall", + "rand", + "redox_syscall 0.2.10", "remove_dir_all", "winapi", ] @@ -1897,9 +1960,21 @@ checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" dependencies = [ "cfg-if 1.0.0", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98863d0dd09fa59a1b79c6750ad80dbda6b75f4e71c437a6a1a8cb91a8bcbd77" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.19" @@ -1966,7 +2041,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.3", + "getrandom", "serde", ] @@ -1998,12 +2073,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" @@ -2152,30 +2221,6 @@ dependencies = [ "linked-hash-map", ] -[[package]] -name = "zfs-core" -version = "0.5.0" -source = "git+https://github.com/oxidecomputer/rust-libzfs#e79f612c116eb752827d0a7f581b99eab288d709" -dependencies = [ - "cstr-argument", - "foreign-types 0.5.0", - "nvpair", - "rand 0.7.3", - "snafu", - "zfs-core-sys", -] - -[[package]] -name = "zfs-core-sys" -version = "0.5.0" -source = "git+https://github.com/oxidecomputer/rust-libzfs#e79f612c116eb752827d0a7f581b99eab288d709" -dependencies = [ - "build-env", - "libc", - "nvpair-sys", - "pkg-config", -] - [[package]] name = "zone" version = "0.1.8" diff --git a/Cargo.toml b/Cargo.toml index 29e63a0e..d6ad81b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "platform", "rift", "rift_protocol", + "riftadm", "illumos", "lab", "icmpv6", diff --git a/illumos/Cargo.toml b/illumos/Cargo.toml index 6d6ffd83..031bf4b3 100644 --- a/illumos/Cargo.toml +++ b/illumos/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "illumos" +name = "mg-illumos" version = "0.1.0" edition = "2018" @@ -12,11 +12,14 @@ rift_protocol = { path = "../rift_protocol" } slog = "2.7" slog-term = "2.7" slog-async = "2.7" +slog-envlogger = "2.2" socket2 = { version = "0.4", features = ["all"] } icmpv6 = { path = "../icmpv6" } tokio = { version = "1.0", features = ["full"] } dropshot = { git = "https://github.com/oxidecomputer/dropshot" } reqwest = { version = "0.11", features = ["blocking", "json"] } +netadm-sys = { path = "/home/ry/netadm-sys/lib" } +clap = { version = "3.0.0-beta.4", features = ["color"] } [build-dependencies] bindgen = "0.59" diff --git a/illumos/build.rs b/illumos/build.rs deleted file mode 100644 index feae4e83..00000000 --- a/illumos/build.rs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2021 Oxide Computer Company -// -// Derived from https://github.com/oxidecomputer/libscf-sys/blob/main/build.rs - -use bindgen; -use std::env; -use std::path::PathBuf; - -fn main() { - #[cfg(not(target_os = "illumos"))] - compile_error!("libdladm-sys is only supported on illumos"); - - println!("cargo:rustc-link-lib=dladm"); - println!("cargo:rustc-link-lib=ipadm"); - println!("cargo:rerun-if-changed=wrapper.h"); - - if let Err(_) = env::var("LIBCLANG_PATH") { - env::set_var("LIBCLANG_PATH", "/opt/ooce/clang-11.0/lib"); - } - - let illumos_src_path = match env::var("ILLUMOS_SRC") { - Err(_) => { - println!("illumos path not set, using system libraries"); - println!("this will not work outside ry's personal illumos build"); - println!("https://github.com/oxidecomputer/illumos-gate/tree/netapis"); - "".to_string() - } - Ok(path) => path.to_string(), - }; - - let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); - - if illumos_src_path != "" { - let bindings = bindgen::Builder::default() - .header("wrapper.h") - .clang_arg(format!("-I{}/usr/src/uts/common", illumos_src_path)) - .clang_arg(format!( - "-I{}/usr/src/lib/libdladm/common", illumos_src_path)) - .clang_arg(format!( - "-I{}/usr/src/lib/libipadm/common", illumos_src_path)) - .parse_callbacks(Box::new(bindgen::CargoCallbacks)) - .generate() - .expect("unable to generate bindings"); - - bindings - .write_to_file(out_path.join("bindings.rs")) - .expect("unable to write bindings"); - } else { - let bindings = bindgen::Builder::default() - .header("wrapper.h") - .parse_callbacks(Box::new(bindgen::CargoCallbacks)) - .generate() - .expect("unable to generate bindings"); - - bindings - .write_to_file(out_path.join("bindings.rs")) - .expect("unable to write bindings"); - - } -} diff --git a/illumos/src/illumos.rs b/illumos/src/illumos.rs deleted file mode 100644 index 6efded1c..00000000 --- a/illumos/src/illumos.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2021 Oxide Computer Company - -// import generated bindings -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] -#![allow(improper_ctypes)] -#![allow(dead_code)] -#![allow(deref_nullptr)] -#![allow(unaligned_references)] -include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/illumos/src/link.rs b/illumos/src/link.rs index 541cff65..1b6132e2 100644 --- a/illumos/src/link.rs +++ b/illumos/src/link.rs @@ -1,8 +1,9 @@ // Copyright 2021 Oxide Computer Company -use std::sync::{Arc, Mutex}; +use std::sync::Arc; +use tokio::sync::Mutex; use std::net::{SocketAddr, SocketAddrV6, Ipv6Addr}; -use std::sync::mpsc::Sender; +use tokio::sync::mpsc::Sender; use dropshot::{ endpoint, ConfigDropshot, @@ -13,41 +14,42 @@ use dropshot::{ RequestContext, HttpResponseOk, HttpError, + HttpServer, TypedBody, }; use rift::LINKINFO_PORT; -use rift_protocol::LinkInfo; +use rift_protocol::lie::LIEPacket; -struct LinkHandlerContext { - tx: Arc::>>, +pub(crate) struct LinkHandlerContext { + tx: Arc::>>, } #[endpoint { method = POST, - path = "linkinfo" + path = "/linkinfo" }] async fn riftp_linkinfo( ctx: Arc>, - rq: TypedBody, + rq: TypedBody, ) -> Result, HttpError> { let api_context = ctx.context(); - let tx = api_context.tx.lock().unwrap(); + let tx = api_context.tx.lock().await; - match (*tx).send(rq.into_inner()) { + match (*tx).send(rq.into_inner()).await { Ok(_) => Ok(HttpResponseOk(())), Err(e) => Err(HttpError::for_internal_error(format!( - "error consuming LinkInfo: {}", e + "error consuming LIEPacket: {}", e ))), } } -pub(crate) async fn link_handler( +pub(crate) fn link_handler( addr: Ipv6Addr, - tx: Arc::>>, -) -> Result<(), String> { + tx: Arc::>>, +) -> Result, String> { let sa = SocketAddr::V6( SocketAddrV6::new(addr, LINKINFO_PORT, 0, 0) @@ -69,14 +71,11 @@ pub(crate) async fn link_handler( let api_context = LinkHandlerContext{tx: tx}; - let server = HttpServerStarter::new( + Ok(HttpServerStarter::new( &config_dropshot, api, api_context, &log, ).map_err(|e| format!("create dropshot link server: {}", e))? - .start(); - - server.await - + .start()) } diff --git a/illumos/src/main.rs b/illumos/src/main.rs index 510fbf0b..856c349b 100644 --- a/illumos/src/main.rs +++ b/illumos/src/main.rs @@ -1,29 +1,49 @@ // Copyright 2021 Oxide Computer Company +#![feature(ip)] +#![feature(maybe_uninit_slice)] + use rift::Rift; +use rift::config::Config; use slog; use slog_term; use slog_async; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; +use tokio::sync::Mutex; +use slog::Drain; +use clap::{AppSettings, Clap}; mod platform; -mod illumos; mod link; -use slog::Drain; +#[derive(Clap)] +#[clap( + version = "0.1", + author = "Ryan Goodfellow " +)] +#[clap(setting = AppSettings::ColoredHelp)] +#[clap(setting = AppSettings::InferSubcommands)] +struct Opts { + #[clap(short, long, parse(from_occurrences))] + verbose: i32, + id: u64, +} + #[tokio::main] async fn main() -> Result<(), String> { let decorator = slog_term::TermDecorator::new().build(); let drain = slog_term::FullFormat::new(decorator).build().fuse(); + let drain = slog_envlogger::new(drain).fuse(); let drain = slog_async::Async::new(drain).build().fuse(); - let log = slog::Logger::root(drain, slog::o!()); + let opts: Opts = Opts::parse(); + let ilu = Arc::new(Mutex::new(crate::platform::Illumos{log: log.clone()})); - let mut riftp = Rift::new(ilu, log.clone()); - match riftp.run() { + let mut riftp = Rift::new(ilu, log.clone(), Config{id: opts.id}); + match riftp.run().await { Ok(()) => slog::warn!(log, "early exit?"), Err(e) => slog::error!(log, "rift: {}", e), }; diff --git a/illumos/src/platform.rs b/illumos/src/platform.rs index 147d7225..4e661d50 100644 --- a/illumos/src/platform.rs +++ b/illumos/src/platform.rs @@ -1,18 +1,25 @@ // Copyright 2021 Oxide Computer Company -use std::sync::{Arc, Mutex}; -use platform::{Platform, error::Error}; +use std::sync::Arc; use std::mem::MaybeUninit; -use slog::{Logger, debug, error}; -use std::{ptr}; -use crate::illumos; +use slog::{Logger, error, debug}; use socket2::{Socket, Domain, Type, Protocol, SockAddr}; -use std::net::{Ipv6Addr, SocketAddrV6}; +use std::net::{IpAddr, Ipv6Addr, SocketAddrV6}; use icmpv6::{RouterSolicitation, RouterAdvertisement, RDPMessage}; -use std::sync::mpsc::{channel, Sender, Receiver}; -use std::thread; -use rift_protocol::LinkInfo; +use std::time::Duration; +use tokio::{ + spawn, select, + time::sleep, + sync::{Mutex, mpsc::{channel, Sender, Receiver}}, +}; +use rift_protocol::lie::LIEPacket; use rift::{LINKINFO_PORT, RDP_MADDR}; +use platform::{ + IpIfAddr, + Platform, + LinkStatus, + error::Error, +}; pub(crate) struct Illumos { pub(crate) log: Logger, @@ -20,7 +27,87 @@ pub(crate) struct Illumos { impl Platform for Illumos { - fn advertise_rift_router(&self) -> Result<(), Error> { + fn get_links(&self) -> Result, Error> { + + let links = match netadm_sys::get_links() { + Ok(links) => links, + Err(e) => return Err(Error::Platform(format!("get links: {}", e))), + }; + + let mut result = Vec::new(); + for l in links { + result.push(LinkStatus{ + name: l.name, + state: match l.state { + netadm_sys::LinkState::Unknown => platform::LinkState::Unknown, + netadm_sys::LinkState::Down => platform::LinkState::Down, + netadm_sys::LinkState::Up => platform::LinkState::Up, + } + }) + } + + Ok(result) + + } + + fn get_link_status(&self, link_name: impl AsRef) -> Result { + let _link_name = link_name.as_ref().to_string(); + match netadm_sys::linkname_to_id(&_link_name) { + Err(e) => Err(Error::Platform(format!("linkname to id: {}", e))), + Ok(id) => { + match netadm_sys::get_link(id) { + Err(e) => Err(Error::Platform(format!("get link info: {}", e))), + Ok(info) => Ok(LinkStatus{ + name: info.name, + state: match info.state { + netadm_sys::LinkState::Unknown => platform::LinkState::Unknown, + netadm_sys::LinkState::Down => platform::LinkState::Down, + netadm_sys::LinkState::Up => platform::LinkState::Up, + }, + }) + } + } + } + } + + fn get_interface_v6ll(&self, interface: impl AsRef) -> Result, Error> { + + //TODO provide a function in netadm_sys that gets addrs for a given + //interface without having to iterate all the interfaces + + let addr_map = match netadm_sys::get_ipaddrs() { + Ok(addrs) => addrs, + Err(e) => return Err(Error::Platform(format!("get ip addrs: {}", e))), + }; + + for (ifname, addrs) in addr_map { + if ifname.as_str() == interface.as_ref() { + for a in addrs { + match a.addr { + IpAddr::V4(_) => continue, + IpAddr::V6(v6addr) => { + if v6addr.is_unicast_link_local() { + return Ok(Some(IpIfAddr{ + addr: v6addr, + if_index: a.index, + })) + } + } + } + } + } + } + + return Ok(None) + + } + + fn advertise_rift_router(&self, interface: Option) -> Result<(), Error> { + + let link_id = match interface.as_ref() { + None => 0, // any interface + Some(ipifa) => ipifa.if_index, + }; let socket = Socket::new( Domain::IPV6, @@ -35,14 +122,14 @@ impl Platform for Illumos { Error::Platform(format!("diable multicast loop: {}", e)) )?; - let sa = SockAddr::from(SocketAddrV6::new(rift::RDP_MADDR, 0, 0, 0)); + let sa = SockAddr::from(SocketAddrV6::new(rift::RDP_MADDR, 0, 0, link_id as u32)); let ra = RouterAdvertisement::new( 1, //hop limit false, // managed address (dhcpv6) false, // other stateful (stateless dhcpv6) 0, // not a default router - 100, // consider this router reachable for 100 ms + 3000, // consider this router reachable for 3000 ms 0, // No retrans timer specified None, // no source address, Some(9216), // jumbo frames ftw @@ -58,7 +145,12 @@ impl Platform for Illumos { } - fn solicit_rift_routers(&self) -> Result<(), Error> { + fn solicit_rift_routers(&self, interface: Option) -> Result<(), Error> { + + let link_id = match interface.as_ref() { + None => 0, // any interface + Some(ipifa) => ipifa.if_index, + }; let socket = Socket::new( Domain::IPV6, @@ -73,7 +165,7 @@ impl Platform for Illumos { Error::Platform(format!("diable multicast loop: {}", e)) )?; - let sa = SockAddr::from(SocketAddrV6::new(RDP_MADDR, 0, 0, 0)); + let sa = SockAddr::from(SocketAddrV6::new(RDP_MADDR, 0, 0, link_id as u32)); let rs = RouterSolicitation::new(None); let wire = rs.wire(); @@ -85,7 +177,12 @@ impl Platform for Illumos { } - fn get_rdp_channel(&self) -> Result, Error> { + fn get_rdp_channel(&self, interface: Option) -> Result, Error> { + + let link_id = match interface.as_ref() { + None => 0, // any interface + Some(ipifa) => ipifa.if_index, + }; let socket = Socket::new( Domain::IPV6, @@ -94,23 +191,32 @@ impl Platform for Illumos { ).map_err(|e| Error::Platform(format!("new socket: {}", e)))?; socket - .join_multicast_v6(&RDP_MADDR, 0) + .join_multicast_v6(&RDP_MADDR, link_id as u32) .map_err(|e| Error::Platform(format!("join multicast: {}", e)))?; - let (tx, rx): (Sender, Receiver) = channel(); + let (tx, rx): (Sender, Receiver) = channel(32); let log = self.log.clone(); - thread::spawn(move || loop { + spawn(async move { loop { + + let mut _buf = [MaybeUninit::new(0); 1024]; - let mut buf: [u8; 1024] = [0;1024]; - let mut _buf = unsafe{ - &mut(*buf.as_mut_ptr().cast::<[MaybeUninit; 1024]>()) + match socket.set_nonblocking(true) { + Ok(_) => {} + Err(e) => { + error!(log, "set nonblocking socket option: {}", e); + break; + } }; - let (sz, sender) = match socket.recv_from(_buf) { + let (sz, sender) = match socket.recv_from(&mut _buf) { Ok(x) => x, Err(e) => { + if e.kind() == std::io::ErrorKind::WouldBlock { + sleep(Duration::from_millis(10)).await; + continue; + } error!(log, "socket recv: {}", e); continue; }, @@ -121,7 +227,9 @@ impl Platform for Illumos { _ => None, }; - let msg = match icmpv6::parse_icmpv6(&buf[..sz]) { + let msg = match icmpv6::parse_icmpv6( + unsafe{&MaybeUninit::slice_assume_init_ref(&_buf)[..sz]}, + ) { Some(packet) => RDPMessage{ from: senderv6, packet: packet, @@ -129,151 +237,88 @@ impl Platform for Illumos { None => { continue; }, }; - match tx.send(msg) { + match tx.send(msg).await { Ok(_) => {}, - Err(e) => error!(log, "rdp channel send: {}", e), + Err(e) => { + debug!(log, "rdp channel closed, exiting rdp loop: {}", e); + break; + } }; - }); + }}); Ok(rx) } - fn get_link_channel(&self, peer: Ipv6Addr) - -> Result<(Sender, Receiver), Error> { + fn get_link_channel(&self, local: Ipv6Addr, peer: Ipv6Addr) + -> Result<(Sender, Receiver), Error> { - //ingress - let ilog = self.log.clone(); - let (_itx, irx): (Sender, Receiver) = channel(); + let (_itx, irx): (Sender, Receiver) = channel(32); let itx = Arc::new(Mutex::new(_itx)); - tokio::spawn(async move { - match crate::link::link_handler(peer, itx).await { - Ok(_) => {}, - Err(e) => error!(ilog, "failed to start link handler: {}", e), - } - }); - - //egress let elog = self.log.clone(); - let (etx, erx): (Sender, Receiver) = channel(); - tokio::spawn(async move { loop { + let (etx, mut erx): (Sender, Receiver) = channel(32); + + spawn(async move { - let msg = match erx.recv() { - Ok(m) => m, + let mut server = match crate::link::link_handler(local, itx) { + Ok(s) => s, Err(e) => { - error!(elog, "linkinfo egress channel rx: {}", e); - continue; + error!(elog, "failed to crate dropshot server: {}", e); + return; } }; - let client = reqwest::Client::new(); - let resp = client - .post(format!("http://{}:{}/linkinfo", peer, LINKINFO_PORT)) - .json(&msg) - .send() - .await; + loop { - match resp { - Ok(_) => {}, - Err(e) => error!(elog, "failed to send linkinfo: {}", e), - } + select! { - }}); + rx_msg = erx.recv() => { + let msg = match rx_msg { + Some(m) => m, + None => { + error!(elog, "linkinfo egress channel closed"); + match server.close().await { + Ok(_) => {}, + Err(e) => error!(elog, "dropshot server close: {}", e), + }; + return; + } + }; - Ok((etx, irx)) + let client = reqwest::Client::new(); + let resp = client + .post(format!("http://[{}]:{}/linkinfo", peer, LINKINFO_PORT)) + .json(&msg) + .send() + .await; - } - -} + match resp { + Ok(_) => {}, + Err(e) => error!(elog, "failed to send linkinfo: {}", e), + }; -#[allow(dead_code)] -const LIFC_DEFAULT: u32 = - illumos::LIFC_NOXMIT | illumos::LIFC_TEMPORARY | - illumos::LIFC_ALLZONES | illumos::LIFC_UNDER_IPMP; - -impl Illumos { - - #[allow(dead_code)] - fn ipadm_handle(&self) -> Result { - - let mut handle: illumos::ipadm_handle_t = ptr::null_mut(); - let status = unsafe { illumos::ipadm_open(&mut handle, 0) }; - if status != illumos::ipadm_status_t_IPADM_SUCCESS { - return Err(Error::Platform(format!("ipadm_open: {}", status))) - } - - Ok(handle) - - } - - #[allow(dead_code)] - fn get_ipv6_addrs(&self) -> Result, Error> { - debug!(self.log, "getting ipv6 addrs"); - - // get address info - let handle = self.ipadm_handle()?; - let mut addrinfo: *mut illumos::ipadm_addr_info_t = ptr::null_mut(); - let status = unsafe { illumos::ipadm_addr_info( - handle, - ptr::null(), - &mut addrinfo, - 0, - LIFC_DEFAULT as i64, - ) }; - if status != illumos::ipadm_status_t_IPADM_SUCCESS { - return Err(Error::Platform(format!("ipadm_addr_info: {}", status))) - } - - // populate results from returned addresses - let mut result: Vec = Vec::new(); - let mut addr : *mut illumos::ifaddrs = unsafe { - &mut (*addrinfo).ia_ifa - }; - loop { - if addr == ptr::null_mut() { break } - - unsafe { - // only ipv6 - if (*(*addr).ifa_addr).sa_family == illumos::AF_INET6 as u16 { + } - let sin6 = (*addr).ifa_addr as *mut illumos::sockaddr_in6; + srv_result = &mut server => { - // only link local - if (*sin6).sin6_addr._S6_un._S6_u8[0] as u8 == 0xfe && - (*sin6).sin6_addr._S6_un._S6_u8[1] as u8 == 0x80 { + match srv_result { + Ok(_) => {}, + Err(e) => error!(elog, "dropshot server exit: {}", e), + }; - // extract address - let v6addr = Ipv6Addr::from( - (*sin6).sin6_addr._S6_un._S6_u8 - ); + } - // extract name - let ifname = std::ffi::CString::from_raw( - (*addr).ifa_name - ); - let ifname_s = ifname.into_string()?; + }; - debug!(self.log, - "found ipv6-ll interface: {}/{}", - ifname_s.as_str(), - v6addr); + } + }); - result.push(SysIpv6Addr{ - addr: v6addr, - local_link: ifname_s, - }) - } - } - } + Ok((etx, irx)) - addr = unsafe { (*addr).ifa_next }; - } - - Ok(result) } } diff --git a/lab/Cargo.toml b/lab/Cargo.toml index 9a89fc54..0ceaf9d6 100644 --- a/lab/Cargo.toml +++ b/lab/Cargo.toml @@ -8,5 +8,5 @@ edition = "2018" [dependencies] libfalcon = { path = "/home/ry/falcon/lib" } # https://github.com/jmesmon/rust-libzfs/pull/71 -zfs-core = { git = "https://github.com/oxidecomputer/rust-libzfs" } +#zfs-core = { git = "https://github.com/oxidecomputer/rust-libzfs" } anyhow = "1.0" diff --git a/lab/src/main.rs b/lab/src/main.rs index 6e9a42eb..ce8e3f2e 100644 --- a/lab/src/main.rs +++ b/lab/src/main.rs @@ -25,8 +25,8 @@ fn do_run() -> Result<()> { match run(&mut d) { RunMode::Launch => { - d.exec(r0, "ipadm create-addr -T addrconf duo_r0_vnic0/v6")?; - d.exec(r1, "ipadm create-addr -T addrconf duo_r1_vnic0/v6")?; + d.exec(r0, "ipadm create-addr -t -T addrconf duo_r0_vnic0/v6")?; + d.exec(r1, "ipadm create-addr -t -T addrconf duo_r1_vnic0/v6")?; Ok(()) } _ => Ok(()), diff --git a/platform/Cargo.toml b/platform/Cargo.toml index c49bd2c9..eed235ef 100644 --- a/platform/Cargo.toml +++ b/platform/Cargo.toml @@ -9,3 +9,6 @@ edition = "2018" thiserror = "1.0" icmpv6 = { path = "../icmpv6" } rift_protocol = { path = "../rift_protocol" } +serde = "1.0" +schemars = { version = "0.8.0", features = [ "uuid" ] } +tokio = { version = "1.0", features = ["full"] } diff --git a/platform/src/lib.rs b/platform/src/lib.rs index 5d024d3b..a44c1ee1 100644 --- a/platform/src/lib.rs +++ b/platform/src/lib.rs @@ -5,20 +5,48 @@ pub mod error; use std::net; use error::Error; use icmpv6::RDPMessage; -use std::sync::mpsc::{Sender, Receiver}; +//use std::sync::mpsc::{Sender, Receiver}; +use tokio::sync::mpsc::{Sender, Receiver}; use std::net::Ipv6Addr; +use serde::{Deserialize, Serialize}; +use schemars::JsonSchema; +use rift_protocol::lie::LIEPacket; pub trait Platform { - fn solicit_rift_routers(&self) -> Result<(), Error>; - fn advertise_rift_router(&self) -> Result<(), Error>; - fn get_rdp_channel(&self) -> Result, Error>; - fn get_link_channel(&self, peer: Ipv6Addr) -> Result< - (Sender, Receiver), + fn get_links(&self) -> Result, Error>; + fn get_link_status(&self, link_name: impl AsRef) -> Result; + fn get_interface_v6ll(&self, interface: impl AsRef) -> Result, Error>; + + + fn solicit_rift_routers(&self, interface: Option) -> Result<(), Error>; + fn advertise_rift_router(&self, interface: Option) -> Result<(), Error>; + fn get_rdp_channel(&self, interface: Option) -> Result, Error>; + fn get_link_channel(&self, local: Ipv6Addr, peer: Ipv6Addr) -> Result< + (Sender, Receiver), Error >; } +#[derive(Debug, Copy, Clone, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct IpIfAddr { + pub addr: Ipv6Addr, + pub if_index: i32, +} + +#[derive(Debug, Copy, Clone, Deserialize, Serialize, JsonSchema, PartialEq)] +pub enum LinkState { + Unknown, + Down, + Up, +} + #[derive(Debug)] +pub struct LinkStatus { + pub name: String, + pub state: LinkState, +} + +#[derive(Clone, Debug)] pub struct NeighborRouter { pub addr: net::Ipv6Addr, pub local_link: String, diff --git a/rift/Cargo.toml b/rift/Cargo.toml index 9280e4a3..93b07cdd 100644 --- a/rift/Cargo.toml +++ b/rift/Cargo.toml @@ -17,3 +17,5 @@ slog-async = "2.7" schemars = { version = "0.8.0", features = [ "uuid" ] } serde = "1.0" tokio = { version = "1.0", features = ["full"] } +bus = "2" +hostname = "0.3" diff --git a/rift/src/admin.rs b/rift/src/admin.rs index 4a3f1aa5..36c4577f 100644 --- a/rift/src/admin.rs +++ b/rift/src/admin.rs @@ -1,7 +1,9 @@ // Copyright 2021 Oxide Computer Company -use crate::{Rift, Peer}; -use std::sync::{Arc, Mutex}; +use crate::{Rift, link::LinkSM, link::LinkSMState}; +use tokio::sync::Mutex; +use std::sync::Arc; +use std::collections::{HashSet, HashMap}; use dropshot::{ endpoint, ConfigDropshot, @@ -13,7 +15,6 @@ use dropshot::{ HttpResponseOk, HttpError, }; -use std::collections::HashSet; use platform::Platform; use slog::error; use std::net::{SocketAddr, SocketAddrV4, Ipv4Addr}; @@ -22,11 +23,11 @@ impl Rift

{ pub(crate) fn admin_handler(&self) { - let peers = self.peers.clone(); let log = self.log.clone(); + let links = self.links.clone(); tokio::spawn(async move { - match handler(peers).await { + match handler(links).await { Ok(_) => {}, Err(e) => error!(log, "failed to start adm handler {}", e), } @@ -37,33 +38,33 @@ impl Rift

{ } struct RiftAdmContext { - peers: Arc::>>, + links: Arc::>>, } -#[endpoint { - method = GET, - path = "/peers", -}] -async fn adm_api_get_peers( +#[endpoint { method = GET, path = "/links" }] +async fn adm_api_get_links ( ctx: Arc>, -) -> Result>, HttpError> { +) -> Result>, HttpError> { let api_context = ctx.context(); - let mut vec: Vec:: = Vec::new(); - let peers = api_context.peers.lock().unwrap(); + let mut result: HashMap:: = HashMap::new(); - for x in (*peers).iter() { - vec.push(*x); + { + let links = api_context.links.lock().await; + for l in links.iter() { + let link_state = l.state.lock().await; + result.insert(l.link_name.clone(), link_state.clone()); + } } - Ok(HttpResponseOk(vec)) + Ok(HttpResponseOk(result)) } -async fn handler( - peers: Arc::>>, +async fn handler ( + links: Arc::>>, ) -> Result<(), String> { let addr = SocketAddr::V4( @@ -82,10 +83,10 @@ async fn handler( .map_err(|e| format!("config dropshot logger: {}", e))?; let mut api = ApiDescription::new(); - api.register(adm_api_get_peers).unwrap(); + api.register(adm_api_get_links).unwrap(); let api_context = RiftAdmContext{ - peers: peers.clone(), + links: links.clone(), }; let server = HttpServerStarter::new( diff --git a/rift/src/config.rs b/rift/src/config.rs new file mode 100644 index 00000000..84ad4c2b --- /dev/null +++ b/rift/src/config.rs @@ -0,0 +1,8 @@ +// Copyright 2021 Oxide Computer Company +use serde::{Deserialize, Serialize}; +use schemars::JsonSchema; + +#[derive(Debug, Clone, Copy, Deserialize, Serialize, JsonSchema)] +pub struct Config { + pub id: u64, +} diff --git a/rift/src/error.rs b/rift/src/error.rs index 845860b7..f359c5b6 100644 --- a/rift/src/error.rs +++ b/rift/src/error.rs @@ -20,3 +20,13 @@ pub enum Error { impl From for Error { fn from(e: platform::error::Error) -> Error { Error::Platform(e) } } + +#[macro_export] +macro_rules! runtime_error { + ($format:expr) => { + Err(Error::Runtime(format!($format))) + }; + ($format:expr, $($args:expr)*) => { + Err(Error::Runtime(format!($format, $($args),*))) + }; +} diff --git a/rift/src/lib.rs b/rift/src/lib.rs index 2af8c956..9970e0c1 100644 --- a/rift/src/lib.rs +++ b/rift/src/lib.rs @@ -2,29 +2,37 @@ mod error; mod admin; -mod rdp; -mod link; +pub mod config; +pub mod link; +use std::time::SystemTime; +use crate::error::Error; use std::net::Ipv6Addr; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; +use tokio::sync::Mutex; +use tokio::time::sleep; +use std::time::{Duration}; use std::collections::HashSet; use std::hash::{Hash, Hasher}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use platform::Platform; -use slog::info; -use std::sync::mpsc::{Sender, Receiver, channel}; +use slog::{trace, info}; use link::LinkSM; +use rift_protocol::lie::{LIEPacket, Neighbor}; /// The RIFT multicast address used for bootstrapping ff02::a1f7. pub const RDP_MADDR: Ipv6Addr = Ipv6Addr::new(0xff02, 0,0,0,0,0,0, 0xa1f7); pub const LINKINFO_PORT: u16 = 914; pub const TOPOLOY_INFO_PORT: u16 = 915; -#[derive(Debug, Copy, Clone, Deserialize, Serialize, JsonSchema)] +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] pub struct Peer { pub remote_addr: Ipv6Addr, pub advertisement: icmpv6::RouterAdvertisement, + pub lie: Option, + pub neighbor: Option, + pub last_seen: u128, } impl Hash for Peer { @@ -40,53 +48,77 @@ impl PartialEq for Peer { } impl Eq for Peer {} +impl Peer { + fn is_expired(&self) -> Result { + let delta = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { + Err(e) => return runtime_error!("system time: {}", e), + Ok(n) => n.as_millis() - self.last_seen, + }; + Ok(delta >= self.advertisement.reachable_time.into()) + } +} + pub struct Rift { platform: Arc::>, - peers: Arc::>>, links: Arc::>>, log: slog::Logger, + config: config::Config, } -impl Rift

{ +impl Rift

{ + pub fn new( platform: Arc::>, log: slog::Logger, + config: config::Config, ) -> Self { Rift{ platform: platform, - peers: Arc::new(Mutex::new(HashSet::new())), links: Arc::new(Mutex::new(HashSet::new())), log: log, + config: config, } } - pub fn run(&mut self) -> Result<(), error::Error> { - - let (peer_tx, peer_rx): (Sender, Receiver) = channel(); - - info!(self.log, "starting link handler"); - self.link_handler(peer_rx)?; - - info!(self.log, "starting rdp handler"); - self.rdp_handler(peer_tx)?; + pub async fn run(&mut self) -> Result<(), error::Error> { + // start admin interface info!(self.log, "starting adm handler"); self.admin_handler(); - info!(self.log, "entering router loop"); - self.router_loop() + // collect link status from the platform + let links = { + let p = self.platform .lock().await; + p.get_links()? + }; + + // start link state machines + for l in links.iter() { + let mut sm = link::LinkSM::new( + self.log.clone(), + l.name.clone(), + l.state, + self.config, + ); + sm.run(self.platform.clone()).await; + let mut lsms = self.links.lock().await; + lsms.insert(sm); + } + + self.router_loop().await?; + + Ok(()) } - fn router_loop(&self) -> Result<(), error::Error> { + async fn router_loop(&self) -> Result<(), error::Error> { loop { - let p = self.platform.lock().unwrap(); - (*p).solicit_rift_routers()?; - (*p).advertise_rift_router()?; - std::thread::sleep( - std::time::Duration::from_secs(5), - ); + //let p = self.platform.lock().unwrap(); + //(*p).solicit_rift_routers()?; + //(*p).advertise_rift_router()?; + sleep(Duration::from_secs(10)).await; + trace!(self.log, "router loop"); } #[allow(unreachable_code)] diff --git a/rift/src/link.rs b/rift/src/link.rs index 2634b6a6..9b23c9e9 100644 --- a/rift/src/link.rs +++ b/rift/src/link.rs @@ -1,101 +1,427 @@ // Copyright 2021 Oxide Computer Company -use crate::{Rift, Peer}; -use std::sync::{Arc, Mutex}; -use platform::Platform; -use std::thread; -use slog::{info, error}; -use std::sync::mpsc::{Sender, Receiver}; +use crate::{runtime_error, config::Config}; +use std::net::Ipv6Addr; +use rift_protocol::{lie::{LIEPacket, Neighbor}, Header}; +use crate::error::Error; +use crate::Peer; +use platform::{ + Platform, + IpIfAddr, + LinkState, +}; +use std::time::{Duration, SystemTime}; +use slog::{info, debug, error, trace, warn}; use std::hash::{Hash, Hasher}; +use serde::{Deserialize, Serialize}; +use schemars::JsonSchema; +use icmpv6::{RDPMessage, RouterAdvertisement, RouterSolicitation}; +use std::marker::{Send, Sync}; +use tokio::{ + spawn, select, + time::sleep, + task::JoinHandle, + sync::{Mutex, broadcast, mpsc::{Sender, Receiver}}, +}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; -pub enum AdjacencyState { +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, Deserialize, Serialize, JsonSchema, PartialEq)] +pub enum State { + WaitForCarrier, + WaitForV6ll, + Solicit, OneWay, TwoWay, ThreeWay, } pub struct LinkSM { - state: Arc::>, + pub state: Arc::>, + pub threads: Arc::>, + pub log: slog::Logger, + pub link_name: String, +} + +pub struct Threads { + carrier: Option>, + v6ll: Option>, + rdp: Option>, + rift: Option>, +} + +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +pub struct LinkSMState { + pub current: State, + pub link_state: platform::LinkState, + pub v6ll: Option::, + pub peer: Option::, + pub config: Config, +} + +#[derive(Debug, Copy, Clone)] +pub enum Event { + LinkDown, + AddressLost, + PeerExpired, +} + +macro_rules! loop_continue { + ($delay:expr) => { + tokio::time::sleep(std::time::Duration::from_secs($delay)).await; + continue + }; +} + +macro_rules! link_error { + ($log:expr, $link:expr, $error:expr, $format:expr) => { + error!($log, "[{}]: {}: {}", $link, $format, $error) + }; + ($log:expr, $link:expr, $error:expr, $format:expr, $($args:expr)*) => { + error!($log, "[{}]: {}: {}", + $link, format!($format, $($args),*), $error) + }; +} + +macro_rules! link_info { + ($log:expr, $link:expr, $format:expr) => { + info!($log, "[{}]: {}", $link, $format) + }; + ($log:expr, $link:expr, $format:expr, $($args:expr)*) => { + info!($log, "[{}]: {}", $link, format!($format, $($args),*)) + }; +} + +macro_rules! link_debug { + ($log:expr, $link:expr, $format:expr) => { + debug!($log, "[{}]: {}", $link, $format) + }; + ($log:expr, $link:expr, $format:expr, $($args:expr)*) => { + debug!($log, "[{}]: {}", $link, format!($format, $($args),*)) + }; } -struct LinkSMState { - current: AdjacencyState, - peer: Peer, +macro_rules! link_trace { + ($log:expr, $link:expr, $format:expr) => { + trace!($log, "[{}]: {}", $link, $format) + }; + ($log:expr, $link:expr, $format:expr, $($args:expr)*) => { + trace!($log, "[{}]: {}", $link, format!($format, $($args),*)) + }; } -impl Rift

{ +macro_rules! link_warn { + ($log:expr, $link:expr, $format:expr) => { + warn!($log, "[{}]: {}", $link, $format) + }; + ($log:expr, $link:expr, $format:expr, $($args:expr)*) => { + warn!($log, "[{}]: {}", $link, format!($format, $($args),*)) + }; +} + +const QUANTUM: u64 = 1; - pub(crate) fn link_handler( +// LinkSM implementation ...................................................... + +impl LinkSM { + + pub(crate) fn new( + log: slog::Logger, + name: String, + link_state: platform::LinkState, + config: Config, + ) -> Self { + LinkSM{ + log: log, + link_name: name, + threads: Arc::new(Mutex::new(Threads { + carrier: None, + v6ll: None, + rdp: None, + rift: None, + })), + state: Arc::new(Mutex::new(LinkSMState { + current: match link_state { + platform::LinkState::Down => State::WaitForCarrier, + platform::LinkState::Unknown => State::WaitForCarrier, + platform::LinkState::Up => State::WaitForV6ll, + }, + link_state: LinkState::Unknown, + peer: None, + v6ll: None, + config: config, + })), + } + } + + pub(crate) async fn run( &mut self, - peer_rx: Receiver, - ) -> Result<(), crate::error::Error> { + platform: Arc::>, + ) { + // clone stuff to move into thread let log = self.log.clone(); - let links = self.links.clone(); - let p = self.platform.clone(); + let state = self.state.clone(); + let link_name = self.link_name.clone(); + let threads = self.threads.clone(); + let p = platform.clone(); + + let mut t = self.threads.lock().await; + t.carrier = Some(spawn(async move { + Self::carrier_sm(&p, &log, &link_name, &state, &threads).await; + })); - thread::spawn(move || loop { - let peer = match peer_rx.recv() { - Ok(p) => p, + + } + + async fn carrier_sm( + platform: &Arc::>, + log: &slog::Logger, + link_name: &String, + state: &Arc::>, + threads: &Arc::>, + ) { + + let (event_tx, _) = broadcast::channel(32); + + loop { + link_trace!(log, link_name, "checking for carrier"); + + // get the current link status from the platform + let link_status = { + let p = platform.lock().await; + match p.get_link_status(link_name) { + Err(e) => { + link_error!(log, &link_name, e, "link status"); + loop_continue!(QUANTUM); + } + Ok(link_status) => link_status + } + }; + + // get the last observed link state + let link_state = { + let s = state.lock().await; + s.link_state + }; + + // if the link state has not changed, do nothing + if link_status.state == link_state { + loop_continue!(QUANTUM); + } + + // handle a link state change + match handle_link_state_change( + &platform, + link_status.state, + &state, + &threads, + &event_tx, + &log, + &link_name, + ).await { Err(e) => { - error!(log, "peer rx: {}", e); - continue; + link_error!(log, &link_name, e, "handle link state change"); + loop_continue!(QUANTUM); } + Ok(_) => {}, + } + + sleep(Duration::from_secs(QUANTUM)).await; + } + } + + async fn v6addr_sm( + platform: Arc::>, + log: slog::Logger, + link_name: String, + state: Arc::>, + threads: Arc::>, + event_tx: broadcast::Sender, + ) { + + link_trace!(log, link_name, "enter v6addr sm"); + + let mut event_rx = event_tx.subscribe(); + + let quit = Arc::new(AtomicBool::new(false)); + + link_trace!(log, link_name, "entering address event loop"); + + addr_loop( + platform.clone(), + log.clone(), + link_name.clone(), + state.clone(), + threads.clone(), + event_tx.clone(), + quit.clone(), + ).await; + + link_trace!(log, link_name, "exited address event loop"); + + loop { + + let event = match event_rx.recv().await { + Err(e) => { + link_error!(log, &link_name, e, "event recv"); + loop_continue!(QUANTUM); + } + Ok(ls) => ls }; + match event { + Event::LinkDown => { + link_warn!(log, link_name, "link lost, exiting v6addr_sm"); + quit.store(true, Ordering::Relaxed); + return + } + _ => {} + } + + } + } + + async fn solicit( + platform: Arc::>, + log: slog::Logger, + link_name: String, + state: Arc::>, + threads: Arc::>, + event_tx: broadcast::Sender, + ) { + + let mut event_rx = event_tx.subscribe(); + loop { - info!(log, - "starting link state machine for peer {:#?}", - peer.remote_addr, - ); + link_trace!(log, link_name, "solicit"); - let mut ls = links.lock().unwrap(); - let pl = p.lock().unwrap(); - let (tx, rx) = match (*pl).get_link_channel(peer.remote_addr) { + let (v6ll, rdp_rx) = match get_rdp_channel(&state, &platform).await { + Ok(r) => r, Err(e) => { - error!(log, "get link channel for {}: {}", - peer.remote_addr, e); - continue; - }, - Ok(channels) => channels, + link_error!(log, link_name, e, "get rdp channel"); + loop_continue!(QUANTUM); + } }; - let mut lsm = LinkSM::new(peer); - lsm.run(tx, rx); - (*ls).insert(lsm); - }); - Ok(()) + let quit = Arc::new(AtomicBool::new(false)); - } + advertise_solicit_tx_loop( + platform.clone(), + log.clone(), + link_name.clone(), + state.clone(), + threads.clone(), + quit.clone(), + event_tx.clone(), + v6ll, + ).await; -} + advertise_solicit_rx_loop( + platform.clone(), + log.clone(), + link_name.clone(), + state.clone(), + threads.clone(), + quit.clone(), + event_tx.clone(), + rdp_rx, + ).await; -// LinkSM implementation ...................................................... + loop { -impl LinkSM { + // listen for an event, in the case that the link + // carrier goes away, stop the v6addr_sm, we'll get + // re-launched by carrier_sm in the event that the link comes + // back up + let event = match event_rx.recv().await { + Err(e) => { + link_error!(log, &link_name, e, "solicit: event recv"); + loop_continue!(QUANTUM); + } + Ok(state) => state + }; - fn new(peer: Peer) -> Self { - LinkSM{ - state: Arc::new(Mutex::new(LinkSMState{ - current: AdjacencyState::OneWay, - peer: peer, - })) + link_trace!(log, link_name, "solicit: event received"); + match event { + Event::LinkDown => { + link_warn!(log, link_name, "link lost exiting solicit"); + quit.store(true, Ordering::Relaxed); + return + } + _ => {} + } + + } } } - fn run( - &mut self, - tx: Sender, - rx: Receiver, + async fn rift_entry( + platform: Arc::>, + log: slog::Logger, + link_name: String, + state: Arc::>, + threads: Arc::>, + event_tx: broadcast::Sender, ) { - //TODO you are here + let quit = Arc::new(AtomicBool::new(false)); - thread::spawn(move || loop { + let (local_addr, peer_addr) = { + let mut s = state.lock().await; + match (s.v6ll, s.peer.as_ref()) { + (Some(v6ll), Some(peer)) => (v6ll.addr, peer.remote_addr), + _ => { + link_warn!(log, link_name, "cannot begin one-way adjacency without local and peer address"); + let mut t = threads.lock().await; + s.current = State::Solicit; + t.rift = None; + return; + } + } + }; - let _msg = rx.recv(); + let (tx, rx) = get_link_channel(&log, &link_name, &platform, local_addr, peer_addr).await; - }); + one_way_loop( + log.clone(), + link_name.clone(), + state.clone(), + threads.clone(), + quit.clone(), + tx, + rx, + ); + let mut event_rx = event_tx.subscribe(); + + loop { + + let event = match event_rx.recv().await { + Err(e) => { + link_error!(log, &link_name, e, "event recv"); + loop_continue!(QUANTUM); + } + Ok(state) => state + }; + + link_trace!(log, link_name, "rift: event received"); + match event { + Event::LinkDown => { + link_warn!(log, link_name, "link lost exiting rift"); + quit.store(true, Ordering::Relaxed); + return + } + Event::PeerExpired => { + link_warn!(log, link_name, "peer expired exiting rift"); + quit.store(true, Ordering::Relaxed); + return + } + _ => {} + } + + + } } } @@ -104,8 +430,7 @@ impl LinkSM { impl Hash for LinkSM { fn hash(&self, state: &mut H) { - let s = self.state.lock().unwrap(); - (*s).peer.hash(state); + self.link_name.hash(state); } } @@ -113,10 +438,1001 @@ impl PartialEq for LinkSM { fn eq(&self, other: &Self) -> bool { if self == other { return true }; - let s = self.state.lock().unwrap(); - let o = other.state.lock().unwrap(); - (*s).peer == (*o).peer + self.link_name == other.link_name } } impl Eq for LinkSM {} +// Helpers .................................................................... + +async fn get_rdp_channel( + state: &Arc::>, + platform: &Arc::>, +) -> Result<(IpIfAddr, Receiver), Error> { + + let v6ll = { + let s = state.lock().await; + match s.v6ll { + None => return runtime_error!("cannot rdp without v6ll"), + Some(v6ll) => v6ll + } + }; + + let rx = { + let p = platform.lock().await; + match p.get_rdp_channel(Some(v6ll)) { + Err(e) => return runtime_error!("get rdp channel: {}", e), + Ok(rx) => rx + } + }; + + Ok((v6ll, rx)) + +} + +async fn advertise_solicit_tx_loop( + platform: Arc::>, + log: slog::Logger, + link_name: String, + state: Arc::>, + threads: Arc::>, + quit: Arc::, + event_tx: broadcast::Sender, + v6ll: IpIfAddr, +) { + + spawn(async move { loop { + + if quit.load(Ordering::Relaxed) { + let mut t = threads.lock().await; + t.rdp = None; + return; + } + + match solicit( + &platform, + &log, + &link_name, + &state, + &threads, + &event_tx, + v6ll).await { + Err(e) => link_error!(log, link_name, e, "solicit"), + Ok(sent) => { + if sent { + link_trace!(log, link_name, "solicitation sent"); + } + } + } + + match advertise(&platform, v6ll).await { + Err(e) => link_error!(log, link_name, e, "advertise"), + Ok(sent) => { + if sent { + link_trace!(log, link_name, "advertisement sent"); + } + } + } + + // 10x sampling rate + sleep(Duration::from_secs_f32(QUANTUM as f32 / 10.0f32)).await; + }}); + +} + +async fn advertise_solicit_rx_loop( + platform: Arc::>, + log: slog::Logger, + link_name: String, + state: Arc::>, + threads: Arc::>, + quit: Arc::, + event_tx: broadcast::Sender, + mut rdp_rx: Receiver, +) { + + spawn(async move { loop { + if quit.load(Ordering::Relaxed) { + return; + } + //TODO recv_timeout + let msg = match rdp_rx.recv().await { + None => { + //TODO exit and close out state for this thead + link_warn!(log, link_name, "rdp receiver closed"); + loop_continue!(QUANTUM); + } + Some(msg) => msg + }; + let from = match msg.from { + None => { + link_warn!(log, link_name, "rdp with no from addr"); + loop_continue!(QUANTUM); + } + Some(from) => from + }; + + match msg.packet { + icmpv6::ICMPv6Packet::RouterSolicitation(s) => { + handle_rdp_solicit( + &platform, + &log, + &link_name, + &state, + &threads, + &quit, + &event_tx, + &rdp_rx, + from, + s, + ).await + } + + icmpv6::ICMPv6Packet::RouterAdvertisement(a) => { + handle_rdp_advertise( + &platform, + &log, + &link_name, + &state, + &threads, + &quit, + &event_tx, + &rdp_rx, + from, + a, + ).await + } + } + + // 10x sampling rate + sleep(Duration::from_secs_f32(QUANTUM as f32 / 10.0f32)).await; + }}); + +} + +fn one_way_loop( + log: slog::Logger, + link_name: String, + state: Arc::>, + threads: Arc::>, + quit: Arc::, + tx: Sender, + mut rx: Receiver, +) { + + spawn(async move { + link_trace!(log, link_name, "enter one way loop"); + loop { + + + if quit.load(Ordering::Relaxed) { + let mut t = threads.lock().await; + t.rift = None; + break; + } + + let tx_msg = match create_lie_packet(&log, &link_name, &state).await { + Err(e) => { + link_error!(log, link_name, e, "create LIE packet"); + loop_continue!(QUANTUM); + } + Ok(msg) => msg, + }; + + select! { + + // handle transmit + tx_result = tx.send(tx_msg) => { + match tx_result { + Err(e) => link_error!(log, link_name, e, "one-way: link-info send"), + Ok(_) => link_trace!(log, link_name, "one-way: link-info sent"), + } + sleep(Duration::from_secs(QUANTUM)).await; + } + + // handle receive + rx_result = rx.recv() => { + + match rx_result { + None => { + link_warn!(log, link_name, "one-way: LIE channel closed"); + break; + }, + Some(msg) => { + link_trace!(log, link_name, "one-way: {:#?}", msg); + let mut s = state.lock().await; + match &mut s.peer { + None => { + // We should get kicked out of this loop by the quit atomic + // being set on the next iteration + link_warn!(log, link_name, "in one-way state with no peer"); + } + Some(ref mut p) => { + p.lie = Some(msg.clone()); + p.neighbor = Some(Neighbor{ + originator: msg.header.sender, + remote_id: msg.local_id, + }); + s.current = State::TwoWay; + drop(s); + two_way_loop( + &log, + &link_name, + &state, + &threads, + &quit, + &tx, + &mut rx, + ).await; + loop_continue!(QUANTUM); + } + } + } + } + + } + + }; + + } + }); + +} + +async fn two_way_loop( + log: &slog::Logger, + link_name: &String, + state: &Arc::>, + threads: &Arc::>, + quit: &Arc::, + tx: &Sender, + rx: &mut Receiver, +) { + + link_trace!(log, link_name, "enter two way loop"); + loop { + + if quit.load(Ordering::Relaxed) { + let mut t = threads.lock().await; + t.rift = None; + break; + } + + let tx_msg = match create_lie_packet(&log, &link_name, &state).await { + Err(e) => { + link_error!(log, link_name, e, "create LIE packet"); + loop_continue!(QUANTUM); + } + Ok(msg) => msg, + }; + + select! { + + // handle transmit + tx_result = tx.send(tx_msg) => { + match tx_result { + Err(e) => link_error!(log, link_name, e, "two-way: link-info send"), + Ok(_) => link_trace!(log, link_name, "two-way: link-info sent"), + } + sleep(Duration::from_secs(QUANTUM)).await; + } + + // handle receive + rx_result = rx.recv() => { + + match rx_result { + None => { + link_warn!(log, link_name, "two-way LIE channel closed"); + break; + } + Some(msg) => { + link_trace!(log, link_name, "two-way: {:#?}", msg); + let mut s = state.lock().await; + + let link_id = match s.v6ll { + None => { + drop(s); + link_warn!(log, link_name, "two-way: no v6ll address"); + loop_continue!(QUANTUM); + } + Some(v6ll) => v6ll.if_index, + }; + // check for valid reflection + if msg.neighbor.originator == s.config.id && + msg.neighbor.remote_id == link_id as u32 { + s.current = State::ThreeWay; + drop(s); + link_debug!(log, link_name, + "valid reflection, transitioning to three-way adjacency"); + three_way_loop( + log, + link_name, + state, + threads, + quit, + tx, + rx, + ).await; + loop_continue!(QUANTUM); + } else { + link_warn!(log, link_name, + "invalid reflection: {:#?} returning to one-way", msg.neighbor); + s.current = State::OneWay; + return; + } + } + } + + } + + } + + sleep(Duration::from_secs(QUANTUM)).await; + + } + +} + +async fn three_way_loop( + log: &slog::Logger, + link_name: &String, + state: &Arc::>, + threads: &Arc::>, + quit: &Arc::, + tx: &Sender, + rx: &mut Receiver, +) { + + link_trace!(log, link_name, "enter three way loop"); + loop { + + if quit.load(Ordering::Relaxed) { + let mut t = threads.lock().await; + t.rift = None; + break; + } + + //TODO mostly copy pasta from two_way + + let tx_msg = match create_lie_packet(&log, &link_name, &state).await { + Err(e) => { + link_error!(log, link_name, e, "create LIE packet"); + loop_continue!(QUANTUM); + } + Ok(msg) => msg, + }; + + select! { + + // handle transmit + tx_result = tx.send(tx_msg) => { + match tx_result { + Err(e) => link_error!(log, link_name, e, "three-way: link-info send"), + Ok(_) => link_trace!(log, link_name, "three-way: link-info sent"), + } + sleep(Duration::from_secs(QUANTUM)).await; + } + + // handle receive + rx_result = rx.recv() => { + + match rx_result { + None => { + link_warn!(log, link_name, "three-way LIE channel closed"); + break; + } + Some(msg) => { + link_trace!(log, link_name, "three-way: {:#?}", msg); + let mut s = state.lock().await; + + let link_id = match s.v6ll { + None => { + drop(s); + link_warn!(log, link_name, "three-way: no v6ll address"); + loop_continue!(QUANTUM); + } + Some(v6ll) => v6ll.if_index, + }; + // check for valid reflection + if msg.neighbor.originator == s.config.id && + msg.neighbor.remote_id == link_id as u32 { + drop(s); + link_debug!(log, link_name, + "valid reflection, remaining in three-way adjacency"); + // nothing to do, we're already here + loop_continue!(QUANTUM); + } else { + link_warn!(log, link_name, + "invalid reflection: {:#?} returning to two-way", msg.neighbor); + s.current = State::TwoWay; + return; + } + } + } + + } + + } + + sleep(Duration::from_secs(QUANTUM)).await; + + } + +} + +async fn create_lie_packet( + log: &slog::Logger, + link_name: &String, + state: &Arc::>, +) -> Result { + + let s = state.lock().await; + let (router_id, link_id) = { + let link_id = match s.v6ll { + None => { + return runtime_error!("no local address") + } + Some(v6ll) => v6ll.if_index, + }; + (s.config.id, link_id) + }; + + let nbr = { + match &s.peer { + None => Neighbor::default(), + Some(p) => { + match &p.neighbor { + None => Neighbor::default(), + Some(nbr) => *nbr, + } + } + } + }; + + Ok(LIEPacket{ + header: Header { + sender: router_id, + ..Default::default() + }, + local_id: link_id as u32, + name: { + match hostname::get() { + Ok(n) => match n.into_string() { + Ok(s) => s, + Err(_) => { + link_warn!(log, link_name, "hostname to string"); + "".to_string() + } + }, + Err(e) => { + link_error!(log, link_name, e, "get hostname"); + "".to_string() + } + } + }, + neighbor: nbr, + ..Default::default() + }) +} + +async fn get_link_channel( + log: &slog::Logger, + link_name: &String, + platform: &Arc::>, + local_addr: Ipv6Addr, + peer_addr: Ipv6Addr, +) -> (Sender, Receiver) { + + loop { + let resp = { + let p = platform.lock().await; + p.get_link_channel(local_addr, peer_addr) + }; + match resp { + Err(e) => { + link_error!(log, link_name, e, "get link channel"); + loop_continue!(QUANTUM); + } + Ok((tx, rx)) => return (tx, rx), + } + } + +} + +async fn addr_loop( + platform: Arc::>, + log: slog::Logger, + link_name: String, + state: Arc::>, + threads: Arc::>, + event_tx: broadcast::Sender, + quit: Arc::, +) { + + let _log = log.clone(); + trace!(log, "BOJANGLES"); + + spawn(async move { loop { + + link_trace!(_log, &link_name, "checking for v6ll"); + + if quit.load(Ordering::Relaxed) { + link_trace!(_log, &link_name, "quitting for v6 addr loop"); + let mut t = threads.lock().await; + t.v6ll = None; + return; + } + + let resp = { + let p = platform.lock().await; + p.get_interface_v6ll(link_name.clone()) + }; + + let v6ll = match resp { + Err(e) => { + link_error!(_log, link_name, e, "get v6ll"); + loop_continue!(QUANTUM); + }, + Ok(None) => { + link_debug!(_log, link_name, "no v6ll"); + loop_continue!(QUANTUM); + } + Ok(Some(v6ll)) => v6ll + }; + + addr_check( + &platform, + &_log, + &state, + &threads, + &link_name, + &event_tx, + v6ll).await; + + sleep(Duration::from_secs(QUANTUM)).await; + }}); + +} + +async fn addr_check( + platform: &Arc::>, + log: &slog::Logger, + state: &Arc::>, + threads: &Arc::>, + link_name: &String, + event_tx: &broadcast::Sender, + v6ll: IpIfAddr, +) { + + let mut s = state.lock().await ; + match s.v6ll { + Some(current_v6ll) => { + if v6ll != current_v6ll { + link_info!(log, link_name, "using v6ll: {:?}", v6ll); + s.v6ll = Some(v6ll); + } + } + None => { + link_info!(log, link_name, "using v6ll: {:?}", v6ll); + s.v6ll = Some(v6ll); + } + } + + let mut t = threads.lock().await; + match t.rdp { + None => { + link_debug!(log, link_name, "launching rdp thread"); + s.current = State::Solicit; + drop(s); + let platform_ = platform.clone(); + let log_ = log.clone(); + let link_ = link_name.clone(); + let state_ = state.clone(); + let threads_ = threads.clone(); + let event_tx_ = event_tx.clone(); + t.rdp = Some(spawn(async move { LinkSM::solicit( + platform_, + log_, + link_, + state_, + threads_, + event_tx_, + ).await})); + } + Some(_) => { + //TODO handle address change + } + }; +} + +async fn handle_link_state_change( + platform: &Arc::>, + link_state: LinkState, + state: &Arc::>, + threads: &Arc::>, + event_tx: &broadcast::Sender, + log: &slog::Logger, + link_name: &String, +) -> Result<(), Error> { + + match link_state { + + LinkState::Up => handle_link_up( + platform, + link_state, + state, threads, + event_tx, + log, + link_name).await, + + _ => handle_link_down( + state, + threads, + event_tx, + log, + link_name).await, + } + +} + +async fn handle_link_down( + state: &Arc::>, + threads: &Arc::>, + event_tx: &broadcast::Sender, + log: &slog::Logger, + link_name: &String, +) -> Result<(), Error> { + + // emit link down signal + + link_debug!(log, link_name, "link lost, emitting link-down event"); + match event_tx.send(Event::LinkDown) { + Err(e) => return runtime_error!("event send: {}", e), + Ok(_) => {} + } + + // wait for dependent threads to stop + + loop { + let ready = { + let t = threads.lock().await; + t.rdp.is_none() && t.v6ll.is_none() && t.rift.is_none() + }; + if !ready { + link_debug!(log, &link_name, + "waiting for address, rdp and rift threads to stop"); + loop_continue!(QUANTUM); + } + let mut s = state.lock().await; + match s.current { + // nothing to do, already here + State::WaitForCarrier => break, + _ => { + s.current = State::WaitForCarrier; + s.peer = None; + s.v6ll = None; + s.link_state = LinkState::Down; + link_debug!(log, &link_name, "address and rdp threads stopped"); + break; + } + } + } + + Ok(()) + +} + +async fn handle_peer_lost( + state: &Arc::>, + threads: &Arc::>, + event_tx: &broadcast::Sender, + log: &slog::Logger, + link_name: &String, +) { + + link_debug!(log, link_name, "peer lost, emitting peer-lost event"); + match event_tx.send(Event::PeerExpired) { + Err(e) => { + //TODO better handling + link_error!(log, link_name, e, "peer lost event send"); + } + Ok(_) => {} + } + + //TODO mostly copy-pasta from handle_link_down + loop { + let ready = { + let t = threads.lock().await; + t.rift.is_none() + }; + if !ready { + link_debug!(log, &link_name, "waiting for rift thread to stop"); + loop_continue!(QUANTUM); + } + let mut s = state.lock().await; + match s.current { + // nothing to do, already here + State::Solicit => break, + _ => { + s.current = State::Solicit; + s.peer = None; + link_debug!(log, &link_name, "rift thread stopped"); + break; + } + } + } + +} + +async fn handle_link_up( + platform: &Arc::>, + link_state: LinkState, + state: &Arc::>, + threads: &Arc::>, + event_tx: &broadcast::Sender, + log: &slog::Logger, + link_name: &String, +) -> Result<(), Error> { + + link_trace!(log, link_name, "handling link up"); + + let mut t = threads.lock().await; + match t.v6ll { + None => { + if link_state == LinkState::Up { + let mut s = state.lock().await; + s.link_state = link_state; + s.current = State::WaitForV6ll; + t.v6ll = Some(launch_v6addr_sm_thread( + platform.clone(), + log.clone(), + link_name.clone(), + state.clone(), + threads.clone(), + event_tx.clone(), + ).await) + } + + } + Some(_) => { } + } + + Ok(()) + +} + +async fn launch_v6addr_sm_thread( + platform: Arc::>, + log: slog::Logger, + link_name: String, + state: Arc::>, + threads: Arc::>, + event_tx: broadcast::Sender, +) -> JoinHandle<()> { + + let _log = log.clone(); + let _link_name = link_name.clone(); + + link_trace!(log, link_name, "launching v6addr sm thread"); + + spawn(async move { LinkSM::v6addr_sm( + platform, + _log, + _link_name, + state, + threads, + event_tx, + ).await}) + +} + +async fn handle_rdp_solicit( + platform: &Arc::>, + log: &slog::Logger, + link_name: &String, + state: &Arc::>, + _threads: &Arc::>, + _quit: &Arc::, + _event_tx: &broadcast::Sender, + _rdp_rx: &Receiver, + from: Ipv6Addr, + _s: RouterSolicitation, +) { + + link_trace!(log, link_name, + "received rdp solicitation msg from {:?}", from); + + let v6ll = { + let s = state.lock().await; + match s.v6ll { + None => { + link_warn!(log, link_name, "cannot solicit without v6ll"); + return; + } + Some(v6ll) => v6ll + } + }; + + match do_advertise(platform, v6ll).await { + Err(e) => link_error!(log, link_name, e, "advertise solicit response"), + Ok(_) => { + link_trace!(log, link_name, + "solicitation response sent to {:?}", from) + } + } +} + +async fn handle_rdp_advertise( + platform: &Arc::>, + log: &slog::Logger, + link_name: &String, + state: &Arc::>, + threads: &Arc::>, + _quit: &Arc::, + event_tx: &broadcast::Sender, + _rdp_rx: &Receiver, + from: Ipv6Addr, + a: RouterAdvertisement, +) { + + link_trace!(log, link_name, + "received rdp advertisement msg from {:?}", from); + + let rift_running = { + let t = threads.lock().await; + match t.rift { + None => false, + Some(_) => true, + } + }; + + let now = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { + Ok(n) => n.as_millis(), + Err(e) => { + link_error!(log, link_name, e, "get duration since unix epoch"); + 0 + } + }; + match rift_running { + false => { + link_info!(log, link_name, "adding peer {:?}", from); + { + let mut s = state.lock().await; + s.peer = Some(Peer{ + remote_addr: from, + advertisement: a, + last_seen: now, + lie: None, + neighbor: None, + }); + s.current = State::OneWay; + } + + //TODO pack this up into a thread launching function + let __platform = platform.clone(); + let __log = log.clone(); + let __link = link_name.clone(); + let __state = state.clone(); + let __threads = threads.clone(); + let __event_tx = event_tx.clone(); + let mut t = threads.lock().await; + t.rift = Some(spawn(async move { LinkSM::rift_entry( + __platform, + __log, + __link, + __state, + __threads, + __event_tx, + ).await})); + } + true => { + let mut s = state.lock().await; + match s.peer { + None => { + s.peer = Some(Peer{ + remote_addr: from, + advertisement: a, + last_seen: now, + lie: None, + neighbor: None, + }); + } + Some(ref mut p) => { + // TODO handle changed peer + link_trace!( + log, link_name, "peer keepalive {}", p.remote_addr); + p.last_seen = now; + } + } + } + } + +} + +async fn solicit( + platform: &Arc::>, + log: &slog::Logger, + link_name: &String, + state: &Arc::>, + threads: &Arc::>, + event_tx: &broadcast::Sender, + v6ll: IpIfAddr, +) -> Result { + + let peer = { + let s = state.lock().await; + s.peer.clone() + }; + match peer { + None => match do_solicit(platform, v6ll).await { + Err(e) => Err(e), + Ok(_) => Ok(true), + }, + Some(p) => { + let expired = match p.is_expired() { + Err(e) => return Err(e), + Ok(expired) => expired + }; + if !expired { + // nothing to do + return Ok(false) + } + { + let mut s = state.lock().await; + s.peer = None; + } + handle_peer_lost( + state, + threads, + event_tx, + log, + link_name, + ).await; + match do_solicit(platform, v6ll).await { + Err(e) => Err(e), + Ok(_) => Ok(true), + } + } + } + +} + +async fn advertise( + platform: &Arc::>, + v6ll: IpIfAddr, +) -> Result { + + //TODO advertise conditions? + + match do_advertise(platform, v6ll).await { + Err(e) => Err(e), + Ok(_) => Ok(true), + } + +} + +async fn do_solicit( + platform: &Arc::>, + v6ll: IpIfAddr, +) -> Result<(), Error> { + + let p = platform.lock().await; + match p.solicit_rift_routers(Some(v6ll)) { + Err(e) => return runtime_error!("solicit: {}", e), + Ok(()) => return Ok(()) + } + +} + +async fn do_advertise( + platform: &Arc::>, + v6ll: IpIfAddr, +) -> Result<(), Error> { + + let p = platform.lock().await; + match p.advertise_rift_router(Some(v6ll)) { + Err(e) => return runtime_error!("advertise: {}", e), + Ok(()) => return Ok(()) + } + +} diff --git a/rift/src/rdp.rs b/rift/src/rdp.rs deleted file mode 100644 index 25075057..00000000 --- a/rift/src/rdp.rs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2021 Oxide Computer Company - -use crate::{Rift, Peer}; -use platform::Platform; -use std::thread; -use icmpv6::ICMPv6Packet; -use slog::{info, error, debug}; -use std::sync::mpsc::Sender; - -impl Rift

{ - - pub(crate) fn rdp_handler( - &mut self, - peer_tx: Sender, - ) -> Result<(), crate::error::Error> { - - let p = self.platform.lock().unwrap(); - - let peers = self.peers.clone(); - let rdp_rx = (*p).get_rdp_channel()?; - let log = self.log.clone(); - - thread::spawn(move || loop { - let msg = match rdp_rx.recv() { - Ok(m) => m, - Err(e) => { - error!(log, "rdp rx: {}", e); - continue; - }, - }; - - let from = match msg.from { - Some(addr) => addr, - _ => continue, - }; - - match msg.packet { - ICMPv6Packet::RouterSolicitation(rs) => { - info!(log, "got RIFT msg {:#?} from {}", rs, from); - //TODO respond to solicitations - }, - ICMPv6Packet::RouterAdvertisement(ra) => { - info!(log, "got RIFT msg {:#?} from {}", ra, from); - let mut ps = peers.lock().unwrap(); - let peer = Peer{ - remote_addr: from, - advertisement: ra, - }; - match (*ps).replace(peer) { - None => {}, - Some(_) => { - debug!(log, "peer already known {}", from); - continue - }, - }; - match peer_tx.send(peer) { - Ok(_) => {}, - Err(e) => error!(log, "send peer to handler {}", e), - }; - } - }; - - }); - - Ok(()) - - } - -} diff --git a/rift_protocol/src/lib.rs b/rift_protocol/src/lib.rs index bb59af9f..610d8446 100644 --- a/rift_protocol/src/lib.rs +++ b/rift_protocol/src/lib.rs @@ -3,29 +3,46 @@ use serde::{Serialize, Deserialize}; use schemars::JsonSchema; -#[derive(Debug, Serialize, Deserialize, JsonSchema)] -pub struct LinkInfo { - pub name: String, - pub local_id: u64, - pub flood_port: u16, - pub mtu: u16, - pub bandwidth: u64, - pub neighbor: Neighbor, - pub node_capabilities: NodeCapabilities, - pub link_capabilities: LinkCapabilities, - pub hold_time: u16, - pub label: u32, - pub not_ztp: bool, - pub repeater: bool, - pub backoff: bool, +pub mod tie; +pub mod lie; + +type SystemId = u64; +type LinkId = u32; +type TIENumber = u32; +type SequenceNumber = u64; +type Lifetime = u32; //seconds +type Metric = i32; +type InterfaceIndex = u32; +type OuterSecurityKeyId = u8; +type Bandwidth = u32; //mbps +type PodId = u32; +type Seconds = u64; +type IPv4Address = u32; +type IPv6Address = u128; +type PrefixLength = u8; +type RouteTag = u64; +type PrefixTransactionId = u8; + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct Header { + pub major_version: MajorVersion, + pub minor_version: u16, + pub sender: SystemId, + pub level: Level, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)] -pub struct Neighbor { - pub originator: u64, - pub remote_id: u32, +impl Default for Header { + fn default() -> Header { + Header { + major_version: MajorVersion::V0, + minor_version: MINOR_VERSION, + sender: 0, + level: Level::default(), + } + } } + #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct NodeCapabilities { pub protocol_minor_version: u16, @@ -43,3 +60,47 @@ pub enum HierarchyIndication { Leaf, ToF, } + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[repr(u8)] +pub enum MajorVersion { + V0 = 0, + V1 = 1, +} + +const MINOR_VERSION: u16 = 1; + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[repr(u8)] +pub enum Level { + Leaf = 0, + L2 = 2, + L3 = 3, + L4 = 4, + L5 = 5, + L6 = 6, + L7 = 7, + L8 = 8, + L9 = 9, + L10 = 10, + L11 = 11, + L12 = 12, + L13 = 13, + L14 = 14, + L15 = 15, + L16 = 16, + L17 = 17, + L18 = 18, + L19 = 19, + L20 = 20, + L21 = 21, + L22 = 22, + L23 = 23, + TopOfFabric = 24, +} + +impl Default for Level { + fn default() -> Level { + Level::Leaf + } +} diff --git a/rift_protocol/src/lie.rs b/rift_protocol/src/lie.rs new file mode 100644 index 00000000..f6ed86b9 --- /dev/null +++ b/rift_protocol/src/lie.rs @@ -0,0 +1,72 @@ +// Copyright 2021 Oxide Computer Company + +use serde::{Serialize, Deserialize}; +use schemars::JsonSchema; +use crate::{ + Header, + NodeCapabilities, + LinkCapabilities, + HierarchyIndication, + SystemId, + LinkId, +}; + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct LIEPacket { + pub header: Header, + pub name: String, + pub local_id: u32, + pub flood_port: u16, + pub mtu: u16, + pub bandwidth: u64, + pub neighbor: Neighbor, + pub node_capabilities: NodeCapabilities, + pub link_capabilities: LinkCapabilities, + pub hold_time: u16, + pub label: u32, + pub not_ztp: bool, + pub repeater: bool, + pub backoff: bool, +} + +impl Default for LIEPacket { + fn default() -> LIEPacket { + LIEPacket{ + header: Header::default(), + name: "".to_string(), + local_id: 0, + flood_port: 0, + mtu: 0, + bandwidth: 0, + neighbor: Neighbor::default(), + node_capabilities: NodeCapabilities{ + protocol_minor_version: 0, + flood_reduction: false, + hierarchy_indication: HierarchyIndication::Leaf, + }, + link_capabilities: LinkCapabilities{ + bfd: false, + }, + hold_time: 0, + label: 0, + not_ztp: true, + repeater: false, + backoff: false, + } + } +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct Neighbor { + pub originator: SystemId, + pub remote_id: LinkId, +} + +impl Default for Neighbor { + fn default() -> Neighbor { + Neighbor{ + originator: 0, + remote_id: 0, + } + } +} diff --git a/rift_protocol/src/tie.rs b/rift_protocol/src/tie.rs new file mode 100644 index 00000000..f1b82de2 --- /dev/null +++ b/rift_protocol/src/tie.rs @@ -0,0 +1,180 @@ +// Copyright 2021 Oxide Computer Company + +use std::collections::{HashMap, HashSet}; +use std::hash::{Hash, Hasher}; +use serde::{Serialize, Deserialize}; +use schemars::JsonSchema; +use crate::{ + Header, + Level, + NodeCapabilities, + SystemId, + LinkId, + TIENumber, + SequenceNumber, + Lifetime, + Metric, + InterfaceIndex, + OuterSecurityKeyId, + Bandwidth, + PodId, + Seconds, + IPv4Address, + IPv6Address, + PrefixLength, + RouteTag, + PrefixTransactionId, +}; + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct TIEPacket { + pub header: Header, + pub tie_header: TIEHeader, + pub tie_element: TIEElement, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct TIEHeader { + pub id: TIEId, + pub seq: SequenceNumber, + pub origination_time: Option, + pub origination_lifetime: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct TIEId { + pub direction: TIEDirection, + pub originator: SystemId, + pub number: TIENumber, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[repr(u8)] +pub enum TIEDirection { + Illegal, + South, + North, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[repr(u8)] +pub enum TIEElement { + Node(NodeTIE), + Prefixes(PrefixTIE), + PositiveDisaggregationPrefixes(PrefixTIE), + NegativeDisaggregationPrefixes(PrefixTIE), + External(PrefixTIE), + PositiveExternalDisaggregationPrefixes(PrefixTIE), + KeyValues(KeyValueTIE), +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct NodeTIE { + pub level: Level, + pub neighbors: HashMap, + pub capabilities: NodeCapabilities, + pub flags: Option, + pub pod: Option, + pub startup_time: Option, + pub miscabled_links: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct PrefixTIE { + pub prefixes: HashMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct KeyValueTIE { +} + +#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)] +pub struct Timestamp { + pub sec: u64, + pub nsec: u32, +} + +#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)] +pub struct NodeFlags { + pub overload: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct NeighborTIE { + pub level: Level, + pub cost: Option, + pub link_ids: Option>, + pub bandwidth: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct LinkIdPair { + pub local_id: LinkId, + pub remote_id: LinkId, + pub local_if_index: Option, + pub local_if_name: Option, + pub outer_security_key: Option, + pub bfd_up: Option, + pub address_families: Option>, +} + +impl PartialEq for LinkIdPair { + fn eq(&self, other: &Self) -> bool { + self.local_id == other.local_id && self.remote_id == self.remote_id + } +} + +impl Eq for LinkIdPair {} + +impl Hash for LinkIdPair { + fn hash(&self, state: &mut H) { + self.local_id.hash(state); + self.remote_id.hash(state); + } +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Hash, Eq)] +#[repr(u8)] +pub enum AddressFamily { + Illegal = 0, + Min = 1, + IPv4 = 2, + IPv6 = 3, + Max = 4, +} + + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Hash, Eq)] +#[repr(u8)] +pub enum IPPrefix { + IPv4(IPv4Prefix), + IPv6(IPv6Prefix), +} + +#[derive(Debug,Clone, Serialize, Deserialize, JsonSchema, PartialEq, Hash, Eq)] +pub struct IPv4Prefix { + pub address: IPv4Address, + pub prefixlen: PrefixLength, +} + +#[derive(Debug,Clone, Serialize, Deserialize, JsonSchema, PartialEq, Hash, Eq)] +pub struct IPv6Prefix { + pub address: IPv6Address, + pub prefixlen: PrefixLength, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct PrefixAttributes { + pub metric: Metric, + pub tags: Option>, + pub monotonic_clock: Option, + pub loopback: Option, + pub directly_attached: Option, + pub from_link: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct PrefixSequence { + pub timestamp: Timestamp, + pub transaction_id: Option, +} diff --git a/riftadm/Cargo.toml b/riftadm/Cargo.toml new file mode 100644 index 00000000..05405761 --- /dev/null +++ b/riftadm/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "riftadm" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +clap = { version = "3.0.0-beta.4", features = ["color"] } +reqwest = "0.11" +rift = { path = "../rift" } +platform = { path = "../platform" } +tabwriter = { version = "1", features = ["ansi_formatting"] } +colored = "2" diff --git a/riftadm/src/main.rs b/riftadm/src/main.rs new file mode 100644 index 00000000..4eab30ad --- /dev/null +++ b/riftadm/src/main.rs @@ -0,0 +1,149 @@ +// Copyright 2021 Oxide Computer Company + +use anyhow::Result; +use std::time::SystemTime; +use clap::{AppSettings, Clap}; +use reqwest; +use std::io::{stdout, Write}; +use rift::link::LinkSMState; +use std::collections::HashMap; +use tabwriter::TabWriter; +use colored::*; + +#[derive(Clap)] +#[clap( + version = "0.1", + author = "Ryan Goodfellow " +)] +#[clap(setting = AppSettings::ColoredHelp)] +#[clap(setting = AppSettings::InferSubcommands)] +struct Opts { + #[clap(short, long, parse(from_occurrences))] + verbose: i32, + + #[clap(subcommand)] + subcmd: SubCommand, +} + +#[derive(Clap)] +enum SubCommand { + Status(Status) +} + + +#[derive(Clap)] +#[clap(setting = AppSettings::ColoredHelp)] +struct Status { +} + +fn main() { + let opts: Opts = Opts::parse(); + match opts.subcmd { + SubCommand::Status(ref s) => { + match status(&opts, &s) { + Ok(()) => {} + Err(e) => println!("{}", e), + } + } + } +} + +fn status(_opts: &Opts, _s: &Status) -> Result<()> { + + let mut tw = TabWriter::new(stdout()); + write!( + &mut tw, + "{}\t{}\t{}\t{}\t{}\t{}\t{}\n", + "Link".dimmed(), + "Rift State".dimmed(), + "Link State".dimmed(), + "Local Address".dimmed(), + "Peer Address".dimmed(), + "Last Seen".dimmed(), + "LIE".dimmed(), + )?; + write!( + &mut tw, + "{}\t{}\t{}\t{}\t{}\t{}\t{}\n", + "----".bright_black(), + "----------".bright_black(), + "----------".bright_black(), + "-------------".bright_black(), + "------------".bright_black(), + "---------".bright_black(), + "---".bright_black(), + )?; + + let response: HashMap = reqwest::blocking::get("http://localhost:7000/links")?.json()?; + + for (link_name, info) in &response { + + write!( + &mut tw, + "{}\t{}\t{}\t{}\t{}\t{}\t{}\n", + link_name, + color_rift_state(info.current), + color_link_state(info.link_state), + match info.v6ll { + None => format!("{}", "none".bright_red()), + Some(a) => format!("{}", a.addr.to_string().cyan()), + }, + match &info.peer { + None => format!("{}", "none".bright_red()), + Some(p) => format!("{}", p.remote_addr.to_string().cyan()), + }, + match &info.peer { + None => format!("{}", "~".bright_red()), + Some(p) => { + match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { + Ok(n) => { + let delta = n.as_millis() - p.last_seen; + format!("{} ms", delta) + } + Err(e) => { + format!("{}: {}", "failed to get system time".bright_red(), e) + } + } + } + }, + match &info.peer { + None => format!("{}", "~".bright_red()), + Some(p) => { + match &p.lie { + None => format!("{}", "~".bright_red()), + Some(lie) => format!("{} ({})", lie.name.bright_green(), match p.neighbor{ + None => format!("{}", "~".bright_red()), + Some(nbr) => format!("{}", nbr.originator), + }) + } + } + } + )?; + + } + tw.flush()?; + + //println!("{:#?}", response); + + Ok(()) + +} + +fn color_rift_state(state: rift::link::State) -> String { + match state { + rift::link::State::WaitForCarrier => format!("{}", "wait for carrier".to_string().bright_red()), + rift::link::State::WaitForV6ll => format!("{}", "wait for v6ll".to_string().bright_yellow()), + rift::link::State::Solicit => format!("{}", "solicit".to_string().bright_yellow()), + rift::link::State::OneWay => format!("{}", "one-way".to_string().bright_yellow()), + rift::link::State::TwoWay => format!("{}", "two-way".to_string().bright_yellow()), + rift::link::State::ThreeWay => format!("{}", "three-way".to_string().bright_green()), + } +} + +fn color_link_state(state: platform::LinkState) -> String { + match state { + platform::LinkState::Unknown => format!("{}", "unknown".to_string().bright_red()), + platform::LinkState::Down => format!("{}", "down".to_string().bright_red()), + platform::LinkState::Up => format!("{}", "up".to_string().bright_green()), + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 704bb10e..e39ff781 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,2 @@ [toolchain] -# for passing environment for libzfs through .cargo/config.toml -# worth it? channel = "nightly"