diff --git a/Cargo.lock b/Cargo.lock index 9a03f29cf..4718b74e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,13 +4,14 @@ version = 3 [[package]] name = "actix" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f728064aca1c318585bf4bb04ffcfac9e75e508ab4e8b1bd9ba5dfe04e2cbed5" +checksum = "cba56612922b907719d4a01cf11c8d5b458e7d3dba946d0435f20f58d6795ed2" dependencies = [ + "actix-macros", "actix-rt", "actix_derive", - "bitflags 1.3.2", + "bitflags 2.4.0", "bytes", "crossbeam-channel", "futures-core", @@ -45,9 +46,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.3.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2079246596c18b4a33e274ae10c0e50613f4d32a4198e09c7b93771013fed74" +checksum = "a92ef85799cba03f76e4f7c10f533e66d87c9a7e7055f3391f09000ad8351bc9" dependencies = [ "actix-codec", "actix-rt", @@ -55,8 +56,8 @@ dependencies = [ "actix-tls", "actix-utils", "ahash 0.8.3", - "base64 0.21.3", - "bitflags 1.3.2", + "base64 0.21.4", + "bitflags 2.4.0", "brotli", "bytes", "bytestring", @@ -90,7 +91,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -147,9 +148,9 @@ dependencies = [ [[package]] name = "actix-tls" -version = "3.1.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a70bd48b6604191a700372f60bdc997db560eff5e4d41a7f00664390b5228b38" +checksum = "72616e7fbec0aa99c6f3164677fa48ff5a60036d0799c98cab894a44f3e0efc3" dependencies = [ "actix-rt", "actix-service", @@ -159,6 +160,8 @@ dependencies = [ "impl-more", "openssl", "pin-project-lite", + "rustls", + "rustls-webpki", "tokio", "tokio-openssl", "tokio-util", @@ -177,9 +180,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.3.1" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3cb42f9566ab176e1ef0b8b3a896529062b4efc6be0123046095914c4c1c96" +checksum = "0e4a5b5e29603ca8c94a77c65cf874718ceb60292c5a5c3e5f4ace041af462b9" dependencies = [ "actix-codec", "actix-http", @@ -189,7 +192,7 @@ dependencies = [ "actix-service", "actix-tls", "actix-utils", - "ahash 0.7.6", + "ahash 0.8.3", "bytes", "bytestring", "cfg-if", @@ -197,7 +200,6 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "http", "itoa", "language-tags", "log", @@ -209,7 +211,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.4.9", + "socket2 0.5.3", "time", "url", ] @@ -231,13 +233,13 @@ dependencies = [ [[package]] name = "actix_derive" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d44b8fee1ced9671ba043476deddef739dd0959bf77030b26b738cc591737a7" +checksum = "7c7db3d5a9718568e4cf4a537cfd7070e6e6ff7481510d0237fb529ac850f6d3" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.32", ] [[package]] @@ -280,9 +282,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" dependencies = [ "memchr", ] @@ -308,7 +310,6 @@ version = "0.1.0" dependencies = [ "althea_types", "ipnetwork", - "itertools", "lazy_static", "log", "mac_address", @@ -316,7 +317,6 @@ dependencies = [ "regex", "serde", "serde_derive", - "serde_json", ] [[package]] @@ -345,7 +345,6 @@ dependencies = [ "bincode", "clarity", "deep_space", - "hex", "ipnetwork", "lettre", "num256", @@ -380,7 +379,6 @@ name = "antenna_forwarding_protocol" version = "0.1.0" dependencies = [ "althea_types", - "clarity", "lazy_static", "log", "rand", @@ -405,12 +403,6 @@ dependencies = [ "serde", ] -[[package]] -name = "ascii" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" - [[package]] name = "async-stream" version = "0.3.5" @@ -430,7 +422,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -441,7 +433,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -452,12 +444,9 @@ dependencies = [ "clarity", "futures 0.3.28", "log", - "num", "num256", - "rand", "serde", "serde_derive", - "tokio", "web30", ] @@ -469,9 +458,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "awc" -version = "3.1.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ef547a81796eb2dfe9b345aba34c2e08391a0502493711395b36dd64052b69" +checksum = "7fa3c705a9c7917ac0f41c0757a0a747b43bbc29b0b364b081bd7c5fc67fb223" dependencies = [ "actix-codec", "actix-http", @@ -479,8 +468,7 @@ dependencies = [ "actix-service", "actix-tls", "actix-utils", - "ahash 0.7.6", - "base64 0.21.3", + "base64 0.21.4", "bytes", "cfg-if", "cookie", @@ -551,8 +539,6 @@ dependencies = [ name = "babel_monitor" version = "0.1.0" dependencies = [ - "ascii", - "env_logger", "ipnetwork", "log", "serde", @@ -593,9 +579,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.3" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "bech32" @@ -765,9 +751,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "bytestring" @@ -822,14 +808,11 @@ dependencies = [ "althea_types", "clarity", "deep_space", - "env_logger", "ipgen", "lazy_static", "log", "rand", "regex", - "serde", - "serde_derive", "serde_json", "settings", "sodiumoxide", @@ -947,11 +930,11 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e" +checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf" dependencies = [ - "nix 0.26.3", + "nix 0.27.1", "windows-sys", ] @@ -962,7 +945,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dd3eeda5582365b7f15ba9d3fa196efde6d344ac9d63eb3dd1c03e169acb17" dependencies = [ "althea_proto", - "base64 0.21.3", + "base64 0.21.4", "bech32", "bytes", "clarity", @@ -1091,7 +1074,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbfb21b9878cf7a348dcb8559109aabc0ec40d69924bd706fa5149846c4fef75" dependencies = [ - "base64 0.21.3", + "base64 0.21.4", "memchr", ] @@ -1131,9 +1114,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", @@ -1275,7 +1258,6 @@ dependencies = [ "futures-core", "futures-task", "futures-util", - "num_cpus", ] [[package]] @@ -1292,7 +1274,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -1408,9 +1390,9 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "handlebars" -version = "4.3.7" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c3372087601b532857d332f5957cbae686da52bb7810bf038c3e3c3cc2fa0d" +checksum = "c39b3bc2a8f715298032cf5087e58573809374b08160aa7d750582bdb82d2683" dependencies = [ "log", "pest", @@ -1450,12 +1432,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "hex-literal" version = "0.3.4" @@ -1649,7 +1625,7 @@ dependencies = [ "ipnetwork", "lazy_static", "log", - "nix 0.26.3", + "nix 0.26.4", "num-traits", "num256", "petgraph", @@ -1705,6 +1681,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -1776,7 +1761,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76bd09637ae3ec7bd605b8e135e757980b3968430ff2b1a4a94fb7769e50166d" dependencies = [ - "base64 0.21.3", + "base64 0.21.4", "email-encoding", "email_address", "fastrand 1.9.0", @@ -1821,9 +1806,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" [[package]] name = "local-channel" @@ -1901,9 +1886,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.0" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76fc44e2588d5b436dbc3c6cf62aef290f90dab6235744a93dfe1cc18f451e2c" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "memoffset" @@ -2010,11 +1995,11 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.3" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abbbc55ad7b13aac85f9401c796dcda1b864e07fcad40ad47792eaa8932ea502" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ - "bitflags 2.4.0", + "bitflags 1.3.2", "cfg-if", "libc", "memoffset 0.7.1", @@ -2022,80 +2007,24 @@ dependencies = [ ] [[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "num" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.43" +name = "nix" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "autocfg", - "num-integer", - "num-traits", + "bitflags 2.4.0", + "cfg-if", + "libc", ] [[package]] -name = "num-rational" -version = "0.4.1" +name = "nom" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ - "autocfg", - "num-bigint", - "num-integer", - "num-traits", + "memchr", + "minimal-lexical", ] [[package]] @@ -2130,9 +2059,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -2155,7 +2084,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c75a0ec2d1b302412fb503224289325fcc0e44600176864804c7211b055cfd58" dependencies = [ - "base64 0.21.3", + "base64 0.21.4", "byteorder", "md-5", "sha2", @@ -2185,7 +2114,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -2196,18 +2125,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "111.27.0+1.1.1v" +version = "300.1.3+3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e8f197c82d7511c5b014030c9b1efeda40d7d5f99d23b4ceed3524a5e63f02" +checksum = "cd2c101a165fff9935e34def4669595ab1c7847943c42be86e21503e482be107" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.92" +version = "0.9.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7e971c2c2bba161b2d2fdf37080177eff520b3bc044787c7f1f5f9e78d869b" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" dependencies = [ "cc", "libc", @@ -2274,19 +2203,20 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" +checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" dependencies = [ + "memchr", "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" +checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" dependencies = [ "pest", "pest_generator", @@ -2294,22 +2224,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" +checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] name = "pest_meta" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" +checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" dependencies = [ "once_cell", "pest", @@ -2335,7 +2265,7 @@ dependencies = [ "bincode", "either", "fnv", - "itertools", + "itertools 0.10.5", "lazy_static", "nom", "quick-xml", @@ -2363,7 +2293,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -2434,10 +2364,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56075c27b20ae524d00f247b8a4dc333e5784f889fe63099f8e626bc8d73486c" dependencies = [ "anyhow", - "itertools", + "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -2551,9 +2481,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" dependencies = [ "aho-corasick", "memchr", @@ -2563,9 +2493,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" dependencies = [ "aho-corasick", "memchr", @@ -2611,7 +2541,7 @@ version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" dependencies = [ - "base64 0.21.3", + "base64 0.21.4", "bytes", "encoding_rs", "futures-core", @@ -2642,6 +2572,21 @@ dependencies = [ "winreg", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi 0.3.9", +] + [[package]] name = "ripemd" version = "0.1.3" @@ -2693,7 +2638,6 @@ version = "0.21.2" dependencies = [ "actix", "actix-web", - "actix-web-httpauth", "althea_kernel_interface", "althea_types", "antenna_forwarding_client", @@ -2701,9 +2645,7 @@ dependencies = [ "awc", "babel_monitor", "clarity", - "clu", "compressed_log", - "deep_space", "futures 0.3.28", "hex-literal", "ipnetwork", @@ -2747,11 +2689,9 @@ dependencies = [ "compressed_log", "cosmos-sdk-proto-althea", "deep_space", - "docopt", "env_logger", "flate2", "futures 0.3.28", - "hex-literal", "ipnetwork", "lazy_static", "log", @@ -2776,14 +2716,10 @@ dependencies = [ "actix-web", "althea_kernel_interface", "althea_types", - "arrayvec", "awc", "babel_monitor", "clarity", - "compressed_log", "deep_space", - "diesel", - "exit_db", "handlebars", "ipnetwork", "lazy_static", @@ -2791,8 +2727,6 @@ dependencies = [ "log", "num256", "phonenumber", - "r2d2", - "rand", "reqwest", "rita_common", "serde", @@ -2883,9 +2817,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.9" +version = "0.38.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bfe0f2582b4931a45d1fa608f8a8722e8b3c7ac54dd6d5f3b3212791fedef49" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" dependencies = [ "bitflags 2.4.0", "errno", @@ -2894,6 +2828,28 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "rustls" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -2939,6 +2895,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "seahash" version = "4.1.0" @@ -3019,14 +2985,14 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2" dependencies = [ "itoa", "ryu", @@ -3054,7 +3020,6 @@ dependencies = [ "arrayvec", "auto-bridge", "clarity", - "deep_space", "ipnetwork", "lazy_static", "log", @@ -3166,6 +3131,12 @@ dependencies = [ "serde", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "strsim" version = "0.10.0" @@ -3191,9 +3162,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.29" +version = "2.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" dependencies = [ "proc-macro2", "quote", @@ -3250,22 +3221,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -3348,7 +3319,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -3416,7 +3387,7 @@ dependencies = [ "async-stream", "async-trait", "axum", - "base64 0.21.3", + "base64 0.21.4", "bytes", "flate2", "h2", @@ -3488,7 +3459,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -3539,6 +3510,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.4.1" @@ -3573,9 +3550,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", @@ -3617,7 +3594,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", "wasm-bindgen-shared", ] @@ -3651,7 +3628,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/althea_kernel_interface/Cargo.toml b/althea_kernel_interface/Cargo.toml index 2ae9e4a6d..96a4bda59 100644 --- a/althea_kernel_interface/Cargo.toml +++ b/althea_kernel_interface/Cargo.toml @@ -8,7 +8,6 @@ edition = "2018" [dependencies] oping = "0.3" -itertools = "0.10" lazy_static = "1.4" log = "0.4" serde_derive = "1.0" @@ -16,7 +15,6 @@ serde = "1.0" althea_types = { path = "../althea_types" } ipnetwork = "0.20" mac_address = "1.1.4" -serde_json = "1.0" [dependencies.regex] version = "1.6" @@ -25,4 +23,4 @@ features = ["std"] [features] -integration_test = [] \ No newline at end of file +integration_test = [] diff --git a/althea_kernel_interface/src/exit_server_tunnel.rs b/althea_kernel_interface/src/exit_server_tunnel.rs index 79451c5fa..aa54b7fb8 100644 --- a/althea_kernel_interface/src/exit_server_tunnel.rs +++ b/althea_kernel_interface/src/exit_server_tunnel.rs @@ -6,10 +6,10 @@ use std::collections::HashSet; use std::net::IpAddr; use KernelInterfaceError as Error; -#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] pub struct ExitClient { pub internal_ip: IpAddr, - pub internet_ipv6_list: Vec, + pub internet_ipv6: Option, pub public_key: WgKey, pub mesh_ip: IpAddr, pub port: u16, @@ -39,15 +39,13 @@ impl dyn KernelInterface { for c in clients.iter() { // For the allowed IPs, we appends the clients internal ip as well - // as the client ipv6 assigned list and add this to wireguards allowed ips - // internet_ipv6_list is already in the form of ",.." - let i_ipv6 = &c.internet_ipv6_list; + // as the client ipv6 assigned ip and add this to wireguards allowed ips + // internet_ipv6 is already in the form of ",.." + let i_ipv6 = &c.internet_ipv6; let mut allowed_ips = c.internal_ip.to_string().to_owned(); - if !i_ipv6.is_empty() { - for ip_net in i_ipv6 { - allowed_ips.push(','); - allowed_ips.push_str(&ip_net.to_string()); - } + if let Some(i_ipv6) = i_ipv6 { + allowed_ips.push(','); + allowed_ips.push_str(&i_ipv6.to_string()); } args.push("peer".into()); diff --git a/althea_types/Cargo.toml b/althea_types/Cargo.toml index 7df7155c8..951b99413 100644 --- a/althea_types/Cargo.toml +++ b/althea_types/Cargo.toml @@ -13,16 +13,14 @@ base64 = "0.13" serde_derive = "1.0" serde = "1.0" serde_json = "1.0" -hex = "0.4" sodiumoxide = "0.2" clarity = "1.2" -arrayvec = {version= "0.7", features = ["serde"]} +arrayvec = { version = "0.7", features = ["serde"] } phonenumber = "0.3" -lettre = {version = "0.10", features = ["serde"]} +lettre = { version = "0.10", features = ["serde"] } ipnetwork = "0.20" bincode = "1.3" -deep_space = {workspace = true} +deep_space = { workspace = true } [dev-dependencies] rand = "0.8" - diff --git a/althea_types/src/interop.rs b/althea_types/src/interop.rs index fc8a92d50..a71132d4c 100644 --- a/althea_types/src/interop.rs +++ b/althea_types/src/interop.rs @@ -778,8 +778,6 @@ pub struct OperatorExitCheckinMessage { pub pass: String, /// This is to keep track of the rita exit uptime for debugging purposes pub exit_uptime: Duration, - /// A list of registered wg keys that ops can use to display routers to be registered - pub registered_keys: Option>, /// Number of users online pub users_online: Option, } diff --git a/antenna_forwarding_protocol/Cargo.toml b/antenna_forwarding_protocol/Cargo.toml index 6fc6a6c8f..bd08d15b9 100644 --- a/antenna_forwarding_protocol/Cargo.toml +++ b/antenna_forwarding_protocol/Cargo.toml @@ -5,14 +5,13 @@ authors = ["Justin Kilpatrick "] edition = "2018" [dependencies] -althea_types = { path = "../althea_types"} +althea_types = { path = "../althea_types" } serde_json = "1.0" serde_derive = "1.0" serde = "1.0" sodiumoxide = "0.2" -clarity = "1.2" log = "0.4" lazy_static = "1.4" [dev-dependencies] -rand = "0.8" \ No newline at end of file +rand = "0.8" diff --git a/auto_bridge/Cargo.toml b/auto_bridge/Cargo.toml index 961ed5566..723b6d49d 100644 --- a/auto_bridge/Cargo.toml +++ b/auto_bridge/Cargo.toml @@ -8,13 +8,9 @@ edition = "2018" web30 = "1.0" num256 = "0.5" clarity = "1.2" -rand = "0.8" -num = "0.4" log = "0.4" serde_derive = "1.0" serde = "1.0" -tokio = "1.2" -futures = {version="0.3", features = ["thread-pool"]} [dev-dependencies] actix = "0.13" diff --git a/babel_monitor/Cargo.toml b/babel_monitor/Cargo.toml index 9fe47060f..cb073194b 100644 --- a/babel_monitor/Cargo.toml +++ b/babel_monitor/Cargo.toml @@ -5,8 +5,6 @@ version = "0.1.0" edition = "2018" [dependencies] -ascii = "1.0" -env_logger = "0.10" ipnetwork = "0.20" log = "0.4" serde = "1.0" diff --git a/clu/Cargo.toml b/clu/Cargo.toml index 9ccbaa9b2..c234b8163 100644 --- a/clu/Cargo.toml +++ b/clu/Cargo.toml @@ -10,15 +10,12 @@ althea_kernel_interface = { path = "../althea_kernel_interface" } althea_types = { path = "../althea_types" } lazy_static = "1.4" log = "0.4" -env_logger = "0.10.0" ipgen = "1.0.1" rand = "0.8" -serde = "1.0" -serde_derive = "1.0" serde_json = "1.0" clarity = "1.2" sodiumoxide = "0.2" -deep_space = {workspace = true} +deep_space = { workspace = true } [dependencies.regex] version = "1.5" diff --git a/integration_tests/src/setup_utils/rita.rs b/integration_tests/src/setup_utils/rita.rs index 88e331001..f65973620 100644 --- a/integration_tests/src/setup_utils/rita.rs +++ b/integration_tests/src/setup_utils/rita.rs @@ -18,7 +18,6 @@ use rita_common::rita_loop::{ start_core_rita_endpoints, start_rita_common_loops, write_to_disk::{save_to_disk_loop, SettingsOnDisk}, }; -use rita_exit::initialize_db_pool; use rita_exit::{ operator_update::update_loop::start_operator_update_loop, rita_loop::{start_rita_exit_endpoints, start_rita_exit_loop}, @@ -164,9 +163,9 @@ pub fn spawn_rita( start_rita_common_loops(); start_rita_client_loops(); - save_to_disk_loop(SettingsOnDisk::RitaClientSettings( + save_to_disk_loop(SettingsOnDisk::RitaClientSettings(Box::new( settings::get_rita_client(), - )); + ))); start_core_rita_endpoints(4); start_client_dashboard(s.network.rita_dashboard_port); start_antenna_forwarder(s); @@ -254,14 +253,12 @@ pub fn spawn_rita_exit( let system = actix_async::System::new(); - initialize_db_pool(); - start_rita_common_loops(); start_rita_exit_loop(); start_operator_update_loop(); - save_to_disk_loop(SettingsOnDisk::RitaExitSettingsStruct( + save_to_disk_loop(SettingsOnDisk::RitaExitSettingsStruct(Box::new( settings::get_rita_exit(), - )); + ))); let workers = 4; start_core_rita_endpoints(workers as usize); diff --git a/integration_tests/src/utils.rs b/integration_tests/src/utils.rs index 847edbb9a..98258189a 100644 --- a/integration_tests/src/utils.rs +++ b/integration_tests/src/utils.rs @@ -348,10 +348,7 @@ pub fn get_default_settings( let mut exit = exit.clone(); let mut client = client.clone(); - // exit should allow instant registration by any requester - exit.verif_settings = None; exit.network.mesh_ip = Some(cluster.root_ip); - exit.exit_network.cluster_exits = cluster_exits.clone(); client.exit_client.contact_info = Some( ContactType::Both { number: "+11111111".parse().unwrap(), diff --git a/rita_bin/src/client.rs b/rita_bin/src/client.rs index b065431ed..8a63f9266 100644 --- a/rita_bin/src/client.rs +++ b/rita_bin/src/client.rs @@ -142,9 +142,9 @@ fn main() { start_rita_common_loops(); start_rita_client_loops(); - save_to_disk_loop(SettingsOnDisk::RitaClientSettings( + save_to_disk_loop(SettingsOnDisk::RitaClientSettings(Box::new( settings::get_rita_client(), - )); + ))); start_core_rita_endpoints(4); start_client_dashboard(settings.network.rita_dashboard_port); start_antenna_forwarder(settings); diff --git a/rita_bin/src/exit.rs b/rita_bin/src/exit.rs index 6dc11a8cd..8b29d1471 100644 --- a/rita_bin/src/exit.rs +++ b/rita_bin/src/exit.rs @@ -13,8 +13,6 @@ #![allow(clippy::pedantic)] #![forbid(unsafe_code)] -use std::collections::HashSet; - #[cfg(feature = "jemalloc")] use jemallocator::Jemalloc; #[cfg(feature = "jemalloc")] @@ -25,7 +23,6 @@ static GLOBAL: Jemalloc = Jemalloc; extern crate log; use docopt::Docopt; -use ipnetwork::IpNetwork; use rita_common::debt_keeper::save_debt_on_shutdown; use rita_common::logging::enable_remote_logging; use rita_common::rita_loop::start_core_rita_endpoints; @@ -34,8 +31,6 @@ use rita_common::rita_loop::write_to_disk::save_to_disk_loop; use rita_common::rita_loop::write_to_disk::SettingsOnDisk; use rita_common::usage_tracker::save_usage_on_shutdown; use rita_common::utils::env_vars_contains; -use rita_exit::database::sms::send_admin_notification_sms; -use rita_exit::initialize_db_pool; use rita_exit::operator_update::update_loop::start_operator_update_loop; use rita_exit::rita_loop::start_rita_exit_endpoints; use rita_exit::rita_loop::start_rita_exit_loop; @@ -56,28 +51,6 @@ fn sanity_check_config() { // check wg_exit_v2 port is valid assert!(exit_settings.exit_network.wg_v2_tunnel_port < 59999); - - // Check that there is atleast one exit in cluster - assert!(!exit_settings.exit_network.cluster_exits.is_empty()); - - // Check cluster exits have different ips and wg_keys - let mut ip_sub: Option = None; - let mut wg_key_hashset = HashSet::new(); - for id in exit_settings.exit_network.cluster_exits.iter() { - if let Some(net) = ip_sub { - if net.contains(id.mesh_ip) { - panic!("Ips in cluster exits collide in /116 subnet"); - } - } else { - ip_sub = Some(IpNetwork::new(id.mesh_ip, 116).unwrap()) - } - - if wg_key_hashset.contains(&id.wg_public_key) { - panic!("Conflicting wg keys in cluster exits, please fix"); - } else { - wg_key_hashset.insert(id.wg_public_key); - } - } } fn main() { @@ -148,19 +121,14 @@ fn main() { ); trace!("Starting with Identity: {:?}", settings.get_identity()); - send_admin_notification_sms("Exit restarted"); - - // Initialize db pool - initialize_db_pool(); - let system = actix_async::System::new(); start_rita_common_loops(); start_rita_exit_loop(); start_operator_update_loop(); - save_to_disk_loop(SettingsOnDisk::RitaExitSettingsStruct( + save_to_disk_loop(SettingsOnDisk::RitaExitSettingsStruct(Box::new( settings::get_rita_exit(), - )); + ))); let workers = settings.workers; start_core_rita_endpoints(workers as usize); diff --git a/rita_client/Cargo.toml b/rita_client/Cargo.toml index 9a3943557..052e5ab77 100644 --- a/rita_client/Cargo.toml +++ b/rita_client/Cargo.toml @@ -7,7 +7,7 @@ license = "Apache-2.0" [dependencies] compressed_log = "0.5" -num-traits="0.2" +num-traits = "0.2" num256 = "0.5" serde = "1.0" serde_derive = "1.0" @@ -25,20 +25,19 @@ lettre = "0.10" rand = "0.8.0" phonenumber = "0.3" babel_monitor = { path = "../babel_monitor" } -arrayvec = {version= "0.7", features = ["serde"]} +arrayvec = { version = "0.7", features = ["serde"] } sodiumoxide = "0.2" -clu = { path = "../clu" } web30 = "1.0" awc = "3.1" ipnetwork = "0.20" -actix-async = {package="actix", version = "0.13"} -actix-web-async = { package="actix-web", version = "4.3", default_features = false, features= ["openssl"]} -actix-web-httpauth-async = { package="actix-web-httpauth", version = "0.8.0"} +actix-async = { package = "actix", version = "0.13" } +actix-web-async = { package = "actix-web", version = "4.3", default_features = false, features = [ + "openssl", +] } clarity = "1.2" openssh-keys = "0.6" mac_address = "1.1.4" futures = { version = "0.3", features = ["compat"] } -deep_space = {workspace = true} [lib] name = "rita_client" diff --git a/rita_common/Cargo.toml b/rita_common/Cargo.toml index e18e27e3b..3dd0995b5 100644 --- a/rita_common/Cargo.toml +++ b/rita_common/Cargo.toml @@ -8,37 +8,45 @@ license = "Apache-2.0" rand = "0.8.0" ipnetwork = "0.20" serde_derive = "1.0" -hex-literal = "0.3" -docopt = "1.1" serde = "1.0" bytes = "1.0" compressed_log = "0.5.4" byteorder = { version = "1.4", features = ["i128"] } -arrayvec = {version= "0.7", features = ["serde"]} +arrayvec = { version = "0.7", features = ["serde"] } babel_monitor = { path = "../babel_monitor" } -flate2 = { version = "1.0", features = ["rust_backend"], default-features = false } -actix-async = {package="actix", version = "0.13"} -auto-bridge = {path = "../auto_bridge"} +flate2 = { version = "1.0", features = [ + "rust_backend", +], default-features = false } +actix-async = { package = "actix", version = "0.13" } +auto-bridge = { path = "../auto_bridge" } serde_json = "1.0" log = { version = "0.4", features = ["release_max_level_info"] } settings = { path = "../settings" } clarity = "1.2" futures = { version = "0.3", features = ["compat"] } num256 = "0.5" -num-traits="0.2" +num-traits = "0.2" bincode = "1.3" serde_cbor = "0.11" lazy_static = "1.4" althea_kernel_interface = { path = "../althea_kernel_interface" } -actix-web-httpauth-async = { package="actix-web-httpauth", version = "0.8.0"} -actix-web-async = { package="actix-web", version = "4.3", default_features = false, features= ["openssl"]} -awc = {version = "3.1", default-features = false, features=["openssl", "compress-gzip", "compress-zstd"]} +actix-web-httpauth-async = { package = "actix-web-httpauth", version = "0.8.0" } +actix-web-async = { package = "actix-web", version = "4.3", default_features = false, features = [ + "openssl", +] } +awc = { version = "3.1", default-features = false, features = [ + "openssl", + "compress-gzip", + "compress-zstd", +] } actix-service = "2.0.2" web30 = "1.0" althea_types = { path = "../althea_types" } -deep_space = {workspace = true} -prost-types ="0.12" -cosmos-sdk-proto-althea = {package = "cosmos-sdk-proto-althea", version = "0.16", features = ["ethermint"]} +deep_space = { workspace = true } +prost-types = "0.12" +cosmos-sdk-proto-althea = { package = "cosmos-sdk-proto-althea", version = "0.16", features = [ + "ethermint", +] } [dependencies.regex] version = "1.6" diff --git a/rita_common/src/rita_loop/write_to_disk.rs b/rita_common/src/rita_loop/write_to_disk.rs index 0d19b3c29..1a38ea87f 100644 --- a/rita_common/src/rita_loop/write_to_disk.rs +++ b/rita_common/src/rita_loop/write_to_disk.rs @@ -17,8 +17,8 @@ pub const FAST_LOOP_TIMEOUT: Duration = Duration::from_secs(4); // pub const SAVING_TO_DISK_FREQUENCY: Duration = Duration::from_secs(600); #[derive(Clone)] pub enum SettingsOnDisk { - RitaClientSettings(RitaClientSettings), - RitaExitSettingsStruct(RitaExitSettingsStruct), + RitaClientSettings(Box), + RitaExitSettingsStruct(Box), } /// This loop attempts to perform all write operations for writing to disk /// This includes writing config/settings, usage tracker, and debt tracker. @@ -65,26 +65,26 @@ pub fn save_to_disk_loop(mut old_settings: SettingsOnDisk) { SettingsOnDisk::RitaClientSettings(old_settings_client) => { let new_settings = get_rita_client(); - if old_settings_client != new_settings { + if old_settings_client != new_settings.clone().into() { let res = write_config(); if let Err(e) = res { error!("Error saving client settings! {:?}", e); } } - old_settings = SettingsOnDisk::RitaClientSettings(new_settings); + old_settings = SettingsOnDisk::RitaClientSettings(Box::new(new_settings)); } SettingsOnDisk::RitaExitSettingsStruct(old_settings_exit) => { let new_settings = get_rita_exit(); - if old_settings_exit != new_settings { + if old_settings_exit != new_settings.clone().into() { let res = write_config(); if let Err(e) = res { error!("Error saving exit settings! {:?}", e); } } - old_settings = SettingsOnDisk::RitaExitSettingsStruct(new_settings); + old_settings = SettingsOnDisk::RitaExitSettingsStruct(Box::new(new_settings)); } } diff --git a/rita_exit/Cargo.toml b/rita_exit/Cargo.toml index a51a4bf30..e1d66314d 100644 --- a/rita_exit/Cargo.toml +++ b/rita_exit/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" [dependencies] # debug is used here to make sure exit logs remain accessible locally -compressed_log = {version="0.5", features = ["debug"]} sodiumoxide = "0.2" num256 = "0.5" rita_common = { path = "../rita_common" } @@ -15,26 +14,23 @@ althea_kernel_interface = { path = "../althea_kernel_interface" } althea_types = { path = "../althea_types" } settings = { path = "../settings" } babel_monitor = { path = "../babel_monitor" } -actix-async = { package = "actix", version = "0.13"} +actix-async = { package = "actix", version = "0.13" } awc = "3.1" handlebars = "4.0" -rand = "0.8.0" lazy_static = "1.4" ipnetwork = "0.20" clarity = "1.2" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" -lettre = { version = "0.10", features = ["file-transport"]} -r2d2 = "0.8" +lettre = { version = "0.10", features = ["file-transport"] } phonenumber = "0.3" -arrayvec = {version= "0.7", features = ["serde"]} log = { version = "0.4", features = ["release_max_level_info"] } reqwest = { version = "0.11", features = ["blocking", "json"] } -exit_db = { path = "../exit_db" } -actix-web-async = {package="actix-web", version = "4.3", default_features = false, features= ["openssl"] } -diesel = { version = "1.4", features = ["postgres", "r2d2"] } -deep_space = {workspace = true} +actix-web-async = { package = "actix-web", version = "4.3", default_features = false, features = [ + "openssl", +] } +deep_space = { workspace = true } [features] # changes operator urls diff --git a/rita_exit/src/database/database_tools.rs b/rita_exit/src/database/database_tools.rs index 6826d3cf6..818d3798d 100644 --- a/rita_exit/src/database/database_tools.rs +++ b/rita_exit/src/database/database_tools.rs @@ -1,736 +1,15 @@ -use crate::database::secs_since_unix_epoch; -use crate::database::struct_tools::client_to_new_db_client; -use crate::database::ONE_DAY; -use exit_db::models::AssignedIps; -use ipnetwork::{IpNetwork, Ipv6Network, NetworkSize}; -use rita_common::utils::ip_increment::increment; +use althea_types::{Identity, WgKey}; +use ipnetwork::{IpNetwork, Ipv6Network}; -use crate::{get_db_pool, RitaExitError}; -use althea_kernel_interface::ExitClient; -use althea_types::ExitClientIdentity; -use diesel::dsl::{delete, exists}; -use diesel::prelude::{ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl}; -use diesel::r2d2::ConnectionManager; -use diesel::r2d2::PooledConnection; -use diesel::select; -use exit_db::{models, schema}; -use std::convert::TryInto; -use std::net::Ipv4Addr; +use crate::RitaExitError; use std::net::{IpAddr, Ipv6Addr}; // Default Subnet size assigned to each client -const DEFAULT_CLIENT_SUBNET_SIZE: u8 = 56; - -/// Takes a list of clients and returns a sorted list of ip addresses spefically v4 since it -/// can implement comparison operators -fn get_internal_ips(clients: &[exit_db::models::Client]) -> Vec { - let mut list = Vec::with_capacity(clients.len()); - for client in clients { - let client_internal_ip = client.internal_ip.parse(); - match client_internal_ip { - Ok(address) => list.push(address), - Err(_e) => error!("Bad database entry! {:?}", client), - } - } - // this list should come sorted from the database, this just double checks - list.sort(); - list -} - -/// Gets the next available client ip, takes about O(n) time, we could make it faster by -/// sorting on the database side but I've left that optimization on the vine for now -pub fn get_next_client_ip(conn: &PgConnection) -> Result> { - use self::schema::clients::dsl::clients; - let rita_exit = settings::get_rita_exit(); - let exit_settings = rita_exit.exit_network; - let netmask = exit_settings.netmask; - let start_ip = exit_settings.exit_start_ip; - let gateway_ip = exit_settings.own_internal_ip; - // drop here to free up the settings lock, this codepath runs in parallel - drop(exit_settings); - - let clients_list = match clients.load::(conn) { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }; - let ips_list = get_internal_ips(&clients_list); - let mut new_ip: IpAddr = start_ip.into(); - - // iterate until we find an open spot, yes converting to string and back is quite awkward - while ips_list.contains({ - match &new_ip.to_string().parse() { - Ok(a) => a, - Err(e) => return Err(Box::new(e.clone().into())), - } - }) { - new_ip = match increment(new_ip, netmask) { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }; - if new_ip == gateway_ip { - new_ip = match increment(new_ip, netmask) { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - } - } - } - trace!( - "The new client's ip is {} selected using {:?}", - new_ip, - ips_list - ); - - Ok(new_ip) -} - -/// updates the last seen time -pub fn update_client( - client: &ExitClientIdentity, - their_record: &models::Client, - conn: &PgConnection, -) -> Result<(), Box> { - use self::schema::clients::dsl::{ - clients, email, eth_address, last_seen, mesh_ip, phone, wg_pubkey, - }; - let ip = client.global.mesh_ip; - let wg = client.global.wg_public_key; - let key = client.global.eth_address; - let filtered_list = clients - .filter(mesh_ip.eq(ip.to_string())) - .filter(wg_pubkey.eq(wg.to_string())) - .filter(eth_address.eq(key.to_string().to_lowercase())); - - if let Some(mail) = client.reg_details.email.clone() { - if their_record.email != mail { - info!( - "Client {} email has changed from {} to {} updating", - their_record.wg_pubkey, their_record.email, mail - ); - if let Err(e) = diesel::update(filtered_list.clone()) - .set(email.eq(mail)) - .execute(conn) - { - return Err(Box::new(e.into())); - } - } - } - - if let Some(number) = client.reg_details.phone.clone() { - if their_record.phone != number { - info!( - "Client {} phonenumber has changed from {} to {} updating", - their_record.wg_pubkey, their_record.phone, number - ); - if let Err(e) = diesel::update(filtered_list.clone()) - .set(phone.eq(number)) - .execute(conn) - { - return Err(Box::new(e.into())); - } - } - } - - let current_time = secs_since_unix_epoch(); - let time_since_last_update = current_time - their_record.last_seen; - // update every 12 hours, no entry timeouts less than a day allowed - if time_since_last_update > ONE_DAY / 2 { - info!("Bumping client timestamp for {}", their_record.wg_pubkey); - if let Err(e) = diesel::update(filtered_list) - .set(last_seen.eq(secs_since_unix_epoch())) - .execute(conn) - { - return Err(Box::new(e.into())); - } - } - - Ok(()) -} - -pub fn get_client( - client: &ExitClientIdentity, - conn: &PgConnection, -) -> Result, Box> { - use self::schema::clients::dsl::{clients, eth_address, mesh_ip, wg_pubkey}; - let ip = client.global.mesh_ip; - let wg = client.global.wg_public_key; - let key = client.global.eth_address; - let filtered_list = clients - .filter(mesh_ip.eq(ip.to_string())) - .filter(wg_pubkey.eq(wg.to_string())) - // TODO search for EIP-55 capitalized key string and add a fallback - // to upgrade entires that are using the old all lowercase format. This should - // simplify the code some and reduce calls to .to_lowercase and worst of all the chance - // that we might forget a lowercase call, which type-checking can't protect us from. - .filter(eth_address.eq(key.to_string().to_lowercase())); - match filtered_list.load::(conn) { - Ok(entry) => { - if entry.len() > 1 { - let err_msg = - format!("More than one exact match with wg: {wg} eth: {key} ip: {ip}"); - error!("{}", err_msg); - panic!("{}", err_msg); - } else if entry.is_empty() { - return Ok(None); - } - Ok(Some(entry[0].clone())) - } - Err(e) => { - error!("We failed to lookup the client {:?} with{:?}", mesh_ip, e); - Err(Box::new(RitaExitError::MiscStringError( - "We failed to lookup the client!".to_string(), - ))) - } - } -} - -/// changes a clients verified value in the database -pub fn verify_client( - client: &ExitClientIdentity, - client_verified: bool, - conn: &PgConnection, -) -> Result<(), Box> { - use self::schema::clients::dsl::*; - let ip = client.global.mesh_ip; - let wg = client.global.wg_public_key; - let key = client.global.eth_address; - let filtered_list = clients - .filter(mesh_ip.eq(ip.to_string())) - .filter(wg_pubkey.eq(wg.to_string())) - .filter(eth_address.eq(key.to_string().to_lowercase())); - - if let Err(e) = diesel::update(filtered_list) - .set(verified.eq(client_verified)) - .execute(conn) - { - return Err(Box::new(e.into())); - } - - Ok(()) -} - -/// Marks a client as verified in the database -pub fn verify_db_client( - client: &models::Client, - client_verified: bool, - conn: &PgConnection, -) -> Result<(), Box> { - use self::schema::clients::dsl::*; - let ip = &client.mesh_ip; - let wg = &client.wg_pubkey; - let key = &client.eth_address; - let filtered_list = clients - .filter(mesh_ip.eq(ip.to_string())) - .filter(wg_pubkey.eq(wg.to_string())) - .filter(eth_address.eq(key.to_string().to_lowercase())); - - if let Err(e) = diesel::update(filtered_list) - .set(verified.eq(client_verified)) - .execute(conn) - { - return Err(Box::new(e.into())); - } - - Ok(()) -} - -/// Increments the text message sent count in the database -pub fn text_sent( - client: &ExitClientIdentity, - conn: &PgConnection, - val: i32, -) -> Result<(), Box> { - use self::schema::clients::dsl::*; - let ip = client.global.mesh_ip; - let wg = client.global.wg_public_key; - let key = client.global.eth_address; - let filtered_list = clients - .filter(mesh_ip.eq(ip.to_string())) - .filter(wg_pubkey.eq(wg.to_string())) - .filter(eth_address.eq(key.to_string().to_lowercase())); - - if let Err(e) = diesel::update(filtered_list) - .set(text_sent.eq(val + 1)) - .execute(conn) - { - return Err(Box::new(e.into())); - } - - Ok(()) -} - -fn client_exists( - client: &ExitClientIdentity, - conn: &PgConnection, -) -> Result> { - use self::schema::clients::dsl::*; - trace!("Checking if client exists"); - let ip = client.global.mesh_ip; - let wg = client.global.wg_public_key; - let key = client.global.eth_address; - let filtered_list = clients - .filter(mesh_ip.eq(ip.to_string())) - .filter(wg_pubkey.eq(wg.to_string())) - .filter(eth_address.eq(key.to_string().to_lowercase())); - Ok(match select(exists(filtered_list)).get_result(conn) { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }) -} - -/// True if there is any client with the same eth address, wg key, or ip address already registered -pub fn client_conflict( - client: &ExitClientIdentity, - conn: &PgConnection, -) -> Result> { - use self::schema::clients::dsl::*; - // we can't possibly have a conflict if we have exactly this client already - // since client exists checks all major details this is safe and will return false - // if it's not exactly the same client - if client_exists(client, conn)? { - return Ok(false); - } - trace!("Checking if client exists"); - let ip = client.global.mesh_ip; - let wg = client.global.wg_public_key; - let key = client.global.eth_address; - let ip_match = clients.filter(mesh_ip.eq(ip.to_string())); - let wg_key_match = clients.filter(wg_pubkey.eq(wg.to_string())); - let eth_address_match = clients.filter(eth_address.eq(key.to_string().to_lowercase())); - - let ip_exists = match select(exists(ip_match)).get_result(conn) { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }; - let wg_exists = match select(exists(wg_key_match)).get_result(conn) { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }; - let eth_exists = match select(exists(eth_address_match)).get_result(conn) { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }; - - info!( - "Signup conflict ip {} eth {} wg {}", - ip_exists, eth_exists, wg_exists - ); - Ok(ip_exists || eth_exists || wg_exists) -} - -/// Delete a client from the Clients database. Retrieve the reclaimed subnet index and add it to -/// available_subnets in assigned_ips database -pub fn delete_client( - client: ExitClient, - connection: &PgConnection, -) -> Result<(), Box> { - use self::schema::assigned_ips::dsl::{assigned_ips, subnet}; - use self::schema::clients::dsl::*; - info!("Deleting clients {:?} in database", client); - - let mesh_ip_string = client.mesh_ip.to_string(); - let statement = clients.find(&mesh_ip_string); - - // Add the reclaimed subnet to available subnets - let filtered_list = clients - .select(internet_ipv6) - .filter(mesh_ip.eq(&mesh_ip_string)); - let mut client_sub = match filtered_list.load::(connection) { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }; - - let filtered_list = assigned_ips.select(subnet); - let exit_sub = match filtered_list.load::(connection) { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }; - - if let Some(client_sub) = client_sub.pop() { - if !client_sub.is_empty() { - let client_sub: Vec<&str> = client_sub.split(',').collect(); - info!( - "For reclaiming subnets, exit subs are: {:?} and client subs are {:?}", - exit_sub, client_sub - ); - reclaim_all_ip_subnets(client_sub, exit_sub, connection)?; - } - } - - if let Err(e) = delete(statement).execute(connection) { - return Err(Box::new(e.into())); - }; - Ok(()) -} - -/// Given a vector of client subnet and exit subnets, reclaim all client subnets into the given exit subnets -/// This relies on the fact that there are no overlapping subnets -/// For example, if client has Ip addrs : "fbad::1000/64,feee::1000/64" -/// The exit subnets in the cluster are fbad::/40, feee::/40, fd00::/40 -/// Then fbad::/40 would gain the subnet fbad::1000/64 and feee::/40 would gain the subnet feee::1000/64 as available subnets -/// when the client get deleted from the database. View the unit test below for more examples -fn reclaim_all_ip_subnets( - client_sub: Vec<&str>, - exit_sub: Vec, - conn: &PgConnection, -) -> Result<(), Box> { - use self::schema::assigned_ips::dsl::{assigned_ips, available_subnets, subnet}; - - for client_ip in client_sub { - for exit_ip in &exit_sub { - let c_net: IpNetwork = client_ip.parse().expect("Unable to parse client subnet"); - let e_net: IpNetwork = exit_ip.parse().expect("Unable to parse exit subnet"); - if e_net.contains(c_net.ip()) { - let index = generate_index_from_subnet(e_net, c_net)?; - info!("Reclaimed index is: {:?}", index); - - let filtered_list = assigned_ips.filter(subnet.eq(exit_ip)); - let res = filtered_list.load::(conn); - - match res { - Ok(mut a) => { - if a.len() > 1 { - error!("Received multiple assigned ip entries for a singular subnet! Error"); - } - let a_ip = match a.pop() { - Some(a) => a, - None => { - return Err(Box::new(RitaExitError::MiscStringError( - "Unable to retrive assigned ip database".to_string(), - ))) - } - }; - let mut avail_ips = a_ip.available_subnets; - if avail_ips.is_empty() { - avail_ips.push_str(&index.to_string()) - } else { - // if our index is '10', we need to append ",10" to the end - let mut new_str = ",".to_owned(); - new_str.push_str(&index.to_string()); - // If avail_ips does not contains '"," + "index"' and (avail ips has only one entry and does not contains 'index') - if !(avail_ips.contains(&new_str) - || (!avail_ips.contains(',') - && avail_ips.contains(&index.to_string()))) - { - avail_ips.push_str(&new_str); - } else { - error!("IPV6 ERROR: We tried adding {:?} to string {:?}, how did we get in this position?", index, avail_ips); - } - } - info!( - "We are updating database with reclaim string: {:?}", - avail_ips - ); - if let Err(e) = diesel::update(assigned_ips.find(exit_ip)) - .set(available_subnets.eq(avail_ips)) - .execute(conn) - { - return Err(Box::new(e.into())); - }; - } - Err(e) => { - error!( - "unable to add a reclaimed ip to database with error: {:?}", - e - ); - } - } - - // After we reclaim an index, we break from the loop. The prevents duplicate reclaiming when two instances have the same subnet - break; - } - } - } - - Ok(()) -} - -// for backwards compatibility with entires that do not have a timestamp -// new entires will be initialized and updated as part of the normal flow -pub fn set_client_timestamp( - client: ExitClient, - connection: &PgConnection, -) -> Result<(), Box> { - use self::schema::clients::dsl::*; - info!("Setting timestamp for client {:?}", client); - - if let Err(e) = diesel::update(clients.find(&client.mesh_ip.to_string())) - .set(last_seen.eq(secs_since_unix_epoch())) - .execute(connection) - { - return Err(Box::new(e.into())); - }; - Ok(()) -} - -// we match on email not key? that has interesting implications for -// shared emails -pub fn update_mail_sent_time( - client: &ExitClientIdentity, - conn: &PgConnection, -) -> Result<(), Box> { - use self::schema::clients::dsl::{clients, email, email_sent_time}; - let mail_addr = match client.clone().reg_details.email { - Some(mail) => mail, - None => { - return Err(Box::new(RitaExitError::EmailNotFound(Box::new( - client.clone(), - )))) - } - }; - - if let Err(e) = diesel::update(clients.filter(email.eq(mail_addr))) - .set(email_sent_time.eq(secs_since_unix_epoch())) - .execute(conn) - { - return Err(Box::new(e.into())); - }; - - Ok(()) -} - -/// Gets the Postgres database connection from the threadpool, since there are dedicated -/// connections for each threadpool member error if non is available right away -pub fn get_database_connection( -) -> Result>, Box> { - match get_db_pool() { - Some(connection) => Ok(connection), - None => { - error!("No available db connection!"); - Err(Box::new(RitaExitError::MiscStringError( - "No Database connection available!".to_string(), - ))) - } - } -} - -pub fn create_or_update_user_record( - conn: &PgConnection, - client: &ExitClientIdentity, - user_country: String, -) -> Result> { - use self::schema::clients::dsl::clients; - - // Retrieve exit subnet - let rita_exit = settings::get_rita_exit(); - let exit_settings = rita_exit.exit_network; - let subnet = exit_settings.subnet; - - // If subnet isnt already present in database, create it - let mut subnet_entry = None; - if let Some(subnet) = subnet { - subnet_entry = Some(initialize_subnet_datastore(subnet, conn)?); - info!("Subnet Database entry: {:?}", subnet_entry); - } - - if let Some(val) = get_client(client, conn)? { - // Give ipv6 if not present - if let Some(subnet) = subnet { - assign_ip_to_client(val.mesh_ip.clone(), subnet, conn)?; - } - update_client(client, &val, conn)?; - Ok(val) - } else { - info!( - "record for {} does not exist, creating", - client.global.wg_public_key - ); - - let new_ip = get_next_client_ip(conn)?; - - let internet_ip = if let (Some(subnet), Some(subnet_entry)) = (subnet, subnet_entry) { - Some(get_client_subnet(subnet, subnet_entry, conn)?) - } else { - None - }; - - let c = client_to_new_db_client(client, new_ip, user_country, internet_ip); - - info!("Inserting new client {}", client.global.wg_public_key); - if let Err(e) = diesel::insert_into(clients).values(&c).execute(conn) { - return Err(Box::new(e.into())); - } - - Ok(c) - } -} - -/// This function creates an entry for the given subnet in the assgined_ips table if doesnt exist -fn initialize_subnet_datastore( - sub: IpNetwork, - conn: &PgConnection, -) -> Result> { - use self::schema::assigned_ips::dsl::{assigned_ips, subnet}; - let filtered_list = assigned_ips.filter(subnet.eq(sub.to_string())); - match filtered_list.load::(conn) { - Err(_) => { - // When there is no entry create an entry in the database - let record = AssignedIps { - subnet: sub.to_string(), - available_subnets: "".to_string(), - iterative_index: 0, - }; - if let Err(e) = diesel::insert_into(assigned_ips) - .values(&record) - .execute(conn) - { - return Err(Box::new(e.into())); - }; - Ok(record) - } - Ok(mut a) => { - // There is an entry in the database. If the entry is just an empty vector, create a new entry - // else, pop the vector (should be only 1 entry) and return it - if a.len() > 1 { - error!("More than one entry for singular subnet in database, please fix"); - } else if a.is_empty() { - let record = AssignedIps { - subnet: sub.to_string(), - available_subnets: "".to_string(), - iterative_index: 0, - }; - info!("Received an empty vector, adding new subnet entry"); - if let Err(e) = diesel::insert_into(assigned_ips) - .values(&record) - .execute(conn) - { - return Err(Box::new(e.into())); - }; - return Ok(record); - } - Ok(a.pop().unwrap()) - } - } -} - -/// This function finds an available ipv6 subnet for a client that connects. It works as follows: -/// 1.) Take assigned subnet and client configured subnet length (currently hardcoded to CLIENT_SUBNET_LENGTH) -/// 2.) Retreive all active subnets from database -/// 3.) Retrieve assigned_ips struct from database table for given exit subnet. Check for any available subnet index -/// 4.) If not get the subnets next iterative index instead -/// 5.) Use this index to retrieve the 'ith' iterative subnet in the larger subnet -/// Max Iterative index should theoretically never be reached because we choose subnets from deleted clients before generating -/// an iterative subnet -pub fn get_client_subnet( - sub: IpNetwork, - ip_tracker: AssignedIps, - conn: &PgConnection, -) -> Result> { - use self::schema::assigned_ips::dsl::{assigned_ips, available_subnets, iterative_index}; - use self::schema::clients::dsl::{clients, internet_ipv6}; - // Get our assigned subnet - info!("Received Exit assigned ipv6 subnet: {}", sub); - - // Make sql query to get list of all client subnets in use - // SELECT internet_ipv6 FROM - let filtered_list = clients.select(internet_ipv6); - let ip_list = match filtered_list.load::(conn) { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }; - - let mut index: Option = None; - - // First check for any available subnets to reclaim - if !ip_tracker.available_subnets.is_empty() { - // available ips are stored in the form of "1,2,5" etc - if let Some((remaining, i)) = ip_tracker.available_subnets.rsplit_once(',') { - // set database available subnets to remainging - if let Err(e) = diesel::update(assigned_ips.find(sub.to_string())) - .set(available_subnets.eq(remaining)) - .execute(conn) - { - return Err(Box::new(e.into())); - }; - - index = match i.parse() { - Ok(a) => Some(a), - Err(e) => { - return Err(Box::new(RitaExitError::MiscStringError(format!( - "Unable to assign user ipv6 subnet when parsing latest index: {e}" - )))); - } - } - } else { - // This is case of singular entry in, for exmaple "2" - // set database available subnets to "" - if let Err(e) = diesel::update(assigned_ips.find(sub.to_string())) - .set(available_subnets.eq("")) - .execute(conn) - { - return Err(Box::new(e.into())); - }; - - index = match ip_tracker.available_subnets.parse() { - Ok(a) => Some(a), - Err(e) => { - return Err(Box::new(RitaExitError::MiscStringError(format!( - "Unable to assign user ipv6 subnet: {e}" - )))); - } - } - } - } - - let mut used_iterative_index = false; - // Next get iterative index to generate next subnet. If index is not already set from available subnets, set it here from database - if index.is_none() { - used_iterative_index = true; - index = Some(match ip_tracker.iterative_index.try_into() { - Ok(a) => a, - Err(e) => { - return Err(Box::new(RitaExitError::MiscStringError(format!( - "Unable to assign user ipv6 subnet when parsing iterative index: {e}" - )))); - } - }); - } - - // Once we get the index, generate the subnet - match generate_iterative_client_subnet( - sub, - index.unwrap(), - settings::get_rita_exit() - .get_client_subnet_size() - .unwrap_or(DEFAULT_CLIENT_SUBNET_SIZE), - ) { - Ok(addr) => { - // increment iterative index - if used_iterative_index { - let new_ind = (index.unwrap() + 1) as i64; - if let Err(e) = diesel::update(assigned_ips.find(sub.to_string())) - .set(iterative_index.eq(new_ind)) - .execute(conn) - { - return Err(Box::new(e.into())); - }; - } - - // ip_list is a vector of a list of ipaddrs, so we check each ipaddr to see if it is already used - if !ip_list - .iter() - .any(|ipv6_list| ipv6_list.contains(&addr.to_string())) - { - Ok(addr) - } else { - error!("Chosen subnet: {:?} is in use! Race condition hit", addr); - Err(Box::new(RitaExitError::MiscStringError(format!( - "Unable to assign user ipv6 subnet. Chosen subnet {addr:?} is in use" - )))) - } - } - Err(e) => { - error!( - "Unable to retrieve an available ipv6 subnet for client: {}", - e - ); - Err(e) - } - } -} +pub const DEFAULT_CLIENT_SUBNET_SIZE: u8 = 56; /// Take an index i, a larger subnet and a smaller subnet length and generate the ith smaller subnet in the larger subnet /// For instance, if our larger subnet is fd00::1330/120, smaller sub len is 124, and index is 1, our generated subnet would be fd00::1310/124 -fn generate_iterative_client_subnet( +pub fn generate_iterative_client_subnet( exit_sub: IpNetwork, ind: u64, subprefix: u8, @@ -784,152 +63,16 @@ fn generate_iterative_client_subnet( } } -/// This function takes a larger subnet and a smaller subnet and generates an iterative index of the smaller -/// subnet within the larger subnet -/// For exmaple fd00::1020/124 is the 3rd subnet in fd00::1000/120, so it generates the index '2' -fn generate_index_from_subnet( - exit_sub: IpNetwork, - sub: IpNetwork, -) -> Result> { - if exit_sub.size() < sub.size() { - error!("Invalid subnet sizes"); - return Err(Box::new(RitaExitError::MiscStringError( - "Invalid subnet sizes provided to generate_index_from_subnet".to_string(), - ))); - } - - let size: u128 = if let NetworkSize::V6(a) = sub.size() { - a - } else { - return Err(Box::new(RitaExitError::MiscStringError( - "Exit Subnet needs to be ipv6!!".to_string(), - ))); - }; - let exit_sub_int: u128 = if let IpAddr::V6(addr) = exit_sub.ip() { - addr.into() - } else { - return Err(Box::new(RitaExitError::MiscStringError( - "Exit Subnet needs to be ipv6!!".to_string(), - ))); - }; - - let sub_int: u128 = if let IpAddr::V6(addr) = sub.ip() { - addr.into() - } else { - return Err(Box::new(RitaExitError::MiscStringError( - "Exit Subnet needs to be ipv6!!".to_string(), - ))); - }; - - let ret: u128 = (sub_int - exit_sub_int) / size; - - Ok(ret as u64) +pub fn get_all_regsitered_clients() -> Vec { + unimplemented!() } -/// This function updates the clients database with an added entry in the internet ipv6 field -/// that stores client ipv6 addrs. ipv6 addrs are stored in the form of "fd00:1330/64,fde0::1100/40" etc -/// with a comma being the delimiter -fn assign_ip_to_client( - client_mesh_ip: String, - exit_sub: IpNetwork, - conn: &PgConnection, -) -> Result> { - // check if ipv6 list already has an ip in its subnet - use self::schema::clients::dsl::{clients, internet_ipv6, mesh_ip}; - - let filtered_list = clients - .select(internet_ipv6) - .filter(mesh_ip.eq(&client_mesh_ip)); - let mut sub = match filtered_list.load::(conn) { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }; - - let client_ipv6_list = sub.pop(); - - if let Some(mut list_str) = client_ipv6_list { - if !list_str.is_empty() { - let list: Vec<&str> = list_str.split(',').collect(); - for ipv6_str in list { - let ipv6_sub: IpNetwork = - ipv6_str.parse().expect("Unable to parse ipnetwork subnet"); - // Since there are no overlapping subnets, If the ip is in the subnet, so is the ip subnet - if exit_sub.contains(ipv6_sub.ip()) { - return Ok(ipv6_sub); - } - } - // If code hasnt returned yet, we need to add the ip to the list - let subnet_entry = initialize_subnet_datastore(exit_sub, conn)?; - let internet_ip = get_client_subnet(exit_sub, subnet_entry.clone(), conn)?; - let mut new_str = ",".to_owned(); - new_str.push_str(&internet_ip.to_string()); - list_str.push_str(&new_str); - info!("Initializing ipv6 addrs for existing clients, IP: {}, is given ip {:?}, subnet entry is {:?}", client_mesh_ip, list_str.clone(), subnet_entry); - if let Err(e) = diesel::update(clients.find(client_mesh_ip)) - .set(internet_ipv6.eq(list_str)) - .execute(conn) - { - return Err(Box::new(e.into())); - }; - Ok(internet_ip) - } else { - // List is empty - let subnet_entry = initialize_subnet_datastore(exit_sub, conn)?; - let internet_ip = get_client_subnet(exit_sub, subnet_entry.clone(), conn)?; - info!("Initializing ipv6 addrs for existing clients, IP: {}, is given ip {:?}, subnet entry is {:?}", client_mesh_ip, internet_ip.clone(), subnet_entry); - if let Err(e) = diesel::update(clients.find(client_mesh_ip)) - .set(internet_ipv6.eq(internet_ip.to_string())) - .execute(conn) - { - return Err(Box::new(e.into())); - }; - Ok(internet_ip) - } - } else { - // The client doesnt not have an appropriate ipv6 addr for our subnet, assign it one - let subnet_entry = initialize_subnet_datastore(exit_sub, conn)?; - let internet_ip = get_client_subnet(exit_sub, subnet_entry.clone(), conn)?; - info!("Initializing ipv6 addrs for existing clients, IP: {}, is given ip {:?}, subnet entry is {:?}", client_mesh_ip, internet_ip.clone(), subnet_entry); - if let Err(e) = diesel::update(clients.find(client_mesh_ip)) - .set(internet_ipv6.eq(internet_ip.to_string())) - .execute(conn) - { - return Err(Box::new(e.into())); - }; - Ok(internet_ip) - } +pub fn get_registered_client_using_wgkey(_key: WgKey) -> Option { + unimplemented!() } -/// Given a database client entry, get ipnetwork string ("fd00::1337,f100:1400") find the correct ipv6 address to send back to client corresponding to our exit instance -pub fn get_client_ipv6( - their_record: &models::Client, -) -> Result, Box> { - let client_subs = &their_record.internet_ipv6; - let client_mesh_ip = &their_record.mesh_ip; - - let rita_exit = settings::get_rita_exit(); - let exit_settings = rita_exit.exit_network; - let exit_sub = exit_settings.subnet; - - if let Some(exit_sub) = exit_sub { - if !client_subs.is_empty() { - let c_sub: Vec<&str> = client_subs.split(',').collect(); - for sub in c_sub { - let c_net: IpNetwork = sub.parse().expect("Unable to parse client subnet"); - if exit_sub.contains(c_net.ip()) { - return Ok(Some(c_net)); - } - } - } - - // If no ip has been returned, an ip has not been setup, so we assign an ip in the database - let conn = get_database_connection()?; - let ip_net = assign_ip_to_client(client_mesh_ip.to_string(), exit_sub, &conn)?; - Ok(Some(ip_net)) - } else { - // This exit doesnt support ipv6 - Ok(None) - } +pub fn get_clients_exit_cluster_list(_key: WgKey) -> Vec { + unimplemented!() } #[cfg(test)] @@ -983,206 +126,4 @@ mod tests { let ret = generate_iterative_client_subnet(net, 16, 124); assert!(ret.is_err()); } - - #[test] - fn test_reclaiming_ips() { - let str = ""; - let str2 = "2"; - let str3 = "1,2,5,10,92"; - let vec = str.rsplit_once(','); - println!("{vec:?}"); - - let vec = str2.rsplit_once(','); - println!("{vec:?}"); - - let vec = str3.rsplit_once(','); - println!("{vec:?}"); - } - - #[test] - fn test_subnet_to_index() { - let sub: IpNetwork = "fd00::1000/124".parse().unwrap(); - - let net = sub.network(); - println!("net: {net:?}"); - let size = sub.size(); - println!("size: {size:?}"); - - let exit_sub: IpNetwork = "fd00::1000/120".parse().unwrap(); - let sub: IpNetwork = "fd00::1060/124".parse().unwrap(); - assert_eq!(generate_index_from_subnet(exit_sub, sub).unwrap(), 6); - - let exit_sub: IpNetwork = "fd00::1000/120".parse().unwrap(); - let sub: IpNetwork = "fd00::1060/128".parse().unwrap(); - assert_eq!(generate_index_from_subnet(exit_sub, sub).unwrap(), 96); - - let exit_sub: IpNetwork = "2602:FBAD::/40".parse().unwrap(); - let sub: IpNetwork = "2602:FBAD:0:32::/64".parse().unwrap(); - assert_eq!(generate_index_from_subnet(exit_sub, sub).unwrap(), 50); - } - - #[test] - fn test_assignment_ipv6_to_client_logic() { - // TEST CASE 1 - // let client_ipv6_list = Some("fbad::1330/64,fedd::1000/64".to_string()); - // let exit_sub: IpNetwork = "fbad::1330/40".parse().unwrap(); - - // TEST CASE 2 - let client_ipv6_list = Some("fbad::1330/64,fedd::1000/64".to_string()); - let exit_sub: IpNetwork = "feee::1330/40".parse().unwrap(); - - // TEST CASE 3 - // let client_ipv6_list = Some("".to_string()); - // let exit_sub: IpNetwork = "fbad::1330/40".parse().unwrap(); - - // TEST CASE 4 - // let client_ipv6_list: Option = None; - // let exit_sub: IpNetwork = "fbad::1330/40".parse().unwrap(); - - if let Some(mut list_str) = client_ipv6_list { - if !list_str.is_empty() { - let list: Vec<&str> = list_str.split(',').collect(); - println!("List looks like: {list:?}"); - for ipv6_str in list { - let ipv6_sub: IpNetwork = - ipv6_str.parse().expect("Unable to parse ipnetwork subnet"); - // Since there are no overlapping subnets, If the ip is in the subnet, so is the ip subnet - if exit_sub.contains(ipv6_sub.ip()) { - println!("Hit Test case 1"); - return; - } - } - // If code hasnt returned yet, we need to add the ip to the list - let internet_ip: IpNetwork = "feee::1000/64".parse().unwrap(); - let mut new_str = ",".to_owned(); - new_str.push_str(&internet_ip.to_string()); - list_str.push_str(&new_str); - println!("list_str looks like: {list_str:?}"); - println!("hit test case 2"); - } else { - // List is empty - println!("Hit Test case 3"); - } - } else { - // The client doesnt not have an appropriate ipv6 addr for our subnet, assign it one - println!("hit Test case 4"); - } - } - - #[test] - fn test_get_client_ipv6() { - let client_subs = "fbad::1000/64"; - let exit_sub: Option = Some("fbad::1000/40".parse().unwrap()); - assert_eq!( - get_client_ipv6_helper(client_subs.to_string(), exit_sub), - Some(client_subs.parse().unwrap()) - ); - - let client_subs = ""; - let exit_sub: Option = Some("fbad::1000/40".parse().unwrap()); - assert_eq!( - get_client_ipv6_helper(client_subs.to_string(), exit_sub), - Some("fbad::1000/64".parse().unwrap()) - ); - - let client_subs = "feee::1000/64"; - let exit_sub: Option = Some("fbad::1000/40".parse().unwrap()); - assert_eq!( - get_client_ipv6_helper(client_subs.to_string(), exit_sub), - Some("fbad::1000/64".parse().unwrap()) - ); - - let client_subs = "feee::1000/64,fbad::1000/64"; - let exit_sub: Option = Some("fbad::1000/40".parse().unwrap()); - assert_eq!( - get_client_ipv6_helper(client_subs.to_string(), exit_sub), - Some("fbad::1000/64".parse().unwrap()) - ); - } - - fn get_client_ipv6_helper( - client_subs: String, - exit_sub: Option, - ) -> Option { - if let Some(exit_sub) = exit_sub { - if !client_subs.is_empty() { - let c_sub: Vec<&str> = client_subs.split(',').collect(); - for sub in c_sub { - let c_net: IpNetwork = sub.parse().expect("Unable to parse client subnet"); - if exit_sub.contains(c_net.ip()) { - return Some(c_net); - } - } - } - // If no ip has been returned, an ip has not been setup, so we assign an ip in the database - let ip_net: IpNetwork = "fbad::1000/64".parse().unwrap(); - Some(ip_net) - } else { - // This exit doesnt support ipv6 - None - } - } - - #[test] - fn test_reclaim_all_subnets() { - //Case 1: no ipv6 instances, should panic - // let client_sub = vec![""]; - // let exit_sub = vec!["".to_string()]; - // reclaim_all_subnets_helper(client_sub, exit_sub); - - //Case 2: One ipv6 instance - let client_sub = vec!["fbad::1000/64"]; - let exit_sub = vec!["fbad::1000/40".to_string()]; - reclaim_all_subnets_helper(client_sub, exit_sub); - - //Case 3: Two ipv6 instances, same subnet (invalid case, there shouldnt be two client subs for 1 exit sub) - let client_sub = vec!["fbad::1000/64", "fbad::eeee/64"]; - let exit_sub = vec!["fbad::1000/40".to_string()]; - reclaim_all_subnets_helper(client_sub, exit_sub); - - //Case 3: Two ipv6 instances, same subnet (invalid case, no overlapping subnets) - let client_sub = vec!["fbad::1000/64"]; - let exit_sub = vec!["fbad::1000/40".to_string(), "fbad::1000/50".to_string()]; - reclaim_all_subnets_helper(client_sub, exit_sub); - - //Case 4: Two ipv6 instances, different subnet - let client_sub = vec!["fbad::1000/64", "feee::eeee/64"]; - let exit_sub = vec!["fbad::1000/40".to_string(), "feee::1000/40".to_string()]; - reclaim_all_subnets_helper(client_sub, exit_sub); - } - - fn reclaim_all_subnets_helper(client_sub: Vec<&str>, exit_sub: Vec) { - for client_ip in client_sub { - for exit_ip in &exit_sub { - let c_net: IpNetwork = client_ip.parse().expect("Unable to parse client subnet"); - let e_net: IpNetwork = exit_ip.parse().expect("Unable to parse exit subnet"); - if e_net.contains(c_net.ip()) { - println!("reclaiming client {c_net:?} to exit sub {e_net:?}"); - - // After we reclaim an index, we break from the loop. The prevents duplicate reclaiming when two instances have the same subnet - break; - } - } - } - } - - #[test] - fn test_playground() { - let a = generate_index_from_subnet( - "2000:fbad:10::/45".parse().unwrap(), - "2000:fbad:10:52f0::/60".parse().unwrap(), - ); - - println!("{a:?}"); - - let client_ipv6 = "2602:fbad:0:2340::/60,2602:fbad:10:2500::/60"; - let client_sub: Vec<&str> = client_ipv6.split(',').collect(); - println!("{client_sub:?}"); - let exit_sub = vec![ - "2602:fbad:10::/45".to_string(), - "2602:fbad::/45".to_string(), - ]; - - reclaim_all_subnets_helper(client_sub, exit_sub); - } } diff --git a/rita_exit/src/database/db_client.rs b/rita_exit/src/database/db_client.rs deleted file mode 100644 index a33995003..000000000 --- a/rita_exit/src/database/db_client.rs +++ /dev/null @@ -1,15 +0,0 @@ -use diesel::dsl::delete; -use diesel::*; -use exit_db::schema; - -use crate::database::database_tools::get_database_connection; -use crate::RitaExitError; - -pub fn truncate_db_tables() -> Result<(), Box> { - use self::schema::clients::dsl::*; - info!("Deleting all clients in database"); - let connection = get_database_connection()?; - (delete(clients).execute(&connection).unwrap()); - - Ok(()) -} diff --git a/rita_exit/src/database/email.rs b/rita_exit/src/database/email.rs deleted file mode 100644 index 6e8dc859f..000000000 --- a/rita_exit/src/database/email.rs +++ /dev/null @@ -1,137 +0,0 @@ -use crate::database::database_tools::update_mail_sent_time; -use crate::database::database_tools::verify_client; -use crate::database::get_exit_info; -use crate::database::secs_since_unix_epoch; -use crate::database::struct_tools::verif_done; -use crate::get_client_ipv6; -use crate::RitaExitError; - -use althea_types::{ExitClientDetails, ExitClientIdentity, ExitState}; -use diesel::prelude::PgConnection; -use exit_db::models; -use handlebars::Handlebars; -use lettre::transport::smtp::authentication::Credentials; -use lettre::transport::smtp::authentication::Mechanism; -use lettre::transport::smtp::extension::ClientId; -use lettre::transport::smtp::PoolConfig; -use lettre::FileTransport; -use lettre::{Message, SmtpTransport, Transport}; -use serde_json::json; -use settings::exit::ExitVerifSettings; - -pub fn send_mail(client: &models::Client) -> Result<(), Box> { - let mailer = match settings::get_rita_exit().verif_settings { - Some(ExitVerifSettings::Email(mailer)) => mailer, - Some(_) => { - return Err(Box::new(RitaExitError::MiscStringError( - "Verification mode is not email!".to_string(), - ))) - } - None => { - return Err(Box::new(RitaExitError::MiscStringError( - "No verification mode configured!".to_string(), - ))) - } - }; - - info!("Sending exit signup email for client"); - - let reg = Handlebars::new(); - - let email = match Message::builder() - .to(client.email.clone().parse().unwrap()) - .from(mailer.from_address.parse().unwrap()) - .subject(mailer.signup_subject) - // TODO: maybe have a proper templating engine - .body( - match reg.render_template( - &mailer.signup_body, - &json!({"email_code": client.email_code.to_string()}), - ) { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }, - ) { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }; - - if mailer.test { - let mailer = FileTransport::new(&mailer.test_dir); - if let Err(e) = mailer.send(&email) { - return Err(Box::new(e.into())); - }; - } else { - let mailer = match SmtpTransport::relay(&mailer.smtp_url) { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - } - .hello_name(ClientId::Domain(mailer.smtp_domain)) - .credentials(Credentials::new(mailer.smtp_username, mailer.smtp_password)) - .authentication(vec![Mechanism::Plain]) - .pool_config(PoolConfig::new().max_size(20)) - .build(); - if let Err(e) = mailer.send(&email) { - return Err(Box::new(e.into())); - }; - } - - Ok(()) -} - -/// handles the minutia of emails and cooldowns -pub fn handle_email_registration( - client: &ExitClientIdentity, - their_record: &exit_db::models::Client, - conn: &PgConnection, - cooldown: i64, -) -> Result> { - let mut their_record = their_record.clone(); - if client.reg_details.email_code == Some(their_record.email_code.clone()) { - info!("email verification complete for {:?}", client); - - verify_client(client, true, conn)?; - their_record.verified = true; - } - - if verif_done(&their_record) { - info!("{:?} is now registered", client); - - let client_internal_ip = match their_record.internal_ip.parse() { - Ok(ip) => ip, - Err(e) => return Err(Box::new(RitaExitError::AddrParseError(e))), - }; - let client_internet_ipv6_subnet = get_client_ipv6(&their_record)?; - Ok(ExitState::Registered { - our_details: ExitClientDetails { - client_internal_ip, - internet_ipv6_subnet: client_internet_ipv6_subnet, - }, - general_details: get_exit_info(), - message: "Registration OK".to_string(), - }) - } else { - let time_since_last_email = secs_since_unix_epoch() - their_record.email_sent_time; - - if time_since_last_email < cooldown { - Ok(ExitState::GotInfo { - general_details: get_exit_info(), - message: format!( - "Wait {} more seconds for verification cooldown", - cooldown - time_since_last_email - ), - }) - } else { - update_mail_sent_time(client, conn)?; - - send_mail(&their_record)?; - - Ok(ExitState::Pending { - general_details: get_exit_info(), - message: "awaiting email verification".to_string(), - email_code: None, - phone_code: None, - }) - } - } -} diff --git a/rita_exit/src/database/mod.rs b/rita_exit/src/database/mod.rs index ec92007d4..14c1c27dc 100644 --- a/rita_exit/src/database/mod.rs +++ b/rita_exit/src/database/mod.rs @@ -1,60 +1,53 @@ //! This module contains all the tools and functions that integrate with the clients database //! for the exit, which is most exit logic in general. Keep in mind database connections are remote //! and therefore synchronous database requests are quite expensive (on the order of tens of milliseconds) - -use crate::create_or_update_user_record; -use crate::database::database_tools::client_conflict; -use crate::database::database_tools::delete_client; -use crate::database::database_tools::get_client; -use crate::database::database_tools::get_database_connection; -use crate::database::database_tools::set_client_timestamp; -use crate::database::database_tools::update_client; -use crate::database::database_tools::verify_client; -use crate::database::database_tools::verify_db_client; -use crate::database::email::handle_email_registration; -use crate::database::geoip::get_country; use crate::database::geoip::get_gateway_ip_bulk; use crate::database::geoip::get_gateway_ip_single; use crate::database::geoip::verify_ip; -use crate::database::sms::handle_sms_registration; use crate::database::struct_tools::display_hashset; +use crate::database::struct_tools::get_client_internal_ip; +use crate::database::struct_tools::get_client_ipv6; use crate::database::struct_tools::to_exit_client; -use crate::database::struct_tools::to_identity; -use crate::database::struct_tools::verif_done; -use crate::get_client_ipv6; +use crate::get_registered_client_using_wgkey; use crate::rita_loop::EXIT_INTERFACE; use crate::rita_loop::EXIT_LOOP_TIMEOUT; use crate::rita_loop::LEGACY_INTERFACE; use crate::RitaExitError; +use crate::DEFAULT_CLIENT_SUBNET_SIZE; use althea_kernel_interface::ExitClient; use althea_types::Identity; use althea_types::WgKey; use althea_types::{ExitClientDetails, ExitClientIdentity, ExitDetails, ExitState, ExitVerifMode}; -use diesel::prelude::PgConnection; -use exit_db::models::Client; use rita_common::blockchain_oracle::calculate_close_thresh; use rita_common::debt_keeper::get_debts_list; use rita_common::debt_keeper::DebtAction; -use rita_common::utils::secs_since_unix_epoch; use rita_common::KI; -use settings::exit::ExitVerifSettings; use settings::get_rita_exit; use std::collections::HashMap; use std::collections::HashSet; use std::net::IpAddr; +use std::time::Duration; use std::time::Instant; use std::time::SystemTime; pub mod database_tools; -pub mod db_client; -pub mod email; pub mod geoip; -pub mod sms; pub mod struct_tools; /// one day in seconds pub const ONE_DAY: i64 = 86400; +/// Timeout when requesting client registration +pub const CLIENT_REGISTER_TIMEOUT: Duration = Duration::from_secs(5); + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ExitSignupReturn { + RegistrationOk, + PendingRegistration, + BadPhoneNumber, + InternalServerError { e: String }, +} + pub fn get_exit_info() -> ExitDetails { let exit_settings = get_rita_exit(); ExitDetails { @@ -64,21 +57,14 @@ pub fn get_exit_info() -> ExitDetails { exit_currency: exit_settings.payment.system_chain, netmask: exit_settings.exit_network.netmask, description: exit_settings.description, - verif_mode: match exit_settings.verif_settings { - Some(ExitVerifSettings::Email(_mailer_settings)) => ExitVerifMode::Email, - Some(ExitVerifSettings::Phone(_phone_settings)) => ExitVerifMode::Phone, - None => ExitVerifMode::Off, - }, + verif_mode: ExitVerifMode::Phone, } } /// Handles a new client registration api call. Performs a geoip lookup /// on their registration ip to make sure that they are coming from a valid gateway /// ip and then sends out an email of phone message -pub async fn signup_client( - client: ExitClientIdentity, - from_ops: bool, -) -> Result> { +pub async fn signup_client(client: ExitClientIdentity) -> Result> { let exit_settings = get_rita_exit(); info!("got setup request {:?}", client); let gateway_ip = get_gateway_ip_single(client.global.mesh_ip)?; @@ -87,110 +73,120 @@ pub async fn signup_client( let verify_status = verify_ip(gateway_ip)?; info!("verified the ip country {:?}", client); - let user_country = get_country(gateway_ip)?; - info!("got the country {:?}", client); - - let conn = get_database_connection()?; - - info!( - "Doing database work for {:?} in country {} with verify_status {}", - client, user_country, verify_status - ); - // check if we have any users with conflicting details - - match client_conflict(&client, &conn) { - Ok(true) => { - return Ok(ExitState::Denied { - message: format!( - "Partially changed registration details! Please reset your router and re-register with all new details. Backup your key first! {}", - display_hashset(&exit_settings.allowed_countries), - ), - }) - }, - Ok(false) => {} - Err(e) => return Err(e), + // Is client requesting from a valid country? If so send registration request to ops + if !verify_status { + return Ok(ExitState::Denied { + message: format!( + "This exit only accepts connections from {}", + display_hashset(&exit_settings.allowed_countries), + ), + }); } - let their_record = create_or_update_user_record(&conn, &client, user_country)?; - - // either update and grab an existing entry or create one - match (verify_status, exit_settings.verif_settings, from_ops) { - (true, _, true) => { - verify_client(&client, true, &conn)?; - let client_internal_ip = match their_record.internal_ip.parse() { - Ok(ip) => ip, - Err(e) => return Err(Box::new(RitaExitError::AddrParseError(e))), - }; - let client_internet_ipv6_subnet = get_client_ipv6(&their_record)?; - Ok(ExitState::Registered { + // Forward request to ops and send result to client accordingly + let exit_client = to_exit_client(client.global); + if let Ok(exit_client) = exit_client { + match forward_client_signup_request(client).await { + ExitSignupReturn::RegistrationOk => Ok(ExitState::Registered { our_details: ExitClientDetails { - client_internal_ip, - internet_ipv6_subnet: client_internet_ipv6_subnet, + client_internal_ip: exit_client.internal_ip, + internet_ipv6_subnet: exit_client.internet_ipv6, }, general_details: get_exit_info(), message: "Registration OK".to_string(), - }) - } + }), - (true, None, false) => { - verify_client(&client, true, &conn)?; - let client_internal_ip = match their_record.internal_ip.parse() { - Ok(ip) => ip, - Err(e) => return Err(Box::new(RitaExitError::AddrParseError(e))), - }; - let client_internet_ipv6_subnet = get_client_ipv6(&their_record)?; - Ok(ExitState::Registered { - our_details: ExitClientDetails { - client_internal_ip, - internet_ipv6_subnet: client_internet_ipv6_subnet, - }, + ExitSignupReturn::PendingRegistration => Ok(ExitState::Pending { general_details: get_exit_info(), - message: "Registration OK".to_string(), - }) - } - (true, Some(ExitVerifSettings::Email(mailer)), false) => { - handle_email_registration(&client, &their_record, &conn, mailer.email_cooldown as i64) - } - (true, Some(ExitVerifSettings::Phone(phone)), false) => { - handle_sms_registration(client, their_record, phone.auth_api_key).await + message: "awaiting email verification".to_string(), + email_code: None, + phone_code: None, + }), + ExitSignupReturn::BadPhoneNumber => Ok(ExitState::Denied { + message: format!( + "Error parsing client phone number {:?}", + exit_client.public_key, + ), + }), + ExitSignupReturn::InternalServerError { e } => Ok(ExitState::Denied { + message: format!("Internal Error from registration server {:?}", e,), + }), } - - (false, _, _) => Ok(ExitState::Denied { - message: format!( - "This exit only accepts connections from {}", - display_hashset(&exit_settings.allowed_countries), - ), - }), + } else { + Ok(ExitState::Denied { + message: format!("Error parsing client details with {:?}", exit_client,), + }) } } -/// Gets the status of a client and updates it in the database -pub fn client_status( - client: ExitClientIdentity, - conn: &PgConnection, -) -> Result> { - trace!("Checking if record exists for {:?}", client.global.mesh_ip); +pub async fn forward_client_signup_request(exit_client: ExitClientIdentity) -> ExitSignupReturn { + let url: &str; + let reg_url = get_rita_exit().client_registration_url; + if cfg!(feature = "dev_env") { + url = "http://0.0.0.0:8080/register_router"; + } else if cfg!(feature = "operator_debug") { + url = "http://192.168.10.2:8080/register_router"; + } else { + url = ®_url; + } - if let Some(their_record) = get_client(&client, conn)? { - trace!("record exists, updating"); + info!( + "About to request client {} registration with {}", + exit_client.global, url + ); - if !verif_done(&their_record) { - return Ok(ExitState::Pending { - general_details: get_exit_info(), - message: "awaiting email verification".to_string(), - email_code: None, - phone_code: None, - }); + let client = awc::Client::default(); + let response = client + .post(url) + .timeout(CLIENT_REGISTER_TIMEOUT) + .send_json(&exit_client) + .await; + + let response = match response { + Ok(mut response) => { + trace!("Response is {:?}", response.status()); + trace!("Response is {:?}", response.headers()); + response.json().await } + Err(e) => { + error!("Failed to perform client registration with {:?}", e); + return ExitSignupReturn::InternalServerError { + e: format!("Unable to contact registration server: {}", e), + }; + } + }; + + let response: ExitSignupReturn = match response { + Ok(a) => a, + Err(e) => { + error!("Failed to decode registration request {:?}", e); + return ExitSignupReturn::InternalServerError { + e: format!("Failed to decode registration request {:?}", e), + }; + } + }; + response +} - let current_ip: IpAddr = match their_record.internal_ip.parse() { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }; +/// Gets the status of a client and updates it in the database +pub fn client_status(client: ExitClientIdentity) -> Result> { + trace!("Checking if record exists for {:?}", client.global.mesh_ip); - let current_internet_ipv6 = get_client_ipv6(&their_record)?; + if let Some(their_record) = get_registered_client_using_wgkey(client.global.wg_public_key) { + trace!("record exists, updating"); - update_client(&client, &their_record, conn)?; + let current_ip: IpAddr = get_client_internal_ip( + their_record, + get_rita_exit().exit_network.netmask, + get_rita_exit().exit_network.own_internal_ip, + )?; + let current_internet_ipv6 = get_client_ipv6( + their_record, + settings::get_rita_exit().exit_network.subnet, + settings::get_rita_exit() + .get_client_subnet_size() + .unwrap_or(DEFAULT_CLIENT_SUBNET_SIZE), + )?; Ok(ExitState::Registered { our_details: ExitClientDetails { @@ -209,28 +205,19 @@ pub fn client_status( /// we also do this in the client status requests but we want to handle the edge case of a modified /// client that doesn't make status requests pub fn validate_clients_region( - clients_list: Vec, - conn: &PgConnection, -) -> Result<(), Box> { + clients_list: Vec, +) -> Result, Box> { info!("Starting exit region validation"); let start = Instant::now(); + let mut blacklist = Vec::new(); + trace!("Got clients list {:?}", clients_list); let mut ip_vec = Vec::new(); let mut client_map = HashMap::new(); for item in clients_list { - // there's no need to check clients that aren't verified - // as they are never setup - if !item.verified { - continue; - } - match item.mesh_ip.parse() { - Ok(ip) => { - client_map.insert(ip, item); - ip_vec.push(ip); - } - Err(_e) => error!("Database entry with invalid mesh ip! {:?}", item), - } + client_map.insert(item.mesh_ip, item); + ip_vec.push(item.mesh_ip); } let list = get_gateway_ip_bulk(ip_vec, EXIT_LOOP_TIMEOUT)?; for item in list.iter() { @@ -240,14 +227,12 @@ pub fn validate_clients_region( Ok(false) => { info!( "Found unauthorized client already registered {}, removing", - client_map[&item.mesh_ip].wg_pubkey + client_map[&item.mesh_ip].wg_public_key ); // get_gateway_ip_bulk can't add new entires to the list // therefore client_map is strictly a superset of ip_bulk results let client_to_deauth = &client_map[&item.mesh_ip]; - if verify_db_client(client_to_deauth, false, conn).is_err() { - error!("Failed to deauth client {:?}", client_to_deauth); - } + blacklist.push(*client_to_deauth); } Err(e) => warn!("Failed to verify ip with {:?}", e), } @@ -258,65 +243,7 @@ pub fn validate_clients_region( start.elapsed().as_secs(), start.elapsed().subsec_millis(), ); - Ok(()) -} - -/// Iterates over the the database of clients, if a client's last_seen value -/// is zero it is set to now if a clients last_seen value is older than -/// the client timeout it is deleted -pub fn cleanup_exit_clients( - clients_list: &[exit_db::models::Client], - conn: &PgConnection, -) -> Result<(), Box> { - trace!("Running exit client cleanup"); - let start = Instant::now(); - - for client in clients_list.iter() { - trace!("Checking client {:?}", client); - match to_exit_client(client.clone()) { - Ok(client_id) => { - let time_delta = secs_since_unix_epoch() - client.last_seen; - let entry_timeout = i64::from(settings::get_rita_exit().exit_network.entry_timeout); - // entry timeout can be disabled, or longer than a day, but not shorter - assert!(entry_timeout == 0 || entry_timeout >= ONE_DAY); - if client.last_seen == 0 { - info!( - "{} does not have a last seen timestamp, adding one now ", - client.wg_pubkey - ); - let res = set_client_timestamp(client_id, conn); - if res.is_err() { - warn!( - "Unable to update the client timestamp for {} with {:?}", - client.wg_pubkey, res - ); - } - } - // a entry_timeout value of 0 means the feature is disabled - else if entry_timeout != 0 && time_delta > entry_timeout { - warn!( - "{} has been inactive for too long, deleting! ", - client.wg_pubkey - ); - let res = delete_client(client_id, conn); - if res.is_err() { - error!( - "Unable to remove inactive client {:?} with {:?}", - client, res - ) - } - } - } - Err(e) => error!("Invalid database entry! {:?}", e), - } - } - - info!( - "Exit cleanup completed in {}s {}ms", - start.elapsed().as_secs(), - start.elapsed().subsec_millis(), - ); - Ok(()) + Ok(blacklist) } #[derive(Default, Clone, Serialize, Deserialize, Debug)] @@ -344,7 +271,8 @@ pub struct CurrentExitClientState { /// wg_exit tunnel (or created if it's the first run). This is the offically supported /// way to update live WireGuard tunnels and should not disrupt traffic pub fn setup_clients( - clients_list: &[exit_db::models::Client], + clients_list: Vec, + geoip_blacklist: Vec, client_states: ExitClientSetupStates, ) -> Result> { let mut client_states = client_states; @@ -352,7 +280,8 @@ pub fn setup_clients( // use hashset to ensure uniqueness and check for duplicate db entries let mut wg_clients = HashSet::new(); - let mut key_to_client_map: HashMap = HashMap::new(); + let mut geoip_blacklist_map = HashSet::new(); + let key_to_client_map: HashMap = HashMap::new(); trace!( "got clients from db {:?} {:?}", @@ -361,19 +290,46 @@ pub fn setup_clients( ); for c in clients_list.iter() { - match (c.verified, to_exit_client(c.clone())) { - (true, Ok(exit_client_c)) => { - if !wg_clients.insert(exit_client_c.clone()) { - error!("Duplicate database entry! {}", c.wg_pubkey); + match to_exit_client(*c) { + Ok(a) => { + if !wg_clients.insert(a) { + error!("Duplicate database entry! {}", c.wg_public_key); + } + } + Err(e) => { + error!( + "Unable to convert client to ExitClient! {} with error {}", + c.wg_public_key, e + ); + } + } + //key_to_client_map.insert(c.wg_public_key, c.clone()); + } + + for c in geoip_blacklist.iter() { + match to_exit_client(*c) { + Ok(a) => { + if !geoip_blacklist_map.insert(a) { + error!("Duplicate database entry! {}", c.wg_public_key); } - key_to_client_map.insert(exit_client_c.public_key, c.clone()); } - (true, Err(e)) => warn!("Error converting {:?} to exit client {:?}", c, e), - (false, _) => trace!("{:?} is not verified, not adding to wg_exit", c), + Err(e) => { + error!( + "Unable to convert client to ExitClient! {} with error {}", + c.wg_public_key, e + ); + } } } trace!("converted clients {:?}", wg_clients); + + // remove geoip blacklisted clients from wg clients + let wg_clients: HashSet = wg_clients + .difference(&geoip_blacklist_map) + .copied() + .collect(); + // symetric difference is an iterator of all items in A but not in B // or in B but not in A, in short if there's any difference between the two // it must be nonzero, since all entires must be unique there can not be duplicates @@ -448,7 +404,7 @@ pub fn setup_clients( .into_iter() .collect(); - let client_list_for_setup: Vec = key_to_client_map + let client_list_for_setup: Vec = key_to_client_map .clone() .into_iter() .filter_map(|(k, v)| { @@ -470,7 +426,7 @@ pub fn setup_clients( client_states.clone(), new_wg_exit_clients_timestamps, wg_exit_clients_timestamps, - &client_list_for_setup, + client_list_for_setup, ); // set previous tick states to current clients on wg interfaces @@ -483,7 +439,20 @@ pub fn setup_clients( for c_key in changed_clients_return.new_v1 { if let Some(c) = key_to_client_map.get(&c_key) { KI.setup_individual_client_routes( - c.internal_ip.parse().expect("Invalid ipv4 in the db!"), + match get_client_internal_ip( + *c, + get_rita_exit().exit_network.netmask, + get_rita_exit().exit_network.own_internal_ip, + ) { + Ok(a) => a, + Err(e) => { + error!( + "Received error while trying to retrieve client internal ip {}", + e + ); + continue; + } + }, internal_ip_v4.into(), LEGACY_INTERFACE, ); @@ -492,7 +461,20 @@ pub fn setup_clients( for c_key in changed_clients_return.new_v2 { if let Some(c) = key_to_client_map.get(&c_key) { KI.teardown_individual_client_routes( - c.internal_ip.parse().expect("Invalid ipv4 in the db!"), + match get_client_internal_ip( + *c, + get_rita_exit().exit_network.netmask, + get_rita_exit().exit_network.own_internal_ip, + ) { + Ok(a) => a, + Err(e) => { + error!( + "Received error while trying to retrieve client internal ip {}", + e + ); + continue; + } + }, ); } } @@ -506,7 +488,7 @@ fn find_changed_clients( client_states: ExitClientSetupStates, all_v2: HashMap, all_v1: HashMap, - clients_list: &[exit_db::models::Client], + clients_list: Vec, ) -> CurrentExitClientState { let mut v1_clients = HashSet::new(); @@ -517,15 +499,9 @@ fn find_changed_clients( match get_client_interface(c, all_v2.clone(), all_v1.clone()) { Ok(interface) => { if interface == ClientInterfaceType::LegacyInterface { - v1_clients.insert(match c.wg_pubkey.parse() { - Ok(a) => a, - Err(_) => continue, - }); + v1_clients.insert(c.wg_public_key); } else if interface == ClientInterfaceType::ExitInterface { - v2_clients.insert(match c.wg_pubkey.parse() { - Ok(a) => a, - Err(_) => continue, - }); + v2_clients.insert(c.wg_public_key); } } Err(_) => { @@ -555,7 +531,7 @@ pub enum ClientInterfaceType { } pub fn get_client_interface( - c: &exit_db::models::Client, + c: Identity, new_wg_exit_clients: HashMap, wg_exit_clients: HashMap, ) -> Result> { @@ -565,14 +541,8 @@ pub fn get_client_interface( wg_exit_clients ); match ( - new_wg_exit_clients.get(match &c.wg_pubkey.parse() { - Ok(a) => a, - Err(e) => return Err(Box::new(e.clone().into())), - }), - wg_exit_clients.get(match &c.wg_pubkey.parse() { - Ok(a) => a, - Err(e) => return Err(Box::new(e.clone().into())), - }), + new_wg_exit_clients.get(&c.wg_public_key), + wg_exit_clients.get(&c.wg_public_key), ) { (Some(_), None) => Ok(ClientInterfaceType::ExitInterface), (None, Some(_)) => Ok(ClientInterfaceType::LegacyInterface), @@ -586,7 +556,7 @@ pub fn get_client_interface( _ => { error!( "WG EXIT SETUP: Client {}, does not have handshake with any wg exit interface. Setting up routes on wg_exit", - c.wg_pubkey + c.wg_public_key ); Ok(ClientInterfaceType::LegacyInterface) } @@ -599,16 +569,16 @@ pub fn get_client_interface( /// Unlike intermediary enforcement we do not need to subdivide the free tier to prevent /// ourselves from exceeding the upstream free tier. As an exit we are the upstream. pub fn enforce_exit_clients( - clients_list: Vec, + clients_list: Vec, old_debt_actions: &HashSet<(Identity, DebtAction)>, ) -> Result, Box> { let start = Instant::now(); let mut clients_by_id = HashMap::new(); let free_tier_limit = settings::get_rita_exit().payment.free_tier_throughput; let close_threshold = calculate_close_thresh(); - for client in clients_list.iter() { - if let Ok(id) = to_identity(client) { - clients_by_id.insert(id, client); + for client_id in clients_list.iter() { + if let Ok(exit_client) = to_exit_client(*client_id) { + clients_by_id.insert(client_id, exit_client); } } let list = get_debts_list(); @@ -638,10 +608,10 @@ pub fn enforce_exit_clients( for debt_entry in list.iter() { match clients_by_id.get(&debt_entry.identity) { Some(client) => { - match client.internal_ip.parse() { - Ok(IpAddr::V4(ip)) => { + match client.internal_ip { + IpAddr::V4(ip) => { if debt_entry.payment_details.action == DebtAction::SuspendTunnel { - info!("Exit is enforcing on {} because their debt of {} is greater than the limit of {}", client.wg_pubkey, debt_entry.payment_details.debt, close_threshold); + info!("Exit is enforcing on {} because their debt of {} is greater than the limit of {}", client.public_key, debt_entry.payment_details.debt, close_threshold); // setup flows this allows us to classify traffic we then limit the class, we delete the class as part of unenforcment but it's difficult to delete the flows // so a user who has been enforced and unenforced while the exit has been online may already have them setup let flow_setup_required = match ( @@ -672,7 +642,13 @@ pub fn enforce_exit_clients( error!("Failed to setup flow for wg_exit_v2 {:?}", e); } // gets the client ipv6 flow for this exit specifically - let client_ipv6 = get_client_ipv6(client); + let client_ipv6 = get_client_ipv6( + debt_entry.identity, + settings::get_rita_exit().exit_network.subnet, + settings::get_rita_exit() + .get_client_subnet_size() + .unwrap_or(DEFAULT_CLIENT_SUBNET_SIZE), + ); if let Ok(Some(client_ipv6)) = client_ipv6 { if let Err(e) = KI.create_flow_by_ipv6(EXIT_INTERFACE, client_ipv6, ip) @@ -682,7 +658,7 @@ pub fn enforce_exit_clients( } info!( "Completed one time enforcement flow setup for {}", - client.wg_pubkey + client.public_key ) } @@ -718,7 +694,7 @@ pub fn enforce_exit_clients( if action_required { // Delete exisiting enforcement class, users who are not enforced are unclassifed becuase // leaving the class in place reduces their speeds. - info!("Deleting enforcement classes for {}", client.wg_pubkey); + info!("Deleting enforcement classes for {}", client.public_key); if let Err(e) = KI.delete_class(LEGACY_INTERFACE, ip) { error!("Unable to delete class on wg_exit, is {} still enforced when they shouldnt be? {:?}", ip, e); } diff --git a/rita_exit/src/database/sms.rs b/rita_exit/src/database/sms.rs deleted file mode 100644 index cc4442c5f..000000000 --- a/rita_exit/src/database/sms.rs +++ /dev/null @@ -1,264 +0,0 @@ -use crate::database::database_tools::text_sent; -use crate::database::database_tools::verify_client; -use crate::database::get_database_connection; -use crate::database::get_exit_info; -use crate::database::struct_tools::texts_sent; -use crate::get_client_ipv6; -use crate::RitaExitError; - -use althea_types::{ExitClientDetails, ExitClientIdentity, ExitState}; -use phonenumber::PhoneNumber; -use settings::exit::ExitVerifSettings; -use settings::get_rita_exit; -use std::time::Duration; - -#[derive(Serialize)] -pub struct SmsCheck { - api_key: String, - verification_code: String, - phone_number: String, - country_code: String, -} - -/// Posts to the validation endpoint with the code, will return success if the code -/// is the same as the one sent to the user -async fn check_text(number: String, code: String, api_key: String) -> Result { - trace!("About to check text message status for {}", number); - let number: PhoneNumber = match number.parse() { - Ok(number) => number, - Err(e) => return Err(e.into()), - }; - let url = "https://api.authy.com/protected/json/phones/verification/check"; - - let client = awc::Client::default(); - let response = match client - .get(url) - .send_form(&SmsCheck { - api_key, - verification_code: code, - phone_number: number.national().to_string(), - country_code: number.code().value().to_string(), - }) - .await - { - Ok(a) => a, - Err(e) => { - return Err(RitaExitError::MiscStringError(format!( - "Send request error: {e:?}" - ))) - } - }; - - trace!("Got {} back from check text", response.status()); - Ok(response.status().is_success()) -} - -#[derive(Serialize)] -pub struct SmsRequest { - api_key: String, - via: String, - phone_number: String, - country_code: String, -} - -/// Sends the authy verification text by hitting the api endpoint -async fn send_text(number: String, api_key: String) -> Result<(), RitaExitError> { - info!("Sending message for {}", number); - let url = "https://api.authy.com/protected/json/phones/verification/start"; - let number: PhoneNumber = match number.parse() { - Ok(number) => number, - Err(e) => return Err(e.into()), - }; - - let client = awc::Client::default(); - match client - .post(url) - .send_form(&SmsRequest { - api_key, - via: "sms".to_string(), - phone_number: number.national().to_string(), - country_code: number.code().value().to_string(), - }) - .await - { - Ok(_a) => Ok(()), - Err(e) => Err(RitaExitError::MiscStringError(format!( - "Send text error: {e:?}" - ))), - } -} - -/// Handles the minutia of phone registration states -pub async fn handle_sms_registration( - client: ExitClientIdentity, - their_record: exit_db::models::Client, - api_key: String, -) -> Result> { - info!( - "Handling phone registration for {}", - client.global.wg_public_key - ); - - // Get magic phone number - let magic_phone_number = get_rita_exit().exit_network.magic_phone_number; - - let text_num = texts_sent(&their_record); - let sent_more_than_allowed_texts = text_num > 10; - info!( - "Magic number is : {:?} and client nubmer is {:?}", - magic_phone_number, - client.reg_details.phone.clone() - ); - - match ( - client.reg_details.phone.clone(), - client.reg_details.phone_code.clone(), - sent_more_than_allowed_texts, - ) { - // all texts exhausted, but they can still submit the correct code - (Some(number), Some(code), true) => { - let result = (magic_phone_number.is_some() - && magic_phone_number.unwrap() == number.clone()) - || check_text(number.clone(), code, api_key).await?; - let conn = get_database_connection()?; - if result { - verify_client(&client, true, &conn)?; - info!( - "Phone registration complete for {}", - client.global.wg_public_key - ); - Ok(ExitState::Registered { - our_details: ExitClientDetails { - client_internal_ip: match their_record.internal_ip.parse() { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }, - internet_ipv6_subnet: get_client_ipv6(&their_record)?, - }, - general_details: get_exit_info(), - message: "Registration OK".to_string(), - }) - } else { - Ok(ExitState::Pending { - general_details: get_exit_info(), - message: "awaiting phone verification".to_string(), - email_code: None, - phone_code: None, - }) - } - } - // user has exhausted attempts but is still not submitting code - (Some(_number), None, true) => Ok(ExitState::Pending { - general_details: get_exit_info(), - message: "awaiting phone verification".to_string(), - email_code: None, - phone_code: None, - }), - // user has attempts remaining and is requesting the code be resent - (Some(number), None, false) => { - send_text(number, api_key).await?; - let conn = get_database_connection()?; - text_sent(&client, &conn, text_num)?; - Ok(ExitState::Pending { - general_details: get_exit_info(), - message: "awaiting phone verification".to_string(), - email_code: None, - phone_code: None, - }) - } - // user has attempts remaining and is submitting a code - (Some(number), Some(code), false) => { - let result = (magic_phone_number.is_some() - && magic_phone_number.unwrap() == number.clone()) - || check_text(number, code, api_key).await?; - let conn = get_database_connection()?; - - trace!("Check text returned {}", result); - if result { - verify_client(&client, true, &conn)?; - info!( - "Phone registration complete for {}", - client.global.wg_public_key - ); - Ok(ExitState::Registered { - our_details: ExitClientDetails { - client_internal_ip: match their_record.internal_ip.parse() { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }, - internet_ipv6_subnet: get_client_ipv6(&their_record)?, - }, - general_details: get_exit_info(), - message: "Registration OK".to_string(), - }) - } else { - Ok(ExitState::Pending { - general_details: get_exit_info(), - message: "awaiting phone verification".to_string(), - email_code: None, - phone_code: None, - }) - } - } - // user did not submit a phonenumber - (None, _, _) => Ok(ExitState::Denied { - message: "This exit requires a phone number to register!".to_string(), - }), - } -} - -#[derive(Serialize)] -pub struct SmsNotification { - #[serde(rename = "To")] - to: String, - #[serde(rename = "From")] - from: String, - #[serde(rename = "Body")] - body: String, -} - -/// This function is used to send texts to the admin notification list, in the case of no configured -/// admin phones you will get an empty array -pub fn send_admin_notification_sms(message: &str) { - let verif_settings = settings::get_rita_exit().verif_settings; - let exit_title = settings::get_rita_exit().description; - if let Some(ExitVerifSettings::Phone(phone)) = verif_settings { - info!("Sending Admin notification message for"); - - let url = format!( - "https://api.twilio.com/2010-04-01/Accounts/{}/Messages.json", - phone.twillio_account_id - ); - - for number in phone.operator_notification_number { - let client = reqwest::blocking::Client::new(); - match client - .post(url.clone()) - .basic_auth( - phone.twillio_account_id.clone(), - Some(phone.twillio_auth_token.clone()), - ) - .form(&SmsNotification { - to: number.to_string(), - from: phone.notification_number.clone(), - body: exit_title.clone() + ": " + message, - }) - .timeout(Duration::from_secs(1)) - .send() - { - Ok(val) => { - info!("Admin notification text sent successfully with {:?}", val); - } - Err(e) => { - error!( - "Admin notification text to {} failed with {:?}", - number.to_string(), - e - ); - } - } - } - } else { - warn!("We don't send admin messages over email!"); - } -} diff --git a/rita_exit/src/database/struct_tools.rs b/rita_exit/src/database/struct_tools.rs index 4625ad243..765ce6d80 100644 --- a/rita_exit/src/database/struct_tools.rs +++ b/rita_exit/src/database/struct_tools.rs @@ -1,89 +1,246 @@ use althea_kernel_interface::ExitClient; -use althea_types::ExitClientIdentity; -use althea_types::Identity; -use arrayvec::ArrayString; -use exit_db::models; -use exit_db::models::Client; -use ipnetwork::IpNetwork; -use rand::Rng; -use std::collections::HashSet; -use std::fmt::Write as _; -use std::net::IpAddr; - -use crate::RitaExitError; - -pub fn to_identity(client: &Client) -> Result> { - trace!("Converting client {:?}", client); - Ok(Identity { - mesh_ip: match client.mesh_ip.clone().parse() { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }, - eth_address: match client.eth_address.clone().parse() { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }, - wg_public_key: match client.wg_pubkey.clone().parse() { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }, - nickname: Some(ArrayString::<32>::from(&client.nickname).unwrap_or_default()), - }) +use althea_types::{Identity, WgKey}; +use ipnetwork::{IpNetwork, Ipv4Network}; +use std::collections::hash_map::DefaultHasher; +use std::collections::{HashMap, HashSet}; +use std::convert::TryInto; +use std::fmt::Write; +use std::hash::{Hash, Hasher}; +use std::net::{IpAddr, Ipv4Addr}; +use std::sync::{Arc, RwLock}; + +use crate::{generate_iterative_client_subnet, RitaExitError, DEFAULT_CLIENT_SUBNET_SIZE}; + +/// Wg exit port on client side +pub const CLIENT_WG_PORT: u16 = 59999; + +/// Max number of time we try to generate a valid ip addr before returning an eror +pub const MAX_IP_RETRIES: u8 = 10; + +lazy_static! { + /// Keep track of ip addrs assigned to clients and ensure collisions dont happen. In worst case + /// the exit restarts and loses all this data in which case those client they had collision may get new + /// ip addrs and would need to setup wg exit tunnel again + static ref IP_ASSIGNMENT_MAP: Arc> = Arc::new(RwLock::new(IpAssignmentMap::default())); +} + +#[derive(Clone, Debug, Default)] +pub struct IpAssignmentMap { + pub ipv6_assignments: HashMap, + pub internal_ip_assignments: HashMap, +} + +// Lazy static setters/getters +pub fn get_ipv6_assignments() -> HashMap { + IP_ASSIGNMENT_MAP.read().unwrap().ipv6_assignments.clone() } -pub fn to_exit_client(client: Client) -> Result> { - let mut internet_ipv6_list = vec![]; - let string_list: Vec<&str> = client.internet_ipv6.split(',').collect(); - for ip_net in string_list { - // can we parse the ip net - match ip_net.parse::() { - Ok(ip_net) => internet_ipv6_list.push(ip_net), - Err(e) => error!( - "Unable to parse {:?}, Invalid database state? {:?}", - ip_net, e - ), +pub fn get_internal_ip_assignments() -> HashMap { + IP_ASSIGNMENT_MAP + .read() + .unwrap() + .internal_ip_assignments + .clone() +} + +pub fn add_new_ipv6_assignment(addr: IpAddr, key: WgKey) { + IP_ASSIGNMENT_MAP + .write() + .unwrap() + .ipv6_assignments + .insert(addr, key); +} + +pub fn add_new_internal_ip_assignement(addr: IpAddr, key: WgKey) { + IP_ASSIGNMENT_MAP + .write() + .unwrap() + .internal_ip_assignments + .insert(addr, key); +} + +/// Given a client identity, get the clients ipv6 addr using the wgkey as a generative seed +pub fn get_client_ipv6( + their_record: Identity, + exit_sub: Option, + client_subnet_size: u8, +) -> Result, Box> { + if let Some(exit_sub) = exit_sub { + let wg_hash = hash_wgkey(their_record.wg_public_key); + + // This bitshifting is the total number of client subnets available. We are checking that our iterative index + // is lower than this number. For example, exit subnet: fd00:1000/120, client subnet /124, number of subnets will be + // 2^(124 - 120) => 2^4 => 16 + let total_subnets = 1 << (client_subnet_size - exit_sub.prefix()); + let mut generative_index = wg_hash % total_subnets; + + // Loop to try to generate a valid address + let mut retries = 0; + loop { + // Return an error if we retry too many times + if retries > MAX_IP_RETRIES { + return Err(Box::new(RitaExitError::MiscStringError(format!( + "Unable to get internet ipv6 using network {} and index {}", + exit_sub, generative_index + )))); + } + + let client_subnet = + generate_iterative_client_subnet(exit_sub, generative_index, client_subnet_size)?; + + if validate_internet_ipv6(client_subnet, their_record.wg_public_key) { + add_new_ipv6_assignment(client_subnet.ip(), their_record.wg_public_key); + return Ok(Some(client_subnet)); + } else { + retries += 1; + generative_index = (generative_index + 1) % total_subnets; + continue; + } } + } else { + // This exit doesnt support ipv6 + Ok(None) } +} - Ok(ExitClient { - mesh_ip: match client.mesh_ip.parse() { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }, - internal_ip: match client.internal_ip.parse() { - Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }, - port: client.wg_port as u16, - public_key: match client.wg_pubkey.parse() { +/// Given a client identity, get the clients internal ip addr using the wgkey as a generative seed +pub fn get_client_internal_ip( + their_record: Identity, + netmask: u8, + gateway_ip: Ipv4Addr, +) -> Result> { + let wg_hash = hash_wgkey(their_record.wg_public_key); + // total number of available addresses + let total_addresses: u64 = 2_u64.pow((32 - netmask).into()); + let mut generative_index = wg_hash % total_addresses; + let network = match Ipv4Network::new(gateway_ip, netmask) { + Ok(a) => a, + Err(e) => { + return Err(Box::new(RitaExitError::MiscStringError(format!( + "Unable to setup and ipnetwork to generate internal ip {}", + e + )))) + } + }; + + // Keep trying to generate an address till we get a valid one + let mut retries = 0; + loop { + // Return an error if we retry too many times + if retries > MAX_IP_RETRIES { + return Err(Box::new(RitaExitError::MiscStringError(format!( + "Unable to get internal ip using network {} and index {}", + network, generative_index + )))); + } + + let internal_ip = network.nth(match generative_index.try_into() { Ok(a) => a, - Err(e) => return Err(Box::new(e.into())), - }, - internet_ipv6_list, - }) + Err(e) => { + warn!("Internal Ip failure: {}", e); + retries += 1; + generative_index = (generative_index + 1) % total_addresses; + continue; + } + }); + + let internal_ip = match internal_ip { + Some(a) => a, + None => { + retries += 1; + generative_index = (generative_index + 1) % total_addresses; + continue; + } + }; + + // Validate that this ip is valid and return it + if validate_internal_ip(network, internal_ip, gateway_ip, their_record.wg_public_key) { + add_new_internal_ip_assignement(IpAddr::V4(internal_ip), their_record.wg_public_key); + return Ok(IpAddr::V4(internal_ip)); + } else { + retries += 1; + generative_index = (generative_index + 1) % total_addresses; + continue; + } + } } -pub fn clients_to_ids(clients: Vec) -> Vec { - let mut ids: Vec = Vec::new(); - for client in clients.iter() { - match (client.verified, to_identity(client)) { - (true, Ok(id)) => ids.push(id), - (true, Err(e)) => warn!("Corrupt database entry {:?}", e), - (false, _) => trace!("{:?} is not registered", client), +/// Check that this ip can be assigned, make sure there isnt a collision with previously assigned ips +pub fn validate_internet_ipv6(client_subnet: IpNetwork, our_wgkey: WgKey) -> bool { + let assigned_ips = get_ipv6_assignments(); + let assignment = assigned_ips.get(&client_subnet.ip()); + match assignment { + Some(a) => { + // There is an entry, verify if its our entry else false + *a == our_wgkey + } + // There is no assigned ip here, ip is valid + None => true, + } +} + +/// Check that this ip can be assigned, make sure it isnt our ip, network ip, broadcast ip, etc +pub fn validate_internal_ip( + network: Ipv4Network, + assigned_ip: Ipv4Addr, + our_ip: Ipv4Addr, + our_wgkey: WgKey, +) -> bool { + let broadcast = network.broadcast(); + let network_ip = network.network(); + + // Collision with our ip + if assigned_ip == our_ip { + return false; + } + // collision with the network ip + if assigned_ip == network_ip { + return false; + } + // collision with broadcast address + if assigned_ip == broadcast { + return false; + } + + let assignments = get_internal_ip_assignments(); + let assignment = assignments.get(&IpAddr::V4(assigned_ip)); + match assignment { + Some(a) => { + // check if this existing ip is ours + *a == our_wgkey } + // No assignment, we can use this address + None => true, } - ids } -/// returns true if client is verified -pub fn verif_done(client: &models::Client) -> bool { - client.verified +pub fn to_exit_client(client: Identity) -> Result> { + let internet_ipv6 = get_client_ipv6( + client, + settings::get_rita_exit().exit_network.subnet, + settings::get_rita_exit() + .get_client_subnet_size() + .unwrap_or(DEFAULT_CLIENT_SUBNET_SIZE), + )?; + let internal_ip = get_client_internal_ip( + client, + settings::get_rita_exit().exit_network.netmask, + settings::get_rita_exit().exit_network.own_internal_ip, + )?; + + Ok(ExitClient { + mesh_ip: client.mesh_ip, + internal_ip, + port: CLIENT_WG_PORT, + public_key: client.wg_public_key, + internet_ipv6, + }) } -/// returns the number of text messages this entry has requested -/// and recieved so far -pub fn texts_sent(client: &models::Client) -> i32 { - client.text_sent +pub fn hash_wgkey(key: WgKey) -> u64 { + let mut hasher = DefaultHasher::new(); + key.to_string().hash(&mut hasher); + hasher.finish() } /// quick display function for a neat error @@ -95,36 +252,229 @@ pub fn display_hashset(input: &HashSet) -> String { out } -pub fn client_to_new_db_client( - client: &ExitClientIdentity, - new_ip: IpAddr, - country: String, - internet_ip: Option, -) -> models::Client { - let mut rng = rand::thread_rng(); - let rand_code: u64 = rng.gen_range(0..999_999); - models::Client { - wg_port: i32::from(client.wg_port), - mesh_ip: client.global.mesh_ip.to_string(), - wg_pubkey: client.global.wg_public_key.to_string(), - eth_address: client.global.eth_address.to_string().to_lowercase(), - nickname: client.global.nickname.unwrap_or_default().to_string(), - internal_ip: new_ip.to_string(), - internet_ipv6: { - if let Some(ip_net) = internet_ip { - ip_net.to_string() - } else { - "".to_string() - } - }, - email: client.reg_details.email.clone().unwrap_or_default(), - phone: client.reg_details.phone.clone().unwrap_or_default(), - country, - email_code: format!("{rand_code:06}"), - text_sent: 0, - verified: false, - email_sent_time: 0, - last_seen: 0, - last_balance_warning_time: 0, +#[cfg(test)] +mod tests { + use althea_types::Identity; + + use crate::database::struct_tools::{ + get_client_internal_ip, get_internal_ip_assignments, get_ipv6_assignments, + }; + + use super::{get_client_ipv6, hash_wgkey}; + + #[test] + fn test_internet_ipv6_assignment() { + let exit_sub = Some("2602:FBAD:10::/126".parse().unwrap()); + let dummy_client = Identity { + mesh_ip: "fd00::1337".parse().unwrap(), + eth_address: "0x4Af6D4125f3CBF07EBAD056E2eCa7b17c58AFEa4" + .parse() + .unwrap(), + wg_public_key: "TgR85AcLBY/7cLHXZIICcwVDU+1Pj/cjFeduCUNvLVU=" + .parse() + .unwrap(), + nickname: None, + }; + + // Generate a client subnet + let ip = get_client_ipv6(dummy_client, exit_sub, 128) + .unwrap() + .unwrap(); + + // Verify assignement db is correctly populated + assert!(get_ipv6_assignments().len() == 1); + assert_eq!( + *get_ipv6_assignments().get(&ip.ip()).unwrap(), + dummy_client.wg_public_key + ); + + // Try retrieving the same client + let ip_2 = get_client_ipv6(dummy_client, exit_sub, 128) + .unwrap() + .unwrap(); + assert_eq!(ip, ip_2); + + // Make sure no new entries in assignemnt db + assert!(get_ipv6_assignments().len() == 1); + assert_eq!( + *get_ipv6_assignments().get(&ip.ip()).unwrap(), + dummy_client.wg_public_key + ); + + println!("Assigned Ip client 1: {:?}", ip); + + // Add a second client + let dummy_client_2 = Identity { + mesh_ip: "fd00::1447".parse().unwrap(), + eth_address: "0x4Af6D4125f3CBF07EBAD056E2eCa7b17c58AFEa4" + .parse() + .unwrap(), + wg_public_key: "CEnTMKvpWr+xTFl7niTYyqH56w5iPdMjiC938X542GA=" + .parse() + .unwrap(), + nickname: None, + }; + + // Generate a client subnet + let ip = get_client_ipv6(dummy_client_2, exit_sub, 128) + .unwrap() + .unwrap(); + + // Verify assignement db is correctly populated + assert!(get_ipv6_assignments().len() == 2); + assert_eq!( + *get_ipv6_assignments().get(&ip.ip()).unwrap(), + dummy_client_2.wg_public_key + ); + + let ip_2 = get_client_ipv6(dummy_client_2, exit_sub, 128) + .unwrap() + .unwrap(); + assert_eq!(ip, ip_2); + + // Make sure no new entries in assignemnt db + assert!(get_ipv6_assignments().len() == 2); + assert_eq!( + *get_ipv6_assignments().get(&ip.ip()).unwrap(), + dummy_client_2.wg_public_key + ); + + println!("Assigned Ip client 2: {:?}", ip); + + // Generate a collision + let dummy_client_3 = Identity { + mesh_ip: "fd00::1557".parse().unwrap(), + eth_address: "0x4Af6D4125f3CBF07EBAD056E2eCa7b17c58AFEa4" + .parse() + .unwrap(), + wg_public_key: "+Iai9Qj5aIuTAq6h1srDL8yKElN65/PhNtkccSOJwls=" + .parse() + .unwrap(), + nickname: None, + }; + + // Generate a client subnet + let ip = get_client_ipv6(dummy_client_3, exit_sub, 128) + .unwrap() + .unwrap(); + + // Verify assignement db is correctly populated + assert!(get_ipv6_assignments().len() == 3); + assert_eq!( + *get_ipv6_assignments().get(&ip.ip()).unwrap(), + dummy_client_3.wg_public_key + ); + + let _ = get_client_ipv6(dummy_client_2, exit_sub, 128) + .unwrap() + .unwrap(); + let ip_2 = get_client_ipv6(dummy_client_3, exit_sub, 128) + .unwrap() + .unwrap(); + assert_eq!(ip, ip_2); + + // Make sure no new entries in assignemnt db + assert!(get_ipv6_assignments().len() == 3); + assert_eq!( + *get_ipv6_assignments().get(&ip.ip()).unwrap(), + dummy_client_3.wg_public_key + ); + + println!("Assigned Ip client 3: {:?}", ip); + } + + #[test] + fn hash_playground() { + let key_1_hash = hash_wgkey( + "TgR85AcLBY/7cLHXZIICcwVDU+1Pj/cjFeduCUNvLVU=" + .parse() + .unwrap(), + ) % 4; + println!("hash: {}", key_1_hash); + let key_1_hash = hash_wgkey( + "+Iai9Qj5aIuTAq6h1srDL8yKElN65/PhNtkccSOJwls=" + .parse() + .unwrap(), + ) % 4; + println!("hash: {}", key_1_hash); + let key_1_hash = hash_wgkey( + "CEnTMKvpWr+xTFl7niTYyqH56w5iPdMjiC938X542GA=" + .parse() + .unwrap(), + ) % 4; + println!("hash: {}", key_1_hash); + } + + #[test] + fn test_internal_ip_assignment() { + let dummy_client = Identity { + mesh_ip: "fd00::1337".parse().unwrap(), + eth_address: "0x4Af6D4125f3CBF07EBAD056E2eCa7b17c58AFEa4" + .parse() + .unwrap(), + wg_public_key: "TgR85AcLBY/7cLHXZIICcwVDU+1Pj/cjFeduCUNvLVU=" + .parse() + .unwrap(), + nickname: None, + }; + let ip = + get_client_internal_ip(dummy_client, 30, "172.168.0.100".parse().unwrap()).unwrap(); + + // Verify assignement db is correctly populated + assert!(get_internal_ip_assignments().len() == 1); + assert_eq!( + *get_internal_ip_assignments().get(&ip).unwrap(), + dummy_client.wg_public_key + ); + + // requesting the same client shouldnt change any state + let ip2 = + get_client_internal_ip(dummy_client, 30, "172.168.0.100".parse().unwrap()).unwrap(); + + assert_eq!(ip, ip2); + + assert!(get_internal_ip_assignments().len() == 1); + assert_eq!( + *get_internal_ip_assignments().get(&ip2).unwrap(), + dummy_client.wg_public_key + ); + + println!("Internal ip client 1: {}", ip); + + // Second client who collides + let dummy_client_2 = Identity { + mesh_ip: "fd00::1557".parse().unwrap(), + eth_address: "0x4Af6D4125f3CBF07EBAD056E2eCa7b17c58AFEa4" + .parse() + .unwrap(), + wg_public_key: "+Iai9Qj5aIuTAq6h1srDL8yKElN65/PhNtkccSOJwls=" + .parse() + .unwrap(), + nickname: None, + }; + + let ip = + get_client_internal_ip(dummy_client_2, 30, "172.168.0.100".parse().unwrap()).unwrap(); + + // Verify assignement db is correctly populated + assert!(get_internal_ip_assignments().len() == 2); + assert_eq!( + *get_internal_ip_assignments().get(&ip).unwrap(), + dummy_client_2.wg_public_key + ); + + // requesting the same client shouldnt change any state + let ip2 = + get_client_internal_ip(dummy_client_2, 30, "172.168.0.100".parse().unwrap()).unwrap(); + + assert_eq!(ip, ip2); + + assert!(get_internal_ip_assignments().len() == 2); + assert_eq!( + *get_internal_ip_assignments().get(&ip2).unwrap(), + dummy_client_2.wg_public_key + ); + + println!("Internal ip client 2: {}", ip); } } diff --git a/rita_exit/src/error.rs b/rita_exit/src/error.rs index 55055e62c..15291a1d5 100644 --- a/rita_exit/src/error.rs +++ b/rita_exit/src/error.rs @@ -16,7 +16,6 @@ pub enum RitaExitError { EmailNotFound(Box), AddrParseError(AddrParseError), IpAddrError(IpAddr), - DieselError(diesel::result::Error), RitaCommonError(RitaCommonError), RenderError(RenderError), EmailError(lettre::error::Error), @@ -31,11 +30,6 @@ pub enum RitaExitError { NoClientError, } -impl From for RitaExitError { - fn from(error: diesel::result::Error) -> Self { - RitaExitError::DieselError(error) - } -} impl From for RitaExitError { fn from(error: AddrParseError) -> Self { RitaExitError::AddrParseError(error) @@ -109,7 +103,6 @@ impl Display for RitaExitError { RitaExitError::EmailNotFound(a) => write!(f, "Could not find email for {a:?}"), RitaExitError::AddrParseError(a) => write!(f, "{a:?}",), RitaExitError::IpAddrError(a) => write!(f, "No route found for mesh ip: {a:?}",), - RitaExitError::DieselError(a) => write!(f, "{a}",), RitaExitError::RitaCommonError(a) => write!(f, "{a}",), RitaExitError::DeepSpaceError(a) => write!(f, "{a}",), RitaExitError::RenderError(a) => write!(f, "{a}",), diff --git a/rita_exit/src/lib.rs b/rita_exit/src/lib.rs index 536942d66..d03973ac7 100644 --- a/rita_exit/src/lib.rs +++ b/rita_exit/src/lib.rs @@ -18,20 +18,11 @@ use actix_async::System; use actix_web_async::web; use actix_web_async::App; use actix_web_async::HttpServer; -use althea_kernel_interface::KI; pub use error::RitaExitError; -use r2d2::PooledConnection; pub use crate::database::database_tools::*; pub use crate::database::database_tools::*; -pub use crate::database::db_client::*; -pub use crate::database::email::*; pub use crate::database::geoip::*; -pub use crate::database::sms::*; -use crate::network_endpoints::nuke_db; -use diesel::r2d2::ConnectionManager; -use diesel::r2d2::Pool; -use diesel::PgConnection; use rita_common::dashboard::babel::*; use rita_common::dashboard::debts::*; use rita_common::dashboard::development::*; @@ -45,46 +36,8 @@ use rita_common::dashboard::wallet::*; use rita_common::dashboard::wg_key::*; use rita_common::middleware; use rita_common::network_endpoints::version; -use std::collections::HashMap; -use std::sync::Arc; -use std::sync::RwLock; use std::thread; -lazy_static! { - pub static ref DB_POOL: Arc>>>> = - Arc::new(RwLock::new(HashMap::new())); -} - -pub fn initialize_db_pool() { - let db_uri = settings::get_rita_exit().db_uri; - if !(db_uri.contains("postgres://") - || db_uri.contains("postgresql://") - || db_uri.contains("psql://")) - { - panic!("You must provide a valid postgressql database uri!"); - } - let manager = ConnectionManager::new(settings::get_rita_exit().db_uri); - let db_pool = &mut *DB_POOL.write().unwrap(); - let netns = KI.check_integration_test_netns(); - - db_pool.insert( - netns, - r2d2::Pool::builder() - .max_size(settings::get_rita_exit().workers + 1) - .build(manager) - .expect("Failed to create pool. Check exit IP is trusted to access postgresql"), - ); -} - -pub fn get_db_pool() -> Option>> { - let netns = KI.check_integration_test_netns(); - let db_pool = DB_POOL.read().unwrap(); - db_pool - .get(&netns) - .expect("This should be initialized at startup") - .try_get() -} - #[derive(Debug, Deserialize, Default)] pub struct Args { pub flag_config: String, @@ -121,7 +74,6 @@ pub fn start_rita_exit_dashboard() { .route("/version", web::get().to(version)) .route("/wg_public_key", web::get().to(get_wg_public_key)) .route("/wipe", web::post().to(wipe)) - .route("/database", web::delete().to(nuke_db)) .route("/debts", web::get().to(get_debts)) .route("/debts/reset", web::post().to(reset_debt)) .route("/withdraw/{address}/{amount}", web::post().to(withdraw)) diff --git a/rita_exit/src/network_endpoints/mod.rs b/rita_exit/src/network_endpoints/mod.rs index 6203db55c..24b907061 100644 --- a/rita_exit/src/network_endpoints/mod.rs +++ b/rita_exit/src/network_endpoints/mod.rs @@ -1,8 +1,8 @@ //! Network endpoints for rita-exit that are not dashboard or local infromational endpoints //! these are called by rita instances to operate the mesh -use crate::database::database_tools::get_database_connection; use crate::database::{client_status, get_exit_info, signup_client}; +use crate::get_clients_exit_cluster_list; #[cfg(feature = "development")] use crate::rita_exit::database::db_client::DbClient; #[cfg(feature = "development")] @@ -158,7 +158,7 @@ pub async fn secure_setup_request( let remote_mesh_ip = remote_mesh_socket.ip(); if remote_mesh_ip == client_mesh_ip { - let result = signup_client(*client, false).await; + let result = signup_client(*client).await; match result { Ok(exit_state) => HttpResponse::Ok().json(secure_setup_return( exit_state, @@ -199,14 +199,7 @@ pub async fn secure_status_request(request: Json) - }; trace!("got status request from {}", their_wg_pubkey); - let conn = match get_database_connection() { - Ok(conn) => conn, - Err(e) => { - return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR) - .json(format!("Error getting database connection: {e:?}")) - } - }; - let state = match client_status(*decrypted_id, &conn) { + let state = match client_status(*decrypted_id) { Ok(state) => state, Err(e) => match *e { RitaExitError::NoClientError => { @@ -254,7 +247,7 @@ pub async fn get_exit_list(request: Json) -> HttpRe let their_nacl_pubkey = request.pubkey.into(); let ret: ExitList = ExitList { - exit_list: settings::get_rita_exit().exit_network.cluster_exits, + exit_list: get_clients_exit_cluster_list(request.pubkey), wg_exit_listen_port: settings::get_rita_exit().exit_network.wg_v2_tunnel_port, }; @@ -330,20 +323,3 @@ pub async fn get_client_debt(client: Json) -> HttpResponse { } HttpResponse::NotFound().json("No client by that ID") } - -#[cfg(not(feature = "development"))] -pub async fn nuke_db(_req: HttpRequest) -> HttpResponse { - // This is returned on production builds. - HttpResponse::NotFound().finish() -} - -#[cfg(feature = "development")] -pub async fn nuke_db(_req: HttpRequest) -> HttpResponse { - use crate::truncate_db_tables; - - trace!("nuke_db: Truncating all data from the database"); - if let Err(e) = truncate_db_tables() { - error!("Error: {}", e); - } - HttpResponse::NoContent().finish() -} diff --git a/rita_exit/src/operator_update/mod.rs b/rita_exit/src/operator_update/mod.rs index 1b96c1bd9..f5a03e827 100644 --- a/rita_exit/src/operator_update/mod.rs +++ b/rita_exit/src/operator_update/mod.rs @@ -1,19 +1,9 @@ //! This module is responsible for checking in with the operator server and getting updated local settings pub mod update_loop; - -use althea_types::ExitClientIdentity; use althea_types::OperatorExitCheckinMessage; -use althea_types::OperatorExitUpdateMessage; -use althea_types::WgKey; -use diesel::QueryDsl; -use diesel::RunQueryDsl; -use exit_db::schema::clients::dsl::clients as db_client; -use exit_db::schema::clients::wg_pubkey; use rita_common::KI; use std::time::{Duration, Instant}; -use crate::database::signup_client; -use crate::get_database_connection; use crate::rita_loop::EXIT_INTERFACE; pub struct UptimeStruct { @@ -51,86 +41,16 @@ pub async fn operator_update(rita_started: Instant) { info!("About to perform operator update with {}", url); let client = awc::Client::default(); - let response = client + let _response = client .post(url) .timeout(OPERATOR_UPDATE_TIMEOUT) .send_json(&OperatorExitCheckinMessage { id, pass, exit_uptime: rita_started.elapsed(), - registered_keys: get_registered_list(), // Since this checkin works only from b20, we only need to look on wg_exit_v2 users_online: KI.get_wg_exit_clients_online(EXIT_INTERFACE).ok(), }) .await; - - let response = match response { - Ok(mut response) => { - trace!("Response is {:?}", response.status()); - trace!("Response is {:?}", response.headers()); - response.json().await - } - Err(e) => { - error!("Failed to perform exit operator checkin with {:?}", e); - return; - } - }; - - let new_settings: OperatorExitUpdateMessage = match response { - Ok(a) => a, - Err(e) => { - error!("Failed to perform exit operator checkin with {:?}", e); - return; - } - }; - - // Perform operator updates - register_op_clients(new_settings.to_register).await; - } -} - -async fn register_op_clients(clients: Vec) { - info!("Signing up ops clients {:?}", clients); - for c in clients { - // Though this is asnyc, it wont block since the only async part (sms handling) - // is skiped in this function - let c_key = c.global.wg_public_key; - if let Err(e) = signup_client(c, true).await { - error!("Unable to signup client {} with {:?}", c_key, e); - }; - } -} - -pub fn get_registered_list() -> Option> { - match get_database_connection() { - Ok(conn) => { - let registered_routers = db_client.select(wg_pubkey); - let registered_routers = match registered_routers.load::(&conn) { - Ok(a) => a, - Err(e) => { - error!("Unable to retrive wg keys {}", e); - return None; - } - }; - Some( - registered_routers - .iter() - .filter_map(|r| match r.parse() { - Ok(a) => Some(a), - Err(_) => { - error!("Invalid wg key in database! {}", r); - None - } - }) - .collect::>(), - ) - } - Err(e) => { - error!( - "Unable to get a database connection to retrieve registered exits: {}", - e - ); - None - } } } diff --git a/rita_exit/src/rita_loop/mod.rs b/rita_exit/src/rita_loop/mod.rs index 9aeb3f3c8..067bfc5c4 100644 --- a/rita_exit/src/rita_loop/mod.rs +++ b/rita_exit/src/rita_loop/mod.rs @@ -10,12 +10,10 @@ //! Two threads are generated by this, one actual worker thread and a watchdog restarting thread that only //! wakes up to restart the inner thread if anything goes wrong. -use crate::{get_database_connection, network_endpoints::*, RitaExitError}; +use crate::{get_all_regsitered_clients, network_endpoints::*}; -use crate::database::struct_tools::clients_to_ids; use crate::database::{ - cleanup_exit_clients, enforce_exit_clients, setup_clients, validate_clients_region, - ExitClientSetupStates, + enforce_exit_clients, setup_clients, validate_clients_region, ExitClientSetupStates, }; use crate::traffic_watcher::watch_exit_traffic; use actix_async::System as AsyncSystem; @@ -25,11 +23,7 @@ use althea_kernel_interface::ExitClient; use althea_types::{Identity, WgKey}; use babel_monitor::{open_babel_stream, parse_routes}; -use diesel::{query_dsl::RunQueryDsl, PgConnection}; -use exit_db::models; -use exit_db::schema::clients::internet_ipv6; use rita_common::debt_keeper::DebtAction; -use settings::{get_rita_exit, set_rita_exit, write_config}; use std::collections::{HashMap, HashSet}; use std::sync::{Arc, RwLock}; @@ -64,6 +58,8 @@ pub struct RitaExitCache { wg_exit_clients: HashSet, // cache of b20 routers we have successful rules and routes for wg_exit_v2_clients: HashSet, + // A blacklist of clients that we fail geoip verification for. We tear down these routes + geoip_blacklist: Vec, } pub type ExitLock = Arc>>; @@ -112,118 +108,78 @@ pub fn start_rita_exit_loop() { fn rita_exit_loop(rita_exit_cache: RitaExitCache, usage_history: ExitLock) -> RitaExitCache { let mut rita_exit_cache = rita_exit_cache; let start = Instant::now(); - // opening a database connection takes at least several milliseconds, as the database server - // may be across the country, so to save on back and forth we open on and reuse it as much - // as possible - match get_database_connection() { - Ok(conn) => { - use exit_db::schema::clients::dsl::clients; - let babel_port = settings::get_rita_exit().network.babel_port; - info!( - "Exit tick! got DB connection after {}ms", - start.elapsed().as_millis(), - ); - - // Resets all ipv6 data in database - if let Err(e) = recompute_ipv6_if_needed(&conn) { - error!("IPV6 Error: Unable to reset databases: {:?}", e); - }; - - let get_clients = Instant::now(); - if let Ok(clients_list) = clients.load::(&conn) { - info!( - "Finished Rita get clients, got {:?} clients in {}ms", - clients_list.len(), - get_clients.elapsed().as_millis() - ); - let ids = clients_to_ids(clients_list.clone()); - - let start_bill = Instant::now(); - // watch and bill for traffic - bill(babel_port, start, ids, usage_history); - info!( - "Finished Rita billing in {}ms", - start_bill.elapsed().as_millis() - ); - - info!("about to setup clients"); - let start_setup = Instant::now(); - // Create and update client tunnels - match setup_clients( - &clients_list, - ExitClientSetupStates { - old_clients: rita_exit_cache.wg_clients.clone(), - wg_exit_clients: rita_exit_cache.wg_exit_clients.clone(), - wg_exit_v2_clients: rita_exit_cache.wg_exit_v2_clients.clone(), - }, - ) { - Ok(client_states) => { - rita_exit_cache.successful_setup = true; - rita_exit_cache.wg_clients = client_states.old_clients; - rita_exit_cache.wg_exit_clients = client_states.wg_exit_clients; - rita_exit_cache.wg_exit_v2_clients = client_states.wg_exit_v2_clients; - } - Err(e) => error!("Setup clients failed with {:?}", e), - } - info!( - "Finished Rita setting up clients in {}ms", - start_setup.elapsed().as_millis() - ); - - let start_cleanup = Instant::now(); - info!("about to cleanup clients"); - // find users that have not been active within the configured time period - // and remove them from the db - if let Err(e) = cleanup_exit_clients(&clients_list, &conn) { - error!("Exit client cleanup failed with {:?}", e); - } - info!( - "Finished Rita cleaning clients in {}ms", - start_cleanup.elapsed().as_millis() - ); - - // Make sure no one we are setting up is geoip unauthorized - let start_region = Instant::now(); - info!("about to check regions"); - check_regions(start, clients_list.clone(), &conn); - info!( - "Finished Rita checking region in {}ms", - start_region.elapsed().as_millis() - ); - - info!("About to enforce exit clients"); - // handle enforcement on client tunnels by querying debt keeper - // this consumes client list - let start_enforce = Instant::now(); - match enforce_exit_clients(clients_list, &rita_exit_cache.debt_actions) { - Ok(new_debt_actions) => rita_exit_cache.debt_actions = new_debt_actions, - Err(e) => warn!("Failed to enforce exit clients with {:?}", e,), - } - info!( - "Finished Rita enforcement in {}ms ", - start_enforce.elapsed().as_millis() - ); - info!( - "Finished Rita exit loop in {}ms, all vars should be dropped", - start.elapsed().as_millis(), - ); - } - } - Err(e) => { - error!("Failed to get database connection with {}", e); - if !rita_exit_cache.successful_setup { - let db_uri = settings::get_rita_exit().db_uri; - let message = format!( - "Failed to get database connection to {db_uri} on first setup loop, the exit can not operate without the ability to get the clients list from the database exiting" - ); - error!("{}", message); - let sys = AsyncSystem::current(); - sys.stop(); - panic!("{}", message); - } + let babel_port = settings::get_rita_exit().network.babel_port; + + let get_clients_benchmark = Instant::now(); + let reg_clients_list = get_all_regsitered_clients(); + info!( + "Finished Rita get clients, got {:?} clients in {}ms", + reg_clients_list.len(), + get_clients_benchmark.elapsed().as_millis() + ); + + let ids = reg_clients_list.clone(); + let start_bill_benchmark = Instant::now(); + // watch and bill for traffic + bill(babel_port, start, ids, usage_history); + info!( + "Finished Rita billing in {}ms", + start_bill_benchmark.elapsed().as_millis() + ); + + info!("About to setup clients"); + let start_setup_benchmark = Instant::now(); + // Create and update client tunnels + match setup_clients( + reg_clients_list.clone(), + rita_exit_cache.geoip_blacklist.clone(), + ExitClientSetupStates { + old_clients: rita_exit_cache.wg_clients.clone(), + wg_exit_clients: rita_exit_cache.wg_exit_clients.clone(), + wg_exit_v2_clients: rita_exit_cache.wg_exit_v2_clients.clone(), + }, + ) { + Ok(client_states) => { + rita_exit_cache.successful_setup = true; + rita_exit_cache.wg_clients = client_states.old_clients; + rita_exit_cache.wg_exit_clients = client_states.wg_exit_clients; + rita_exit_cache.wg_exit_v2_clients = client_states.wg_exit_v2_clients; } + Err(e) => error!("Setup clients failed with {:?}", e), } + info!( + "Finished Rita setting up clients in {}ms", + start_setup_benchmark.elapsed().as_millis() + ); + + // Make sure no one we are setting up is geoip unauthorized + let start_region_benchmark = Instant::now(); + info!("about to check regions"); + if let Some(list) = check_regions(start, reg_clients_list.clone()) { + rita_exit_cache.geoip_blacklist = list; + } + info!( + "Finished Rita checking region in {}ms", + start_region_benchmark.elapsed().as_millis() + ); + info!("About to enforce exit clients"); + // handle enforcement on client tunnels by querying debt keeper + // this consumes client list + let start_enforce_benchmark = Instant::now(); + match enforce_exit_clients(reg_clients_list, &rita_exit_cache.debt_actions.clone()) { + Ok(new_debt_actions) => rita_exit_cache.debt_actions = new_debt_actions, + Err(e) => warn!("Failed to enforce exit clients with {:?}", e,), + } + info!( + "Finished Rita enforcement in {}ms ", + start_enforce_benchmark.elapsed().as_millis() + ); + info!( + "Finished Rita exit loop in {}ms, all vars should be dropped", + start.elapsed().as_millis(), + ); + thread::sleep(EXIT_LOOP_SPEED_DURATION); rita_exit_cache } @@ -266,60 +222,31 @@ fn bill(babel_port: u16, start: Instant, ids: Vec, usage_history: Exit } } -fn check_regions(start: Instant, clients_list: Vec, conn: &PgConnection) { +/// Run a region validation and return a list of blacklisted clients. This list is later used +/// in setup clients to teardown blacklisted client tunnels +fn check_regions(start: Instant, clients_list: Vec) -> Option> { let val = settings::get_rita_exit().allowed_countries.is_empty(); if !val { - let res = validate_clients_region(clients_list, conn); + let res = validate_clients_region(clients_list); match res { - Err(e) => warn!( - "Failed to validate client region with {:?} {}ms since start", - e, - start.elapsed().as_millis() - ), - Ok(_) => info!( - "validate client region completed successfully {}ms since loop start", - start.elapsed().as_millis() - ), - } - } -} - -/// When the ipv6 database gets into an invalid state, we can have unexpected behaviors if rita exit doesnt -/// crash. This function checks if a config variable is set; if it is, clear out ipv6 database and let it recompute -fn recompute_ipv6_if_needed(conn: &PgConnection) -> Result<(), Box> { - use diesel::ExpressionMethods; - use exit_db::schema::assigned_ips::dsl::assigned_ips; - use exit_db::schema::clients::dsl::clients; - let mut rita_exit = get_rita_exit(); - let recompute = rita_exit.exit_network.recompute_ipv6; - info!("About to call recompute with : {:?}", recompute); - if recompute { - info!("Reseting IPV6 databases"); - - // Reseting client ipv6 column - let empty_str = ""; - if let Err(e) = diesel::update(clients) - .set(internet_ipv6.eq(empty_str)) - .execute(conn) - { - return Err(Box::new(e.into())); - }; - - // Reseting assigned_ips - if let Err(e) = diesel::delete(assigned_ips).execute(conn) { - return Err(Box::new(e.into())); - }; - - // Set recompute ipv6 to false - rita_exit.exit_network.recompute_ipv6 = false; - set_rita_exit(rita_exit); - - if let Err(e) = write_config() { - error!("Unable to write to config with: {:?}", e); + Err(e) => { + warn!( + "Failed to validate client region with {:?} {}ms since start", + e, + start.elapsed().as_millis() + ); + return None; + } + Ok(blacklist) => { + info!( + "validate client region completed successfully {}ms since loop start", + start.elapsed().as_millis() + ); + return Some(blacklist); + } } } - - Ok(()) + None } fn setup_exit_wg_tunnel() { diff --git a/settings/Cargo.toml b/settings/Cargo.toml index ecccadb44..8b7826dd7 100644 --- a/settings/Cargo.toml +++ b/settings/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Ben "] edition = "2018" [dependencies] -althea_types = { path = "../althea_types"} +althea_types = { path = "../althea_types" } althea_kernel_interface = { path = "../althea_kernel_interface" } auto-bridge = { path = "../auto_bridge" } num256 = "0.5" @@ -16,9 +16,8 @@ toml = "0.5" log = "0.4" lazy_static = "1.4" clarity = "1.2" -arrayvec = {version= "0.7", features = ["serde"]} +arrayvec = { version = "0.7", features = ["serde"] } phonenumber = "0.3" ipnetwork = "0.20" -deep_space = {workspace = true} -[features] \ No newline at end of file +[features] diff --git a/settings/example_exit.toml b/settings/example_exit.toml index 49f8407f8..ae636e0cd 100644 --- a/settings/example_exit.toml +++ b/settings/example_exit.toml @@ -56,7 +56,7 @@ wg_public_key = "bvM10HW73yePrxdtCQQ4U20W5ogogdiZtUihrPc/oGY=" type = "Email" [verif_settings.contents] -email_cooldown=60 +email_cooldown = 60 from_address = "verification@example.com" smtp_url = "smtp.fastmail.com" smtp_domain = "mail.example.com" diff --git a/settings/src/exit.rs b/settings/src/exit.rs index 63087fe32..2ed02a759 100644 --- a/settings/src/exit.rs +++ b/settings/src/exit.rs @@ -5,7 +5,6 @@ use crate::{json_merge, set_rita_exit, setup_accepted_denoms, SettingsError}; use althea_types::{Identity, WgKey}; use core::str::FromStr; use ipnetwork::IpNetwork; -use phonenumber::PhoneNumber; use std::collections::HashSet; use std::net::Ipv4Addr; use std::path::Path; @@ -23,18 +22,12 @@ pub struct ExitNetworkSettings { pub exit_price: u64, /// This is the exit's own ip/gateway ip in the exit wireguard tunnel pub own_internal_ip: Ipv4Addr, - /// This is the start of the exit tunnel's internal address allocation to clients, incremented - /// by 1 every time a new client is added - pub exit_start_ip: Ipv4Addr, /// The netmask, in bits to mask out, for the exit tunnel pub netmask: u8, /// The subnet we use to assign to client routers for ipv6 pub subnet: Option, /// The specified client subnet, else use /56 pub client_subnet_size: Option, - /// Time in seconds before user is dropped from the db due to inactivity - /// 0 means disabled - pub entry_timeout: u32, /// api credentials for Maxmind geoip pub geoip_api_user: Option, pub geoip_api_key: Option, @@ -45,13 +38,6 @@ pub struct ExitNetworkSettings { pub wg_private_key: WgKey, /// path for the exit tunnel keyfile must be distinct from the common tunnel path! pub wg_private_key_path: String, - /// Magic phone number operators enter in order to register to exit without auth - pub magic_phone_number: Option, - /// Lists of exit ip addrs in this cluster - pub cluster_exits: Vec, - /// when this is set, clear out all ipv6 entries from database - #[serde(default = "recompute_ipv6_default")] - pub recompute_ipv6: bool, /// password that operator tools uses to verify that this is an exit pub pass: Option, /// Determines if enforcement is ensabled on the wg_exit interfaces, the htb classifier used here @@ -65,10 +51,6 @@ fn enable_enforcement_default() -> bool { true } -fn recompute_ipv6_default() -> bool { - false -} - impl ExitNetworkSettings { /// Generates a configuration that can be used in integration tests, does not use the /// default trait to prevent some future code from picking up on the 'default' implementation @@ -80,20 +62,15 @@ impl ExitNetworkSettings { wg_v2_tunnel_port: 59998, exit_price: 10, own_internal_ip: "172.16.255.254".parse().unwrap(), - exit_start_ip: "172.16.0.0".parse().unwrap(), netmask: 12, subnet: Some(IpNetwork::V6("ff01::0/128".parse().unwrap())), client_subnet_size: None, - entry_timeout: 0, geoip_api_user: None, geoip_api_key: None, wg_public_key: WgKey::from_str("Ha2YlTfDimJNboqxOSCh6M29W/H0jKtB4utitjaTO3A=").unwrap(), wg_private_key: WgKey::from_str("mFFBLqQYrycxfHo10P9l8I2G7zbw8tia4WkGGgjGCn8=") .unwrap(), wg_private_key_path: String::new(), - magic_phone_number: None, - cluster_exits: Vec::new(), - recompute_ipv6: false, pass: None, enable_enforcement: true, } @@ -124,6 +101,9 @@ fn default_remote_log() -> bool { fn default_save_interval() -> u64 { 300 } +pub fn default_reg_url() -> String { + "https://operator.althea.net:8080/register_router".to_string() +} /// These are the settings for email verification #[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Default)] @@ -167,39 +147,14 @@ pub struct EmailVerifSettings { pub notify_low_balance: bool, } -/// These are the settings for text message verification using the twillio api -/// note that while you would expect the authentication and text notification flow -/// to be the same they are in fact totally different and each have separate -/// credentials below -#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Default)] -pub struct PhoneVerifSettings { - /// API key used for the authentication calls - pub auth_api_key: String, - /// The Twillio number used to send the notification message - pub notification_number: String, - /// The Twillio account id used to authenticate for notifications - pub twillio_account_id: String, - /// The auth token used to authenticate for notifications - pub twillio_auth_token: String, - /// Operator notification numbers, used to text the operators when we need them - #[serde(default)] - pub operator_notification_number: Vec, -} - -/// Struct containing the different types of supported verification -/// and their respective settings -#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] -#[serde(tag = "type", content = "contents")] -pub enum ExitVerifSettings { - Email(EmailVerifSettings), - Phone(PhoneVerifSettings), -} - /// This is the main settings struct for rita_exit #[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] pub struct RitaExitSettingsStruct { /// starts with file:// or postgres://username:password@localhost/diesel_demo pub db_uri: String, + /// url exit uses to request a clients registration + #[serde(default = "default_reg_url")] + pub client_registration_url: String, /// the size of the worker thread pool, the connection pool is this plus one pub workers: u32, /// if we should log remotely or if we should send our logs to the logging server @@ -216,10 +171,6 @@ pub struct RitaExitSettingsStruct { /// (ISO country code) #[serde(skip_serializing_if = "HashSet::is_empty", default)] pub allowed_countries: HashSet, - #[serde(skip_serializing_if = "Option::is_none")] - pub verif_settings: Option, - #[serde(skip)] - pub future: bool, /// The save interval defaults to 5 minutes for exit settings represented in seconds #[serde(default = "default_save_interval")] pub save_interval: u64, @@ -231,6 +182,7 @@ impl RitaExitSettingsStruct { pub fn test_default() -> Self { RitaExitSettingsStruct { db_uri: "".to_string(), + client_registration_url: "".to_string(), workers: 1, remote_log: false, description: "".to_string(), @@ -239,8 +191,6 @@ impl RitaExitSettingsStruct { network: NetworkSettings::default(), exit_network: ExitNetworkSettings::test_default(), allowed_countries: HashSet::new(), - verif_settings: None, - future: false, save_interval: default_save_interval(), } } diff --git a/settings/test_exit.toml b/settings/test_exit.toml index 92f32eaaf..476684050 100644 --- a/settings/test_exit.toml +++ b/settings/test_exit.toml @@ -59,7 +59,7 @@ type = "Email" [verif_settings.contents] test = true -email_cooldown=60 +email_cooldown = 60 test_dir = "mail" from_address = "email-verif@example.com" balance_notification_interval = 600