From 3c6074f735d05b861207fc344148b3030a044edd Mon Sep 17 00:00:00 2001 From: ghe0 Date: Mon, 30 Dec 2024 20:45:01 +0000 Subject: [PATCH] connected daemon to the brain (#2) Instead of grabbing commands from files, the daemon now connects to the brain and receives commands via gRPC. Reviewed-on: https://gitea.detee.cloud/SNP/daemon/pulls/2 --- Cargo.lock | 699 ++++++++++++++++++++++++++++++++++++-- Cargo.toml | 16 +- brain.proto | 145 ++++++++ build.rs | 6 + prod_setting/config1.yaml | 25 +- rustfmt.toml | 3 + scripts/start_qemu_vm.sh | 3 +- src/config.rs | 114 ++++++- src/constants.rs | 8 +- src/grpc.rs | 226 ++++++++++++ src/main.rs | 284 +++++++++++++--- src/state.rs | 355 +++++++++---------- src/tcontract.rs | 25 -- 13 files changed, 1580 insertions(+), 329 deletions(-) create mode 100644 brain.proto create mode 100644 build.rs create mode 100644 rustfmt.toml create mode 100644 src/grpc.rs delete mode 100644 src/tcontract.rs diff --git a/Cargo.lock b/Cargo.lock index 70b78cd..bd276d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,12 +17,103 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys 0.59.0", +] + [[package]] name = "anyhow" version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -35,6 +126,53 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -56,6 +194,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "2.6.0" @@ -91,9 +235,9 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cc" -version = "1.2.3" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" +checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" dependencies = [ "shlex", ] @@ -105,13 +249,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "cidr" -version = "0.3.0" +name = "colorchoice" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfc95a0c21d5409adc146dbbb152b5c65aaea32bc2d2f57cf12f850bffdd7ab8" -dependencies = [ - "serde", -] +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "core-foundation" @@ -148,17 +295,65 @@ dependencies = [ "typenum", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "detee-snp-daemon" version = "0.1.0" dependencies = [ "anyhow", - "cidr", + "ed25519-dalek", + "env_logger", + "lazy_static", + "log", + "prost", + "prost-types", "rand", + "rand_core", "reqwest", "serde", "serde_yaml", "sha2", + "tokio", + "tokio-stream", + "tonic", + "tonic-build", ] [[package]] @@ -182,6 +377,37 @@ dependencies = [ "syn", ] +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -191,6 +417,29 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -213,6 +462,18 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "fnv" version = "1.0.7" @@ -332,19 +593,31 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "http" version = "1.2.0" @@ -385,6 +658,18 @@ version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "1.5.1" @@ -398,6 +683,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -407,9 +693,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.3" +version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", "http", @@ -422,6 +708,19 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -596,6 +895,16 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.7.0" @@ -603,7 +912,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -612,6 +921,21 @@ version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.14" @@ -628,6 +952,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.167" @@ -652,6 +982,12 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.4" @@ -684,6 +1020,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + [[package]] name = "native-tls" version = "0.2.12" @@ -760,12 +1102,51 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.7.0", +] + +[[package]] +name = "pin-project" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.15" @@ -778,6 +1159,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.31" @@ -793,6 +1184,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.92" @@ -802,6 +1203,58 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f3e5beed80eb580c68e2c600937ac2c4eedabdfd5ef1e5b7ea4f3fba84497b" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2f1e56baa61e93533aebc21af4d2134b70f66275e0fcdf3cbe43d77ff7e8fc" +dependencies = [ + "prost", +] + [[package]] name = "quote" version = "1.0.37" @@ -842,10 +1295,39 @@ dependencies = [ ] [[package]] -name = "reqwest" -version = "0.12.9" +name = "regex" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe060fe50f524be480214aba758c71f99f90ee8c83c5a36b5e9e1d568eb4eb3" dependencies = [ "base64", "bytes", @@ -877,6 +1359,7 @@ dependencies = [ "system-configuration", "tokio", "tokio-native-tls", + "tower 0.5.2", "tower-service", "url", "wasm-bindgen", @@ -906,6 +1389,15 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.42" @@ -921,9 +1413,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.19" +version = "0.23.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" +checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" dependencies = [ "once_cell", "rustls-pki-types", @@ -943,9 +1435,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" [[package]] name = "rustls-webpki" @@ -958,6 +1450,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + [[package]] name = "ryu" version = "1.0.18" @@ -988,14 +1486,20 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" dependencies = [ "core-foundation-sys", "libc", ] +[[package]] +name = "semver" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" + [[package]] name = "serde" version = "1.0.215" @@ -1018,9 +1522,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "itoa", "memchr", @@ -1046,7 +1550,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap", + "indexmap 2.7.0", "itoa", "ryu", "serde", @@ -1070,6 +1574,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + [[package]] name = "slab" version = "0.4.9" @@ -1101,6 +1614,16 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -1200,9 +1723,21 @@ dependencies = [ "mio", "pin-project-lite", "socket2", + "tokio-macros", "windows-sys 0.52.0", ] +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio-native-tls" version = "0.3.1" @@ -1223,6 +1758,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.13" @@ -1236,6 +1782,91 @@ dependencies = [ "tokio", ] +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "socket2", + "tokio", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -1249,9 +1880,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.33" @@ -1314,6 +1957,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index c916286..ce8ef08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,10 +4,22 @@ version = "0.1.0" edition = "2021" [dependencies] +ed25519-dalek = { version = "2.1.1", default-features = false, features = ["std", "alloc", "rand_core", "pem"] } +rand_core = { version = "0.6.4", features = ["alloc", "getrandom", "std"] } anyhow = "1.0.94" -cidr = { version = "0.3.0", features = ["serde"] } -rand = "0.8.5" +env_logger = "0.11.6" +log = "0.4.22" reqwest = { version = "0.12.9", features = ["blocking"] } serde = { version = "1.0.215", features = ["derive"] } serde_yaml = "0.9.34" sha2 = "0.10.8" +lazy_static = "1.5.0" +prost = "0.13.4" +prost-types = "0.13.4" +rand = "0.8.5" +tokio = { version = "1.42.0", features = ["macros", "rt-multi-thread"] } +tokio-stream = "0.1.17" +tonic = "0.12" + +[build-dependencies] +tonic-build = "0.12" diff --git a/brain.proto b/brain.proto new file mode 100644 index 0000000..5fd8227 --- /dev/null +++ b/brain.proto @@ -0,0 +1,145 @@ +syntax = "proto3"; +package brain; + +message Empty { +} + +message NodePubkey { + string node_pubkey = 1; +} + +message RegisterNodeReq { + string node_pubkey = 1; + string owner_pubkey = 2; +} + +message NodeResourceReq { + string node_pubkey = 1; + uint32 avail_ports = 2; + uint32 avail_ipv4 = 3; + uint32 avail_ipv6 = 4; + uint32 avail_vcpus = 5; + uint32 avail_memory_mb = 6; + uint32 avail_storage_gb = 7; + uint32 max_ports_per_vm = 8; +} + +message NewVMReq { + string uuid = 1; // UUID is empty when CLI sends request; brain sets UUID + string hostname = 2; + string admin_pubkey = 3; + string node_pubkey = 4; + repeated uint32 extra_ports = 5; + bool public_ipv4 = 6; + bool public_ipv6 = 7; + uint32 disk_size_gb = 8; + uint32 vcpus = 9; + uint32 memory_mb = 10; + string kernel_url = 11; + string kernel_sha = 12; + string dtrfs_url = 13; + string dtrfs_sha = 14; +} + +message UpdateVMReq { + string uuid = 1; + uint32 disk_size_gb = 3; + uint32 vcpus = 4; + uint32 memory_mb = 5; + string kernel_url = 6; + string kernel_sha = 7; + string dtrfs_url = 8; + string dtrfs_sha = 9; +} + +message UpdateVMResp { + string uuid = 1; + string error = 3; +} + +message VMContract { + string uuid = 1; + string hostname = 2; + string admin_pubkey = 3; + string node_pubkey = 4; + repeated uint32 exposed_ports = 5; + string public_ipv4 = 6; + string public_ipv6 = 7; + uint32 disk_size_gb = 8; + uint32 vcpus = 9; + uint32 memory_mb = 10; + string kernel_sha = 11; + string dtrfs_sha = 12; + string created_at = 13; + string updated_at = 14; +} + +message ListVMContractsReq { + string admin_pubkey = 1; + string node_pubkey = 2; + string uuid = 3; +} + +message NewVmRespIP { + uint32 nic_index = 1; + string address = 2; + string mask = 3; + string gateway = 4; +} + +message NewVMResp { + string uuid = 1; + repeated uint32 exposed_ports = 2; + string ovmf_hash = 5; + // This is needed to allow the CLI to build the kernel params from known data. + // The CLI will use the kernel params to get the measurement. + repeated NewVmRespIP ips = 6; + string error = 7; +} + +message DeleteVMReq { + string uuid = 1; +} + +service BrainDaemonService { + rpc RegisterNode (RegisterNodeReq) returns (Empty); + rpc SendNodeResources (stream NodeResourceReq) returns (Empty); + rpc GetNewVMReqs (NodePubkey) returns (stream NewVMReq); + rpc SendNewVMResp (stream NewVMResp) returns (Empty); + rpc GetDeleteVMReq (NodePubkey) returns (stream DeleteVMReq); + rpc ListVMContracts (ListVMContractsReq) returns (stream VMContract); + rpc GetUpdateVMReq (NodePubkey) returns (stream UpdateVMReq); + rpc SendUpdateVMResp (stream UpdateVMResp) returns (Empty); +} + +message NodeFilters { + uint32 free_ports = 1; + bool offers_ipv4 = 2; + bool offers_ipv6 = 3; + uint32 vcpus = 4; + uint32 memory_mb = 5; + uint32 storage_gb = 6; + string country = 7; + string region = 8; + string city = 9; + string ip = 10; +} + +message NodeListResp { + string node_pubkey = 1; + string country = 2; + string region = 3; + string city = 4; + string ip = 5; // required for latency test + uint32 server_rating = 6; + uint32 provider_rating = 7; +} + +service BrainCliService { + rpc CreateVMContract (NewVMReq) returns (NewVMResp); + rpc ListVMContracts (ListVMContractsReq) returns (stream VMContract); + rpc ListNodes (NodeFilters) returns (stream NodeListResp); + rpc GetOneNode (NodeFilters) returns (NodeListResp); + rpc DeleteVM (DeleteVMReq) returns (Empty); + rpc UpdateVM (UpdateVMReq) returns (UpdateVMResp); +} diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..aa8f7eb --- /dev/null +++ b/build.rs @@ -0,0 +1,6 @@ +fn main() { + tonic_build::configure() + .build_server(true) + .compile_protos(&["brain.proto"], &["proto"]) + .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); +} diff --git a/prod_setting/config1.yaml b/prod_setting/config1.yaml index 435562c..338b36c 100644 --- a/prod_setting/config1.yaml +++ b/prod_setting/config1.yaml @@ -1,25 +1,24 @@ +brain_url: "http://164.92.249.180:31337" max_cores_per_vm: 4 max_vcpu_reservation: 8 max_mem_reservation_mb: 25000 network_interfaces: - driver: "MACVTAP" device: "eno8303" - ipv4: - - subnet: "173.234.136.152/29" + ipv4_ranges: + - first_ip: "173.234.136.154" + last_ip: "173.234.136.155" + netmask: "27" gateway: "173.234.136.158" - reserved_addrs: - - "173.234.136.153" - - "173.234.136.156" - - "173.234.136.157" - - "173.234.136.158" - - subnet: "173.234.137.16/31" + - first_ip: "173.234.137.17" + last_ip: "173.234.137.17" + netmask: "27" gateway: "173.234.137.30" - reserved_addrs: - - "173.234.137.16" - ipv6: - - subnet: "2a0d:3003:b666:a00c:2::/112" + ipv6_ranges: + - first_ip: "2a0d:3003:b666:a00c:0002:0000:0000:0011" + last_ip: "2a0d:3003:b666:a00c:0002:0000:0000:fffc" + netmask: "64" gateway: "2a0d:3003:b666:a00c::1" - reserved_addrs: [] volumes: - path: "/opt/detee_vms/" max_reservation_gb: 200 diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..a484977 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ +reorder_impl_items = true +use_small_heuristics = "Max" +imports_granularity = "Crate" diff --git a/scripts/start_qemu_vm.sh b/scripts/start_qemu_vm.sh index 0244ed1..e97c5a8 100755 --- a/scripts/start_qemu_vm.sh +++ b/scripts/start_qemu_vm.sh @@ -1,4 +1,5 @@ #!/bin/bash +OVMF_PATH="/var/lib/detee/boot/0346619257269b9a61ee003e197d521b8e2283483070d163a34940d6a1d40d76"; [[ -z "$VM_UUID" ]] && { echo "Environment variable VM_UUID is not set." @@ -65,7 +66,7 @@ qemu-system-x86_64 $qemu_device_params \ -machine q35,confidential-guest-support=sev0,memory-backend=ram1 \ -smp $VCPUS,maxcpus=$VCPUS \ -m $MEMORY,slots=5,maxmem=$MAX_MEMORY \ - -no-reboot -bios /usr/share/edk2/ovmf/OVMF.amdsev.fd \ + -no-reboot -bios "$OVMF_PATH" \ -drive file=${DISK},if=none,id=disk0,format=qcow2 \ -device virtio-blk-pci,drive=disk0 \ -object memory-backend-memfd,id=ram1,size=$MEMORY,share=true,prealloc=false \ diff --git a/src/config.rs b/src/config.rs index 3d6335d..925efb9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,9 +1,10 @@ #![allow(dead_code)] -use cidr::{Ipv4Cidr, Ipv6Cidr}; +use anyhow::Result; use serde::Deserialize; -use std::collections::HashSet; -use std::net::{Ipv4Addr, Ipv6Addr}; -use std::ops::Range; +use std::{ + net::{Ipv4Addr, Ipv6Addr}, + ops::Range, +}; #[derive(Deserialize, Debug, Clone)] pub struct Volume { @@ -13,24 +14,26 @@ pub struct Volume { #[derive(Deserialize, Debug)] pub struct IPv4Range { - pub subnet: Ipv4Cidr, + pub first_ip: Ipv4Addr, + pub last_ip: Ipv4Addr, + pub netmask: String, pub gateway: Ipv4Addr, - pub reserved_addrs: HashSet, } #[derive(Deserialize, Debug)] pub struct IPv6Range { - pub subnet: Ipv6Cidr, + pub first_ip: Ipv6Addr, + pub last_ip: Ipv6Addr, + pub netmask: String, pub gateway: Ipv6Addr, - pub reserved_addrs: HashSet, } #[derive(Deserialize, Debug)] pub struct Interface { pub driver: InterfaceType, pub device: String, - pub ipv4: Vec, - pub ipv6: Vec, + pub ipv4_ranges: Vec, + pub ipv6_ranges: Vec, } #[derive(Deserialize, Debug)] @@ -42,6 +45,7 @@ pub enum InterfaceType { #[derive(Deserialize, Debug)] pub struct Config { + pub brain_url: String, pub max_cores_per_vm: usize, pub max_vcpu_reservation: usize, pub max_mem_reservation_mb: usize, @@ -60,10 +64,7 @@ mod range_format { where S: Serializer, { - let range_repr = RangeRepr { - start: range.start, - end: range.end, - }; + let range_repr = RangeRepr { start: range.start, end: range.end }; range_repr.serialize(serializer) } @@ -83,10 +84,93 @@ mod range_format { } impl Config { - pub fn load_from_disk(path: &str) -> Result> { + pub fn load_from_disk(path: &str) -> Result { let content = std::fs::read_to_string(path)?; let config: Config = serde_yaml::from_str(&content)?; + for nic in &config.network_interfaces { + for range in &nic.ipv4_ranges { + let ipv4_netmask = range.netmask.parse::()?; + if ipv4_netmask > 32 { + return Err(anyhow::anyhow!( + "IPv4 netmask must be in short format: a number from 1 to 32" + )); + } + if range.first_ip.to_bits() > range.last_ip.to_bits() { + return Err(anyhow::anyhow!( + "For range {range:?} first ip is bigger than last ip." + )); + } + let expected_netmask = std::cmp::min( + calc_ipv4_netmask(range.first_ip, range.gateway), + calc_ipv4_netmask(range.last_ip, range.gateway), + ); + if expected_netmask < ipv4_netmask as u32 { + return Err(anyhow::anyhow!( + "Your netmask is too small to include the IPs and also the gateway: {range:?}" + )); + } + } + for range in &nic.ipv6_ranges { + let ipv6_netmask = range.netmask.parse::()?; + if ipv6_netmask > 128 { + return Err(anyhow::anyhow!( + "IPv6 netmask must be in short format: a number from 1 to 128" + )); + } + if range.first_ip.to_bits() > range.last_ip.to_bits() { + return Err(anyhow::anyhow!( + "For range {range:?} first ip is bigger than last ip." + )); + } + let expected_netmask = std::cmp::min( + calc_ipv6_netmask(range.first_ip, range.gateway), + calc_ipv6_netmask(range.last_ip, range.gateway), + ); + if expected_netmask < ipv6_netmask as u128 { + return Err(anyhow::anyhow!( + "Your netmask is too small to include the IPs and also the gateway: {range:?}" + )); + } + } + } Ok(config) } } +fn calc_ipv4_netmask(ip: Ipv4Addr, gateway: Ipv4Addr) -> u32 { + // Convert the IPs to u32 for easier bit manipulation + let ip_u32 = u32::from(ip); + let gateway_u32 = u32::from(gateway); + + // Find the smallest common prefix + let mut prefix_len = 0; + for i in 1..=32 { + if (ip_u32 >> (32 - i)) == (gateway_u32 >> (32 - i)) { + prefix_len = i; + } else { + break; + } + } + + // Return the mask as a string + prefix_len +} + +fn calc_ipv6_netmask(ip: Ipv6Addr, gateway: Ipv6Addr) -> u128 { + // Convert the IPs to u128 for easier bit manipulation + let ip_u128 = u128::from(ip); + let gateway_u128 = u128::from(gateway); + + // Find the smallest common prefix + let mut prefix_len = 0; + for i in 1..=128 { + if (ip_u128 >> (128 - i)) == (gateway_u128 >> (128 - i)) { + prefix_len = i; + } else { + break; + } + } + + // Return the mask as a string + prefix_len +} diff --git a/src/constants.rs b/src/constants.rs index c54fbb4..25cbb4c 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -2,9 +2,13 @@ pub(crate) const DEFAULT_OVMF: &str = "/usr/share/edk2/ovmf/OVMF.amdsev.fd"; pub(crate) const VM_BOOT_DIR: &str = "/var/lib/detee/boot/"; -pub(crate) const USED_RESOURCES: &str = "/etc/detee/daemon/used_resources.yaml"; -pub(crate) const VM_CONFIG_DIR: &str = "/etc/detee/daemon/vms/"; +pub(crate) const USED_RESOURCES: &str = "/etc/detee/daemon/used_resources.yaml"; +pub(crate) const VM_CONFIG_DIR: &str = "/etc/detee/daemon/vms/"; +pub(crate) const SECRET_KEY_PATH: &str = "/etc/detee/daemon/node_secret_key.pem"; pub(crate) const DAEMON_CONFIG_PATH: &str = "/etc/detee/daemon/config.yaml"; pub(crate) const START_VM_SCRIPT: &str = "/usr/local/bin/detee/start_qemu_vm.sh"; // TODO: research if other CPU types provide better performance pub(crate) const QEMU_VM_CPU_TYPE: &str = "EPYC-v4"; +// If you modify this, also modify scripts/start_qemu_vm.sh +pub(crate) const OVMF_HASH: &str = "0346619257269b9a61ee003e197d521b8e2283483070d163a34940d6a1d40d76"; +pub(crate) const OVMF_URL: &str = "https://drive.google.com/uc?export=download&id=1V-vLkaiLaGmFSjrN84Z6nELQOxKNAoSJ"; diff --git a/src/grpc.rs b/src/grpc.rs new file mode 100644 index 0000000..f6e448a --- /dev/null +++ b/src/grpc.rs @@ -0,0 +1,226 @@ +#![allow(dead_code)] +pub mod brain { + tonic::include_proto!("brain"); +} + +use anyhow::Result; +use brain::{ + brain_daemon_service_client::BrainDaemonServiceClient, DeleteVmReq, ListVmContractsReq, + NewVmReq, NewVmResp, NodePubkey, NodeResourceReq, RegisterNodeReq, UpdateVmReq, UpdateVmResp, + VmContract, +}; +use lazy_static::lazy_static; +use log::{debug, error, info, warn}; +use std::{fs::File, io::Write}; +use tokio::{ + sync::mpsc::{Receiver, Sender}, + task::JoinSet, +}; +use tokio_stream::{wrappers::ReceiverStream, StreamExt}; +use tonic::transport::Channel; + +lazy_static! { + static ref PUBLIC_KEY: String = get_public_key(); +} + +fn create_secret_key() -> Result { + use ed25519_dalek::pkcs8::{spki::der::pem::LineEnding, EncodePrivateKey}; + let key_path = crate::constants::SECRET_KEY_PATH; + info!("Creating new secret key at {}", key_path); + let sk = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); + let sk_pem = sk.to_pkcs8_pem(LineEnding::default()).unwrap(); + let mut file = File::create(key_path)?; + file.write_all(sk_pem.as_bytes())?; + Ok(sk) +} + +fn load_secret_key() -> Result { + use ed25519_dalek::pkcs8::DecodePrivateKey; + let secret_key_pem = match std::fs::read_to_string(crate::constants::SECRET_KEY_PATH) { + Ok(secret_key_pem) => secret_key_pem, + Err(e) => { + warn!("Could not load secret key due to error: {e:?}"); + return Ok(create_secret_key()?); + } + }; + Ok(ed25519_dalek::SigningKey::from_pkcs8_pem(&secret_key_pem)?) +} + +pub fn get_public_key() -> String { + use ed25519_dalek::pkcs8::{spki::der::pem::LineEnding, EncodePublicKey}; + let pubkey = load_secret_key() + .unwrap() + .verifying_key() + .to_public_key_pem(LineEnding::default()) + .unwrap() + .lines() + .nth(1) + .unwrap() + .to_string(); + log::info!("Loaded the following public key: {pubkey}"); + pubkey +} + +pub async fn list_contracts(brain_url: String) -> Result> { + let mut client = BrainDaemonServiceClient::connect(brain_url).await?; + let mut contracts = Vec::new(); + let mut grpc_stream = client + .list_vm_contracts(ListVmContractsReq { + node_pubkey: PUBLIC_KEY.to_string(), + ..Default::default() + }) + .await? + .into_inner(); + while let Some(stream_update) = grpc_stream.next().await { + match stream_update { + Ok(node) => { + debug!("Received contract from brain: {node:?}"); + contracts.push(node); + } + Err(e) => { + warn!("Received error instead of contracts: {e:?}"); + } + } + } + info!("Brain terminated list_contracts stream."); + Ok(contracts) +} + +async fn listen_for_new_vm_reqs( + mut client: BrainDaemonServiceClient, + tx: Sender, +) -> Result<()> { + debug!("starting listen_for_new_vm_reqs"); + let node_pubkey = PUBLIC_KEY.clone(); + let mut grpc_stream = client.get_new_vm_reqs(NodePubkey { node_pubkey }).await?.into_inner(); + while let Some(stream_update) = grpc_stream.next().await { + match stream_update { + Ok(req) => { + info!("Received new vm request: {req:?}"); + let _ = tx.send(req).await; + } + Err(e) => { + warn!("Brain disconnected from listen_for_new_vm_reqs: {e}"); + } + } + } + debug!("listen_for_new_vm_reqs is about to exit"); + Ok(()) +} + +async fn send_newvm_resp( + mut client: BrainDaemonServiceClient, + rx: Receiver, +) -> Result<()> { + debug!("starting send_newvm_resp stream"); + let rx_stream = ReceiverStream::new(rx); + client.send_new_vm_resp(rx_stream).await?; + debug!("send_newvm_resp is about to exit"); + Ok(()) +} + +async fn send_node_resources( + mut client: BrainDaemonServiceClient, + rx: Receiver, +) -> Result<()> { + debug!("starting send_newvm_resp stream"); + let rx_stream = ReceiverStream::new(rx).map(|mut node_resources| { + node_resources.node_pubkey = get_public_key(); + node_resources + }); + client.send_node_resources(rx_stream).await?; + debug!("send_newvm_resp is about to exit"); + Ok(()) +} + +async fn register_node(mut client: BrainDaemonServiceClient) { + debug!("Starting node registration..."); + let req = RegisterNodeReq { + node_pubkey: PUBLIC_KEY.clone(), + owner_pubkey: "IamTheOwnerOf".to_string() + &PUBLIC_KEY, + }; + match client.register_node(req).await { + Ok(_) => { + info!("Registered as 10.0.10.1 from Bruma/Cyrodiil with ID {}", PUBLIC_KEY.clone()) + } + Err(e) => error!("Could not register node data: {e:?}"), + }; +} + +async fn listen_for_deleted_vms( + mut client: BrainDaemonServiceClient, + tx: Sender, +) -> Result<()> { + debug!("starting listen_for_new_vm_reqs"); + let node_pubkey = PUBLIC_KEY.clone(); + let mut grpc_stream = client.get_delete_vm_req(NodePubkey { node_pubkey }).await?.into_inner(); + while let Some(stream_update) = grpc_stream.next().await { + match stream_update { + Ok(req) => { + info!("Received delete vm request: {req:?}"); + let _ = tx.send(req).await; + } + Err(e) => { + warn!("Brain disconnected from listen_for_deleted_vms: {e}"); + } + } + } + debug!("listen_for_new_vm_reqs is about to exit"); + Ok(()) +} + +async fn listen_for_update_vm_reqs( + mut client: BrainDaemonServiceClient, + tx: Sender, +) -> Result<()> { + debug!("starting listen_for_update_vm_reqs"); + let node_pubkey = PUBLIC_KEY.clone(); + let mut grpc_stream = client.get_update_vm_req(NodePubkey { node_pubkey }).await?.into_inner(); + while let Some(stream_update) = grpc_stream.next().await { + match stream_update { + Ok(req) => { + info!("Received update vm request: {req:?}"); + let _ = tx.send(req).await; + } + Err(e) => { + warn!("Brain disconnected from listen_for_update_vm_reqs: {e}"); + } + } + } + debug!("listen_for_update_vm_reqs is about to exit"); + Ok(()) +} + +async fn send_updatevm_resp( + mut client: BrainDaemonServiceClient, + rx: Receiver, +) -> Result<()> { + debug!("starting send_updatevm_resp stream"); + let rx_stream = ReceiverStream::new(rx); + client.send_update_vm_resp(rx_stream).await?; + debug!("send_updatevm_resp is about to exit"); + Ok(()) +} + +pub struct ConnectionData { + pub brain_url: String, + pub newvm_tx: Sender, + pub confirm_vm_rx: Receiver, + pub delete_vm_tx: Sender, + pub resources_rx: Receiver, +} + +pub async fn connect_and_run(cd: ConnectionData) -> Result<()> { + let client = BrainDaemonServiceClient::connect(cd.brain_url).await?; + let mut streaming_tasks = JoinSet::new(); + + register_node(client.clone()).await; + streaming_tasks.spawn(listen_for_new_vm_reqs(client.clone(), cd.newvm_tx)); + streaming_tasks.spawn(send_newvm_resp(client.clone(), cd.confirm_vm_rx)); + streaming_tasks.spawn(listen_for_deleted_vms(client.clone(), cd.delete_vm_tx)); + streaming_tasks.spawn(send_node_resources(client.clone(), cd.resources_rx)); + + let task_output = streaming_tasks.join_next().await; + warn!("One stream exited: {task_output:?}"); + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 1a9870e..7c0298c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,62 +1,234 @@ +#[allow(dead_code)] mod config; mod constants; +mod grpc; mod state; -mod tcontract; -use crate::config::Config; -use crate::state::NewVMRequest; -use crate::state::UpdateVMReq; -use std::fs::read_dir; +use crate::{config::Config, grpc::brain}; +use anyhow::Result; +use log::{debug, info, warn}; +use tokio::{ + sync::mpsc::{Receiver, Sender}, + time::{sleep, Duration}, +}; -fn main() -> Result<(), Box> { - let config = Config::load_from_disk(crate::constants::DAEMON_CONFIG_PATH)?; - let mut res = match state::Resources::load_from_disk() { - Ok(res) => res, - Err(e) => { - println!("Could not load resources from disk: {e:?}"); - println!("Creating new resource calculator."); - state::Resources::new(&config.volumes) - } - }; - - for entry in read_dir("/etc/detee/daemon/newvmreq/")? { - let entry = entry?; - let path = entry.path(); - if path.is_file() { - let new_vm_req = NewVMRequest::from_file(path.to_str().unwrap())?; - let vm = state::VM::new(new_vm_req, &config, &mut res).unwrap(); - vm.start()?; - println!("started vm {}", vm.uuid); - } - } - - for entry in read_dir("/etc/detee/daemon/deletevm/")? { - let entry = entry?; - let path = entry.path(); - if path.is_file() { - let vm_id = path.file_name().unwrap().to_str().unwrap(); - let content = std::fs::read_to_string( - crate::constants::VM_CONFIG_DIR.to_string() + vm_id + ".yaml", - )?; - let vm: crate::state::VM = serde_yaml::from_str(&content)?; - vm.delete(&mut res)?; - println!("deleted vm {}", vm.uuid); - } - } - - for entry in read_dir("/etc/detee/daemon/updatedvmreq/")? { - let entry = entry?; - let path = entry.path(); - if path.is_file() { - let update_vm_req = UpdateVMReq::from_file(path.to_str().unwrap())?; - let content = std::fs::read_to_string( - crate::constants::VM_CONFIG_DIR.to_string() + &update_vm_req.uuid + ".yaml", - )?; - let mut vm: crate::state::VM = serde_yaml::from_str(&content)?; - vm.update(update_vm_req, &config, &mut res).unwrap(); - println!("updated vm {}", vm.uuid); - } - } - - Ok(()) +#[allow(dead_code)] +struct VMHandler { + new_vm_req_chan: Receiver, + new_vm_resp_chan: Sender, + delete_vm_chan: Receiver, + resources_chan: Sender, + config: Config, + res: state::Resources, +} + +#[allow(dead_code)] +impl VMHandler { + fn new( + new_vm_req_chan: Receiver, + new_vm_resp_chan: Sender, + delete_vm_chan: Receiver, + resources_chan: Sender, + ) -> Self { + let config = match Config::load_from_disk(crate::constants::DAEMON_CONFIG_PATH) { + Ok(config) => config, + Err(e) => panic!("Could not load config: {e:?}"), + }; + let res = match state::Resources::load_from_disk() { + Ok(res) => res, + Err(e) => { + warn!("Could not load resources from disk: {e:?}"); + info!("Creating new resource calculator."); + state::Resources::new(&config.volumes) + } + }; + Self { new_vm_req_chan, new_vm_resp_chan, delete_vm_chan, resources_chan, config, res } + } + + fn get_available_ips(&self) -> (u32, u32) { + let mut avail_ipv4 = 0; + let mut avail_ipv6 = 0; + for nic in self.config.network_interfaces.iter() { + for range in nic.ipv4_ranges.iter() { + avail_ipv4 += (range.last_ip.to_bits() + 1) - range.first_ip.to_bits(); + } + for range in nic.ipv6_ranges.iter() { + avail_ipv6 += (range.last_ip.to_bits() + 1) - range.first_ip.to_bits(); + } + } + ( + avail_ipv4.saturating_sub(self.res.reserved_ipv4.len() as u32), + avail_ipv6.saturating_sub(self.res.reserved_ipv6.len() as u128) as u32, + ) + } + + async fn send_node_resources(&mut self) { + let (avail_ipv4, avail_ipv6) = self.get_available_ips(); + let mut avail_storage_gb = 0; + for volume in self.config.volumes.iter() { + avail_storage_gb += volume.max_reservation_gb; + if let Some(reservation) = self.res.reserved_storage.get(&volume.path) { + avail_storage_gb -= reservation; + } + } + let avail_storage_gb = avail_storage_gb as u32; + let res = brain::NodeResourceReq { + node_pubkey: String::new(), + avail_ports: (self.config.public_port_range.len() - self.res.reserved_ports.len()) + as u32, + avail_ipv4, + avail_ipv6, + avail_vcpus: (self.config.max_vcpu_reservation - self.res.reserved_vcpus) as u32, + avail_memory_mb: (self.config.max_mem_reservation_mb - self.res.reserved_memory) as u32, + avail_storage_gb, + max_ports_per_vm: self.config.max_ports_per_vm as u32, + }; + debug!("sending node resources on brain: {res:?}"); + let _ = self.resources_chan.send(res).await; + } + + async fn handle_new_vm_req(&mut self, new_vm_req: brain::NewVmReq) { + debug!("Processing new vm request: {new_vm_req:?}"); + let uuid = new_vm_req.uuid.clone(); + match state::VM::new(new_vm_req.into(), &self.config, &mut self.res) { + Ok(vm) => match vm.start() { + Ok(_) => { + info!("Succesfully started VM {uuid}"); + let _ = self.new_vm_resp_chan.send(vm.into()).await; + self.send_node_resources().await; + } + Err(e) => { + log::error!("Could not start VM {uuid}: {e:?}"); + let _ = self + .new_vm_resp_chan + .send(brain::NewVmResp { + uuid, + error: "This node has an internal error. Choose another node." + .to_string(), + ..Default::default() + }) + .await; + } + }, + Err(e) => match e { + crate::state::VMCreationErrors::VMAlreadyExists(vm) => { + log::info!( + "Got NewVmReq for VM {}, that already exist. Will send NewVmResp.", + vm.uuid + ); + let _ = self.new_vm_resp_chan.send(vm.into()).await; + } + _ => { + warn!("Refusing to service vm {uuid} due to error: {e:?}"); + let _ = self + .new_vm_resp_chan + .send(brain::NewVmResp { + uuid, + error: format!("{e:?}"), + ..Default::default() + }) + .await; + } + }, + } + } + + fn handle_delete_vm(&mut self, delete_vm_req: brain::DeleteVmReq) -> Result<()> { + let vm_id = delete_vm_req.uuid; + let content = + std::fs::read_to_string(constants::VM_CONFIG_DIR.to_string() + &vm_id + ".yaml")?; + let vm: state::VM = serde_yaml::from_str(&content)?; + vm.delete(&mut self.res)?; + Ok(()) + } + + async fn run(mut self) { + self.send_node_resources().await; + loop { + tokio::select! { + Some(new_vm_req) = self.new_vm_req_chan.recv() => { + self.handle_new_vm_req(new_vm_req).await; + } + Some(delete_vm_req) = self.delete_vm_chan.recv() => { + let uuid = delete_vm_req.uuid.clone(); + if let Err(e) = self.handle_delete_vm(delete_vm_req) { + log::error!("Could not delete vm {uuid}: {e:?}"); + } else { + self.send_node_resources().await; + } + } + else => { + log::error!("All data channels closed."); + return; + } + } + } + } + + fn clear_deleted_contracts(&mut self, contracts: Vec) { + for uuid in self.res.existing_vms.clone() { + if contracts.iter().find(|c| c.uuid == uuid).is_none() { + info!("VM {uuid} exists locally but not found in brain. Deleting..."); + let content = match std::fs::read_to_string( + crate::constants::VM_CONFIG_DIR.to_string() + &uuid + ".yaml", + ) { + Ok(content) => content, + Err(e) => { + log::error!("Could not find VM config for {uuid}. Cannot delete VM: {e:?}"); + continue; + } + }; + let vm: crate::state::VM = match serde_yaml::from_str(&content) { + Ok(vm) => vm, + Err(e) => { + log::error!("VM config corrupted for {uuid}. Cannot delete VM: {e:?}"); + continue; + } + }; + match vm.delete(&mut self.res) { + Ok(()) => info!("Successfully deleted VM {uuid}"), + Err(e) => log::error!("Deletion failed for VM {uuid}: {e:?}"), + } + } + } + } +} + +#[tokio::main] +async fn main() { + env_logger::builder().filter_level(log::LevelFilter::Debug).init(); + + loop { + let (newvm_tx, newvm_rx) = tokio::sync::mpsc::channel(6); + let (confirm_vm_tx, confirm_vm_rx) = tokio::sync::mpsc::channel(6); + let (delete_vm_tx, delete_vm_rx) = tokio::sync::mpsc::channel(6); + let (resources_tx, resources_rx) = tokio::sync::mpsc::channel(6); + + let mut vm_handler = VMHandler::new(newvm_rx, confirm_vm_tx, delete_vm_rx, resources_tx); + let brain_url = vm_handler.config.brain_url.clone(); + + info!("Trying to get VM Contracts from Brain to see if some Contracts got removed..."); + match grpc::list_contracts(brain_url.clone()).await { + Ok(contracts) => vm_handler.clear_deleted_contracts(contracts), + Err(e) => log::error!("Could not get contracts from brain: {e:?}"), + }; + + tokio::spawn(async move { + vm_handler.run().await; + }); + + info!("Connecting to brain..."); + if let Err(e) = grpc::connect_and_run(grpc::ConnectionData { + brain_url, + newvm_tx, + confirm_vm_rx, + delete_vm_tx, + resources_rx, + }) + .await + { + log::error!("The connection broke: {e}"); + } + sleep(Duration::from_secs(3)).await; + } } diff --git a/src/state.rs b/src/state.rs index 86ca809..2198015 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,32 +1,28 @@ #![allow(dead_code)] -use crate::config::Config; -use crate::constants::*; -use anyhow::anyhow; -use anyhow::Result; -use serde::Deserialize; -use serde::Serialize; +use crate::{config::Config, constants::*, grpc::brain}; +use anyhow::{anyhow, Result}; +use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use std::collections::HashMap; -use std::collections::HashSet; -use std::fs; -use std::fs::remove_file; -use std::fs::File; -use std::io::Read; -use std::io::Write; -use std::net::{Ipv4Addr, Ipv6Addr}; -use std::path::Path; -use std::process::Command; +use std::{ + collections::{HashMap, HashSet}, + fs, + fs::{remove_file, File}, + io::{Read, Write}, + path::Path, + process::Command, +}; #[derive(Serialize, Deserialize, Debug)] pub struct Resources { - existing_vms: HashSet, + pub existing_vms: HashSet, // QEMU does not support MHz limiation - reserved_vcpus: usize, - reserved_memory: usize, - reserved_ports: HashSet, - reserved_storage: HashMap, - reserved_ips: HashSet, - reserved_if_names: HashSet, + pub reserved_vcpus: usize, + pub reserved_memory: usize, + pub reserved_ports: HashSet, + pub reserved_storage: HashMap, + pub reserved_ipv4: HashSet, + pub reserved_ipv6: HashSet, + pub reserved_if_names: HashSet, // sha256sum -> absolute path boot_files: HashSet, } @@ -60,7 +56,8 @@ impl Resources { reserved_memory: 0, reserved_ports: HashSet::new(), reserved_storage: HashMap::new(), - reserved_ips: HashSet::new(), + reserved_ipv4: HashSet::new(), + reserved_ipv6: HashSet::new(), reserved_if_names: HashSet::new(), boot_files: HashSet::new(), }; @@ -111,11 +108,8 @@ impl Resources { fn available_if_name(&mut self) -> String { use rand::{distributions::Alphanumeric, Rng}; loop { - let mut interface_name: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(9) - .map(char::from) - .collect(); + let mut interface_name: String = + rand::thread_rng().sample_iter(&Alphanumeric).take(9).map(char::from).collect(); interface_name = "detee".to_string() + &interface_name; if !self.reserved_if_names.contains(&interface_name) { return interface_name; @@ -123,13 +117,15 @@ impl Resources { } } - fn available_ipv4(&mut self, config: &Config) -> Option { - for nic in config.network_interfaces.iter() { - for range in nic.ipv4.iter() { - for ip in range.subnet.iter().skip(1) { - if !range.reserved_addrs.contains(&ip.address()) - && !self.reserved_ips.contains(&ip.address().to_string()) - && ip.address() != range.gateway + fn available_ipv4(&mut self, nics: &Vec) -> Option { + for nic in nics.iter() { + for range in nic.ipv4_ranges.iter() { + let first_ip = range.first_ip.to_bits(); + let last_ip = range.last_ip.to_bits(); + for ip in first_ip..last_ip + 1 { + let ip_addr = std::net::Ipv4Addr::from_bits(ip); + if !self.reserved_ipv4.contains(&ip_addr.to_string()) + && ip_addr != range.gateway { let if_config = match nic.driver { crate::config::InterfaceType::MACVTAP => InterfaceConfig::MACVTAP { @@ -140,14 +136,14 @@ impl Resources { name: self.available_if_name(), device: nic.device.clone(), }, - crate::config::InterfaceType::Bridge => InterfaceConfig::Bridge { - device: nic.device.clone(), - }, + crate::config::InterfaceType::Bridge => { + InterfaceConfig::Bridge { device: nic.device.clone() } + } }; let mut ips = Vec::new(); ips.push(IPConfig { - address: ip.address().to_string(), - mask: calc_ipv4_netmask(ip.address(), range.gateway), + address: ip_addr.to_string(), + mask: range.netmask.clone(), gateway: range.gateway.to_string(), }); return Some(VMNIC { if_config, ips }); @@ -158,13 +154,15 @@ impl Resources { None } - fn available_ipv6(&mut self, config: &Config) -> Option { - for nic in config.network_interfaces.iter() { - for range in nic.ipv6.iter() { - for ip in range.subnet.iter().skip(1) { - if !range.reserved_addrs.contains(&ip.address()) - && !self.reserved_ips.contains(&ip.address().to_string()) - && ip.address() != range.gateway + fn available_ipv6(&mut self, nics: &Vec) -> Option { + for nic in nics.iter() { + for range in nic.ipv6_ranges.iter() { + let first_ip = range.first_ip.to_bits(); + let last_ip = range.last_ip.to_bits(); + for ip in first_ip..last_ip + 1 { + let ip_addr = std::net::Ipv6Addr::from_bits(ip); + if !self.reserved_ipv6.contains(&ip_addr.to_string()) + && ip_addr != range.gateway { let if_config = match nic.driver { crate::config::InterfaceType::MACVTAP => InterfaceConfig::MACVTAP { @@ -175,14 +173,14 @@ impl Resources { name: self.available_if_name(), device: nic.device.clone(), }, - crate::config::InterfaceType::Bridge => InterfaceConfig::Bridge { - device: nic.device.clone(), - }, + crate::config::InterfaceType::Bridge => { + InterfaceConfig::Bridge { device: nic.device.clone() } + } }; let mut ips = Vec::new(); ips.push(IPConfig { - address: ip.address().to_string(), - mask: calc_ipv6_netmask(ip.address(), range.gateway), + address: ip_addr.to_string(), + mask: range.netmask.clone(), gateway: range.gateway.to_string(), }); return Some(VMNIC { if_config, ips }); @@ -213,7 +211,7 @@ impl Resources { Ok(()) } - fn download_boot_file(&mut self, url: String, sha: String) -> Result<()> { + fn find_or_download_file(&mut self, url: String, sha: String) -> Result<()> { if !self.boot_files.contains(&sha) { download_and_check_sha(&url, &sha)?; } @@ -230,7 +228,14 @@ impl Resources { self.reserved_if_names.insert(vtap); } for ip in nic.ips.iter() { - self.reserved_ips.insert(ip.address.clone()); + if let Ok(ip_address) = ip.address.parse::() { + if ip_address.is_ipv4() { + self.reserved_ipv4.insert(ip.address.clone()); + } + if ip_address.is_ipv6() { + self.reserved_ipv6.insert(ip.address.clone()); + } + } } } for (host_port, _) in vm.fw_ports.iter() { @@ -253,15 +258,20 @@ impl Resources { self.reserved_if_names.remove(&vtap); } for ip in nic.ips.iter() { - self.reserved_ips.remove(&ip.address.clone()); + if let Ok(ip_address) = ip.address.parse::() { + if ip_address.is_ipv4() { + self.reserved_ipv4.remove(&ip.address); + } + if ip_address.is_ipv6() { + self.reserved_ipv6.remove(&ip.address); + } + } } } for (host_port, _) in vm.fw_ports.iter() { self.reserved_ports.remove(host_port); } - self.reserved_storage - .entry(vm.storage_dir.clone()) - .and_modify(|gb| *gb -= vm.disk_size_gb); + self.reserved_storage.entry(vm.storage_dir.clone()).and_modify(|gb| *gb -= vm.disk_size_gb); let _ = self.save_to_disk(); } } @@ -333,12 +343,6 @@ pub struct VMNIC { ips: Vec, } -impl VMNIC { - fn new() -> VMNIC { - todo!("implement this here to improve code elegance of resource reservation"); - } -} - #[derive(Serialize, Deserialize, Debug)] pub struct VM { pub uuid: String, @@ -356,6 +360,35 @@ pub struct VM { storage_dir: String, } +impl Into for VM { + fn into(self) -> brain::NewVmResp { + let mut nic_index: u32 = 0; + let mut ips: Vec = Vec::new(); + if self.fw_ports.len() > 0 { + nic_index += 1; + } + // TODO: when brain supports multiple IPs per VM, fix this + for nic in self.nics { + for ip in nic.ips { + ips.push(brain::NewVmRespIp { + nic_index, + address: ip.address, + mask: ip.mask, + gateway: ip.gateway, + }); + } + nic_index += 1; + } + brain::NewVmResp { + uuid: self.uuid, + exposed_ports: self.fw_ports.iter().map(|(p, _)| *p as u32).collect(), + ips, + ovmf_hash: crate::constants::OVMF_HASH.to_string(), + error: "".to_string(), + } + } +} + #[derive(Deserialize, Debug)] pub struct NewVMRequest { uuid: String, @@ -373,11 +406,23 @@ pub struct NewVMRequest { dtrfs_sha: String, } -impl NewVMRequest { - pub fn from_file(path: &str) -> Result> { - let content = std::fs::read_to_string(path)?; - let request: NewVMRequest = serde_yaml::from_str(&content)?; - Ok(request) +impl From for NewVMRequest { + fn from(req: brain::NewVmReq) -> Self { + Self { + uuid: req.uuid, + hostname: req.hostname, + admin_key: req.admin_pubkey, + extra_ports: req.extra_ports.iter().map(|&port| port as u16).collect(), + public_ipv4: req.public_ipv4, + public_ipv6: req.public_ipv6, + disk_size_gb: req.disk_size_gb as usize, + vcpus: req.vcpus as usize, + memory_mb: req.memory_mb as usize, + kernel_url: req.kernel_url, + kernel_sha: req.kernel_sha, + dtrfs_url: req.dtrfs_url, + dtrfs_sha: req.dtrfs_sha, + } } } @@ -394,17 +439,9 @@ pub struct UpdateVMReq { dtrfs_sha: String, } -impl UpdateVMReq { - pub fn from_file(path: &str) -> Result> { - let content = std::fs::read_to_string(path)?; - let request: UpdateVMReq = serde_yaml::from_str(&content)?; - Ok(request) - } -} - #[derive(Debug)] pub enum VMCreationErrors { - VMAlreadyExists, + VMAlreadyExists(VM), NATandIPv4Conflict, TooManyCores, NotEnoughPorts, @@ -426,7 +463,11 @@ impl VM { res: &mut Resources, ) -> Result { if res.existing_vms.contains(&req.uuid) { - return Err(VMCreationErrors::VMAlreadyExists); + let content = std::fs::read_to_string(VM_CONFIG_DIR.to_string() + &req.uuid + ".yaml") + .map_err(|e| VMCreationErrors::ServerDiskError(e.to_string()))?; + let vm: crate::state::VM = serde_yaml::from_str(&content) + .map_err(|e| VMCreationErrors::ServerDiskError(e.to_string()))?; + return Err(VMCreationErrors::VMAlreadyExists(vm)); } if req.extra_ports.len() > 0 && req.public_ipv4 { return Err(VMCreationErrors::NATandIPv4Conflict); @@ -444,12 +485,20 @@ impl VM { return Err(VMCreationErrors::DiskTooSmall); } - if let Err(kernel_err) = res.download_boot_file(req.kernel_url, req.kernel_sha.clone()) { + if let Err(ovmf_err) = res.find_or_download_file( + crate::constants::OVMF_URL.to_string(), + crate::constants::OVMF_HASH.to_string(), + ) { + return Err(VMCreationErrors::BootFileError(format!( + "Could not get OVMF: {ovmf_err:?}" + ))); + }; + if let Err(kernel_err) = res.find_or_download_file(req.kernel_url, req.kernel_sha.clone()) { return Err(VMCreationErrors::BootFileError(format!( "Could not get kernel: {kernel_err:?}" ))); }; - if let Err(dtrfs_err) = res.download_boot_file(req.dtrfs_url, req.dtrfs_sha.clone()) { + if let Err(dtrfs_err) = res.find_or_download_file(req.dtrfs_url, req.dtrfs_sha.clone()) { return Err(VMCreationErrors::BootFileError(format!( "Could not get dtrfs: {dtrfs_err:?}" ))); @@ -457,13 +506,13 @@ impl VM { let mut vm_nics = Vec::new(); if req.public_ipv4 { - match res.available_ipv4(config) { + match res.available_ipv4(&config.network_interfaces) { Some(vmnic) => vm_nics.push(vmnic), None => return Err(VMCreationErrors::IPv4NotAvailable), } } if req.public_ipv6 { - match res.available_ipv6(config) { + match res.available_ipv6(&config.network_interfaces) { Some(mut vmnic) => { if let Some(mut existing_vmnic) = vm_nics.pop() { if vmnic.if_config.device_name() == existing_vmnic.if_config.device_name() { @@ -478,7 +527,7 @@ impl VM { } } None => { - return Err(VMCreationErrors::IPv4NotAvailable); + return Err(VMCreationErrors::IPv6NotAvailable); } } } @@ -532,18 +581,12 @@ impl VM { return Err(VMCreationErrors::TooManyCores); } if config.max_vcpu_reservation - < res - .reserved_vcpus - .saturating_sub(self.vcpus) - .saturating_add(req.vcpus) + < res.reserved_vcpus.saturating_sub(self.vcpus).saturating_add(req.vcpus) { return Err(VMCreationErrors::NotEnoughCPU); } if config.max_mem_reservation_mb - < res - .reserved_memory - .saturating_sub(self.memory_mb) - .saturating_add(req.memory_mb) + < res.reserved_memory.saturating_sub(self.memory_mb).saturating_add(req.memory_mb) { return Err(VMCreationErrors::NotEnoughMemory); } @@ -558,12 +601,14 @@ impl VM { )); } - if let Err(kern_err) = res.download_boot_file(req.kernel_url, req.kernel_sha.clone()) { + if let Err(kern_err) = res.find_or_download_file(req.kernel_url, req.kernel_sha.clone()) + { return Err(VMCreationErrors::BootFileError(format!( "Could not get kernel: {kern_err:?}" ))); }; - if let Err(dtrfs_err) = res.download_boot_file(req.dtrfs_url, req.dtrfs_sha.clone()) { + if let Err(dtrfs_err) = res.find_or_download_file(req.dtrfs_url, req.dtrfs_sha.clone()) + { return Err(VMCreationErrors::BootFileError(format!( "Could not get dtrfs: {dtrfs_err:?}" ))); @@ -578,12 +623,10 @@ impl VM { res.reserved_memory = res.reserved_memory.saturating_sub(self.memory_mb); res.reserved_vcpus = res.reserved_vcpus.saturating_add(req.vcpus); res.reserved_vcpus = res.reserved_vcpus.saturating_sub(self.vcpus); - res.reserved_storage - .entry(self.storage_dir.clone()) - .and_modify(|gb| { - *gb = gb.saturating_add(req.disk_size_gb); - *gb = gb.saturating_sub(self.disk_size_gb); - }); + res.reserved_storage.entry(self.storage_dir.clone()).and_modify(|gb| { + *gb = gb.saturating_add(req.disk_size_gb); + *gb = gb.saturating_sub(self.disk_size_gb); + }); let _ = res.save_to_disk(); self.memory_mb = req.memory_mb; @@ -645,28 +688,25 @@ impl VM { dir + &self.uuid + ".qcow2" } + // If you change this here, you also have to change it in the CLI. + // The kernel params must match on both daemon and CLI to build the measurement. pub fn kernel_params(&self) -> String { let mut ip_string = String::new(); let mut i = 0; if self.fw_ports.len() > 0 { - ip_string += &format!( - "detee_net_eth{}={}_{}_{} ", - i, "10.0.2.15", "24", "10.0.2.2" - ); + ip_string += &format!("detee_net_eth{}={}_{}_{} ", i, "10.0.2.15", "24", "10.0.2.2"); i += 1; } for nic in self.nics.iter() { for ip in nic.ips.iter() { - ip_string += &format!( - "detee_net_eth{}={}_{}_{} ", - i, ip.address, ip.mask, ip.gateway - ); + ip_string += + &format!("detee_net_eth{}={}_{}_{} ", i, ip.address, ip.mask, ip.gateway); } i += 1; } - let admin_key = format!("detee_admin={}", self.admin_key); + let admin_key = format!("detee_admin={} ", self.admin_key); let hostname = format!("detee_name={}", self.hostname); - format!("{} {} {}", ip_string, admin_key, hostname) + format!("{}{}{}", ip_string, admin_key, hostname) } fn write_config(&self) -> Result<()> { @@ -686,11 +726,7 @@ impl VM { let mut i = 0; for nic in self.nics.iter() { let mut interface = String::new(); - interface += &format!( - r#"export NETWORK_INTERFACE_{}="{}"#, - i, - nic.if_config.if_type() - ); + interface += &format!(r#"export NETWORK_INTERFACE_{}="{}"#, i, nic.if_config.if_type()); // device is currently ignored in case of NAT cause we assume QEMU userspace NAT if let Some(vtap_name) = nic.if_config.vtap_name() { interface += &format!("_{}_{}", nic.if_config.device_name(), vtap_name); @@ -711,15 +747,9 @@ impl VM { vars += "export NETWORK_INTERFACE_0000=NAT\n"; } - vars += &format!( - r#"export KERNEL="{}""#, - VM_BOOT_DIR.to_string() + &self.kernel_sha - ); + vars += &format!(r#"export KERNEL="{}""#, VM_BOOT_DIR.to_string() + &self.kernel_sha); vars += "\n"; - vars += &format!( - r#"export INITRD="{}""#, - VM_BOOT_DIR.to_string() + &self.dtrfs_sha - ); + vars += &format!(r#"export INITRD="{}""#, VM_BOOT_DIR.to_string() + &self.dtrfs_sha); vars += "\n"; vars += &format!(r#"export PARAMS="{}""#, self.kernel_params()); vars += "\n"; @@ -747,11 +777,7 @@ impl VM { fn delete_vtap_interfaces(&self) -> Result<()> { for nic in self.nics.iter() { if let Some(name) = nic.if_config.vtap_name() { - let result = Command::new("ip") - .arg("link") - .arg("del") - .arg(&name) - .output()?; + let result = Command::new("ip").arg("link").arg("del").arg(&name).output()?; if !result.status.success() { return Err(anyhow!( "Could not delete vtap interface {:?}:\n{:?}\n{:?}", @@ -794,10 +820,7 @@ impl VM { fn create_disk(&self) -> Result<()> { if std::path::Path::new(&self.disk_path()).exists() { - return Err(anyhow!( - "Could not create {}. The file already exists.", - self.disk_path() - )); + return Err(anyhow!("Could not create {}. The file already exists.", self.disk_path())); } let result = Command::new("qemu-img") @@ -844,10 +867,8 @@ impl VM { } fn systemctl_start_and_enable(vm_uuid: &str) -> Result<()> { - let result = Command::new("systemctl") - .arg("start") - .arg(vm_uuid.to_string() + ".service") - .output()?; + let result = + Command::new("systemctl").arg("start").arg(vm_uuid.to_string() + ".service").output()?; if !result.status.success() { return Err(anyhow!( "Could not reload systemctl daemon:\n{:?}\n{:?}", @@ -857,10 +878,8 @@ fn systemctl_start_and_enable(vm_uuid: &str) -> Result<()> { .unwrap_or("Could not grab stderr from creation script.".to_string()), )); } - let result = Command::new("systemctl") - .arg("enable") - .arg(vm_uuid.to_string() + ".service") - .output()?; + let result = + Command::new("systemctl").arg("enable").arg(vm_uuid.to_string() + ".service").output()?; if !result.status.success() { return Err(anyhow!( "Could not reload systemctl daemon:\n{:?}\n{:?}", @@ -874,10 +893,8 @@ fn systemctl_start_and_enable(vm_uuid: &str) -> Result<()> { } fn systemctl_stop_and_disable(vm_uuid: &str) -> Result<()> { - let result = Command::new("systemctl") - .arg("stop") - .arg(vm_uuid.to_string() + ".service") - .output()?; + let result = + Command::new("systemctl").arg("stop").arg(vm_uuid.to_string() + ".service").output()?; if !result.status.success() { return Err(anyhow!( "Could not reload systemctl daemon:\n{:?}\n{:?}", @@ -887,10 +904,8 @@ fn systemctl_stop_and_disable(vm_uuid: &str) -> Result<()> { .unwrap_or("Could not grab stderr from creation script.".to_string()), )); } - let result = Command::new("systemctl") - .arg("disable") - .arg(vm_uuid.to_string() + ".service") - .output()?; + let result = + Command::new("systemctl").arg("disable").arg(vm_uuid.to_string() + ".service").output()?; if !result.status.success() { return Err(anyhow!( "Could not reload systemctl daemon:\n{:?}\n{:?}", @@ -919,9 +934,7 @@ fn systemctl_reload() -> Result<()> { fn download_and_check_sha(url: &str, sha: &str) -> Result<()> { use reqwest::blocking::get; - use std::fs::File; - use std::io::copy; - use std::path::Path; + use std::{fs::File, io::copy, path::Path}; let save_path = VM_BOOT_DIR.to_string() + sha; let response = get(url)?; if !response.status().is_success() { @@ -958,41 +971,3 @@ fn compute_sha256>(path: P) -> Result { let result = hasher.finalize(); Ok(format!("{:x}", result)) } - -fn calc_ipv4_netmask(ip: Ipv4Addr, gateway: Ipv4Addr) -> String { - // Convert the IPs to u32 for easier bit manipulation - let ip_u32 = u32::from(ip); - let gateway_u32 = u32::from(gateway); - - // Find the smallest common prefix - let mut prefix_len = 0; - for i in 1..=32 { - if (ip_u32 >> (32 - i)) == (gateway_u32 >> (32 - i)) { - prefix_len = i; - } else { - break; - } - } - - // Return the mask as a string - prefix_len.to_string() -} - -fn calc_ipv6_netmask(ip: Ipv6Addr, gateway: Ipv6Addr) -> String { - // Convert the IPs to u128 for easier bit manipulation - let ip_u128 = u128::from(ip); - let gateway_u128 = u128::from(gateway); - - // Find the smallest common prefix - let mut prefix_len = 0; - for i in 1..=128 { - if (ip_u128 >> (128 - i)) == (gateway_u128 >> (128 - i)) { - prefix_len = i; - } else { - break; - } - } - - // Return the mask as a string - prefix_len.to_string() -} diff --git a/src/tcontract.rs b/src/tcontract.rs deleted file mode 100644 index bc9ddbe..0000000 --- a/src/tcontract.rs +++ /dev/null @@ -1,25 +0,0 @@ -#![allow(dead_code)] -// this is defined in the engine but we will mock it here for now - -pub struct FinalizedTContract { - pub owner: String, - pub user: String, - pub alloc: ResourceAllocation, - pub kernel_uri: String, - pub kernel_sha: String, - pub dtrfs_uri: String, - pub dtrfs_sha: String, -} - -#[derive(Default)] -pub struct ResourceAllocation { - pub vcpus: usize, - pub memory: usize, - pub storage: usize, - pub extra_ports: Vec, - // storage tier: not part of MVP - // pub storage_tier: usize, - pub public_ipv4: Option, - pub public_ipv6: Option, -} -