feature: hopefully final steps of the smoosh™️ (#5201)

* removed mnemonic from gateway config struct

scaffolding for common mixnet listener

running verloc unconditionally in a nym-node

remove filtering by mixnode

extracted verloc to separate crate

integrated nym-node-http-server more tightly with the binary

most logic for handling forward packets

running all mixnode-related tasks natively inside nymnode

removed gateway storage trait in favour of the only concrete implementation

most logic for handling final hop packets

using nym-node owned socket listener for gateways

utility for sending plain message through mixnet + gateway fix

using common packet forwarding in both modes

nifying nym-node metrics

reproduce behaviour of the console logger

cleaned up cli args

redesigned gateway tasks startup procedure

removing dead code

scaffolding for old config v6

config migration

implemented MixnetMetricsCleaner

* clippy

* require entry/exit for wireguard

* removed dead code in migration code

* updated config template

* use custom user agent for verloc queries

* fixed premature shutdown of gateway tasks

* hidden nym-api flag to allow illegal node ips

* experiment: final hop handing with wireguard

* added additional startup logs

* typo

* fixed legacy stats endpoint data

* additional logs

* apply review comments

* fixed local testnet manager
This commit is contained in:
Jędrzej Stuczyński
2024-12-05 17:21:36 +00:00
committed by GitHub
parent d1f702c4aa
commit 5a07b73375
201 changed files with 7434 additions and 7071 deletions
Generated
+139 -123
View File
@@ -11,7 +11,7 @@ dependencies = [
"macroific",
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -283,7 +283,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -352,7 +352,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -363,7 +363,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -573,7 +573,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -1140,7 +1140,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -1790,7 +1790,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -1948,7 +1948,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim 0.11.1",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -1970,7 +1970,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178"
dependencies = [
"darling_core 0.20.9",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -2021,7 +2021,7 @@ dependencies = [
"macroific",
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -2064,7 +2064,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -2097,7 +2097,7 @@ dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -2165,7 +2165,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -2531,7 +2531,7 @@ dependencies = [
"macroific",
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -2765,7 +2765,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -2860,7 +2860,7 @@ dependencies = [
"proc-macro-error2",
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -3248,6 +3248,12 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "human-repr"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f58b778a5761513caf593693f8951c97a5b610841e754788400f32102eefdff1"
[[package]]
name = "humantime"
version = "1.3.0"
@@ -3822,9 +3828,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.162"
version = "0.2.167"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
[[package]]
name = "libm"
@@ -3950,7 +3956,7 @@ dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -3961,7 +3967,7 @@ checksum = "13198c120864097a565ccb3ff947672d969932b7975ebd4085732c9f09435e55"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -3974,7 +3980,7 @@ dependencies = [
"macroific_core",
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -4128,6 +4134,21 @@ dependencies = [
"wasm-utils",
]
[[package]]
name = "mixnet-connectivity-check"
version = "0.1.0"
dependencies = [
"anyhow",
"clap 4.5.20",
"futures",
"nym-bin-common",
"nym-crypto",
"nym-network-defaults",
"nym-sdk",
"tokio",
"tracing",
]
[[package]]
name = "moka"
version = "0.12.8"
@@ -4431,7 +4452,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -4851,6 +4872,7 @@ dependencies = [
"nym-gateway-requests",
"nym-id",
"nym-metrics",
"nym-mixnet-client",
"nym-network-defaults",
"nym-nonexhaustive-delayqueue",
"nym-pemstore",
@@ -5443,7 +5465,7 @@ dependencies = [
"nym-mixnode-common",
"nym-network-defaults",
"nym-network-requester",
"nym-node-http-api",
"nym-node-metrics",
"nym-sdk",
"nym-sphinx",
"nym-statistics-common",
@@ -5535,9 +5557,11 @@ name = "nym-gateway-stats-storage"
version = "0.1.0"
dependencies = [
"nym-credentials-interface",
"nym-node-metrics",
"nym-sphinx",
"nym-statistics-common",
"sqlx",
"strum 0.26.3",
"thiserror",
"time",
"tokio",
@@ -5548,7 +5572,6 @@ dependencies = [
name = "nym-gateway-storage"
version = "0.1.0"
dependencies = [
"async-trait",
"bincode",
"defguard_wireguard_rs",
"log",
@@ -5741,11 +5764,11 @@ name = "nym-mixnet-client"
version = "0.1.0"
dependencies = [
"futures",
"log",
"nym-sphinx",
"nym-task",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
@@ -5772,35 +5795,6 @@ dependencies = [
"utoipa",
]
[[package]]
name = "nym-mixnode"
version = "1.1.37"
dependencies = [
"colored",
"futures",
"nym-contracts-common",
"nym-crypto",
"nym-http-api-common",
"nym-metrics",
"nym-mixnet-client",
"nym-mixnode-common",
"nym-node-http-api",
"nym-nonexhaustive-delayqueue",
"nym-sphinx",
"nym-sphinx-params",
"nym-sphinx-types",
"nym-task",
"nym-topology",
"nym-types",
"nym-validator-client",
"thiserror",
"time",
"tokio",
"tokio-util",
"tracing",
"url",
]
[[package]]
name = "nym-mixnode-common"
version = "0.1.0"
@@ -5809,11 +5803,9 @@ dependencies = [
"futures",
"humantime-serde",
"log",
"nym-bin-common",
"nym-crypto",
"nym-metrics",
"nym-network-defaults",
"nym-node-http-api",
"nym-sphinx-acknowledgements",
"nym-sphinx-addressing",
"nym-sphinx-forwarding",
@@ -5821,7 +5813,6 @@ dependencies = [
"nym-sphinx-params",
"nym-sphinx-types",
"nym-task",
"nym-validator-client",
"rand",
"serde",
"thiserror",
@@ -5946,6 +5937,9 @@ name = "nym-node"
version = "1.1.12"
dependencies = [
"anyhow",
"async-trait",
"axum 0.7.7",
"axum-extra",
"bip39",
"bs58",
"cargo_metadata 0.18.1",
@@ -5953,6 +5947,10 @@ dependencies = [
"clap 4.5.20",
"colored",
"cupid",
"dashmap",
"futures",
"headers",
"human-repr",
"humantime-serde",
"ipnetwork 0.20.0",
"nym-authenticator",
@@ -5961,63 +5959,60 @@ dependencies = [
"nym-config",
"nym-crypto",
"nym-gateway",
"nym-gateway-stats-storage",
"nym-http-api-common",
"nym-ip-packet-router",
"nym-mixnode",
"nym-metrics",
"nym-mixnet-client",
"nym-network-requester",
"nym-node-http-api",
"nym-node-metrics",
"nym-node-requests",
"nym-nonexhaustive-delayqueue",
"nym-pemstore",
"nym-sphinx-acknowledgements",
"nym-sphinx-addressing",
"nym-sphinx-forwarding",
"nym-sphinx-framing",
"nym-sphinx-types",
"nym-task",
"nym-topology",
"nym-types",
"nym-validator-client",
"nym-verloc",
"nym-wireguard",
"nym-wireguard-types",
"rand",
"semver 1.0.23",
"serde",
"serde_json",
"si-scale",
"sysinfo",
"thiserror",
"time",
"tokio",
"tokio-util",
"toml 0.8.14",
"tower-http",
"tracing",
"tracing-subscriber",
"url",
"utoipa",
"utoipa-swagger-ui",
"zeroize",
]
[[package]]
name = "nym-node-http-api"
name = "nym-node-metrics"
version = "0.1.0"
dependencies = [
"axum 0.7.7",
"axum-extra",
"base64 0.22.1",
"colored",
"dashmap",
"fastrand 2.1.1",
"headers",
"hmac",
"hyper 1.4.1",
"ipnetwork 0.20.0",
"nym-crypto",
"nym-http-api-common",
"futures",
"nym-metrics",
"nym-node-requests",
"nym-task",
"nym-wireguard",
"rand",
"serde_json",
"thiserror",
"nym-statistics-common",
"strum 0.26.3",
"time",
"tokio",
"tower 0.4.13",
"tower-http",
"tracing",
"utoipa",
"utoipa-swagger-ui",
"x25519-dalek",
]
[[package]]
@@ -6076,6 +6071,7 @@ dependencies = [
"nym-crypto",
"nym-explorer-client",
"nym-network-defaults",
"nym-node-metrics",
"nym-node-requests",
"nym-node-status-client",
"nym-serde-helpers",
@@ -6812,6 +6808,25 @@ dependencies = [
"zeroize",
]
[[package]]
name = "nym-verloc"
version = "0.1.0"
dependencies = [
"bytes",
"futures",
"humantime 2.1.0",
"nym-crypto",
"nym-task",
"nym-validator-client",
"rand",
"thiserror",
"time",
"tokio",
"tokio-util",
"tracing",
"url",
]
[[package]]
name = "nym-vesting-contract-common"
version = "0.7.0"
@@ -6893,6 +6908,7 @@ dependencies = [
"time",
"tokio",
"tokio-stream",
"tracing",
"x25519-dalek",
]
@@ -7024,9 +7040,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.104"
version = "0.9.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
dependencies = [
"cc",
"libc",
@@ -7226,7 +7242,7 @@ dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -7313,7 +7329,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -7354,7 +7370,7 @@ checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -7569,14 +7585,14 @@ dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
name = "proc-macro2"
version = "1.0.89"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
@@ -7589,7 +7605,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
"version_check",
"yansi",
]
@@ -7652,7 +7668,7 @@ dependencies = [
"itertools 0.12.1",
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -7885,7 +7901,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -8112,7 +8128,7 @@ dependencies = [
"proc-macro2",
"quote",
"rocket_http",
"syn 2.0.87",
"syn 2.0.90",
"unicode-xid",
"version_check",
]
@@ -8238,7 +8254,7 @@ dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.87",
"syn 2.0.90",
"walkdir",
]
@@ -8294,9 +8310,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.40"
version = "0.38.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6"
dependencies = [
"bitflags 2.5.0",
"errno",
@@ -8487,7 +8503,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals 0.29.1",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -8519,7 +8535,7 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -8651,7 +8667,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -8662,7 +8678,7 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -8673,7 +8689,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -8735,7 +8751,7 @@ checksum = "aafbefbe175fa9bf03ca83ef89beecff7d2a95aaacd5732325b90ac8c3bd7b90"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -8756,7 +8772,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -8807,7 +8823,7 @@ dependencies = [
"darling 0.20.9",
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -9406,7 +9422,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -9449,9 +9465,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.87"
version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
@@ -9757,7 +9773,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -9888,7 +9904,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -10167,7 +10183,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -10331,7 +10347,7 @@ checksum = "0ea0b99e8ec44abd6f94a18f28f7934437809dd062820797c52401298116f70e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
"termcolor",
]
@@ -10358,7 +10374,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals 0.28.0",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -10561,7 +10577,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55137c122f712d9330fd985d66fa61bdc381752e89c35708c13ce63049a3002c"
dependencies = [
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -10593,7 +10609,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
"syn 2.0.87",
"syn 2.0.90",
"toml 0.5.11",
"uniffi_build",
"uniffi_meta",
@@ -10725,7 +10741,7 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
"syn 2.0.87",
"syn 2.0.90",
"uuid",
]
@@ -10764,7 +10780,7 @@ checksum = "17e82ab96c5a55263b5bed151b8426410d93aa909a453acdbd4b6792b5af7d64"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -10775,7 +10791,7 @@ checksum = "86b8338dc3c9526011ffaa2aa6bd60ddfda9d49d2123108690755c6e34844212"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
"utoipauto-core",
]
@@ -10882,7 +10898,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
"wasm-bindgen-shared",
]
@@ -10916,7 +10932,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -10950,7 +10966,7 @@ checksum = "4b8220be1fa9e4c889b30fd207d4906657e7e90b12e0e6b0c8b8d8709f5de021"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -11478,7 +11494,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
@@ -11498,7 +11514,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"syn 2.0.90",
]
[[package]]
+3 -5
View File
@@ -104,7 +104,6 @@ members = [
"explorer-api/explorer-client",
"gateway",
"integrations/bity",
"mixnode",
"sdk/ffi/cpp",
"sdk/ffi/go",
"sdk/ffi/shared",
@@ -123,8 +122,8 @@ members = [
"nym-data-observatory",
"nym-network-monitor",
"nym-node",
"nym-node/nym-node-http-api",
"nym-node/nym-node-requests",
"nym-node/nym-node-metrics",
"nym-node-status-api/nym-node-status-agent",
"nym-node-status-api/nym-node-status-api",
"nym-node-status-api/nym-node-status-client",
@@ -149,15 +148,13 @@ members = [
"tools/internal/contract-state-importer/importer-cli",
"tools/internal/contract-state-importer/importer-contract",
"tools/internal/testnet-manager",
"tools/internal/testnet-manager/dkg-bypass-contract",
"tools/internal/testnet-manager/dkg-bypass-contract", "common/verloc", "tools/internal/mixnet-connectivity-check",
]
default-members = [
"clients/native",
"clients/socks5",
"explorer-api",
"gateway",
"mixnode",
"nym-api",
"nym-credential-proxy/nym-credential-proxy",
"nym-data-observatory",
@@ -263,6 +260,7 @@ http-body-util = "0.1"
httpcodec = "0.2.3"
humantime = "2.1.0"
humantime-serde = "1.1.1"
human-repr = "1.1.0"
hyper = "1.4.1"
hyper-util = "0.1"
indicatif = "0.17.8"
+1
View File
@@ -46,6 +46,7 @@ nym-sphinx = { path = "../nymsphinx" }
nym-statistics-common = { path = "../statistics" }
nym-pemstore = { path = "../pemstore" }
nym-topology = { path = "../topology", features = ["serializable"] }
nym-mixnet-client = { path = "../client-libs/mixnet-client", default-features = false }
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
nym-task = { path = "../task" }
nym-credentials-interface = { path = "../credentials-interface" }
@@ -14,7 +14,7 @@ use std::os::raw::c_int as RawFd;
use thiserror::Error;
#[cfg(not(target_arch = "wasm32"))]
use futures::channel::{mpsc, oneshot};
use futures::channel::oneshot;
// we need to type erase the error type since we can't have dynamic associated types alongside dynamic dispatch
#[derive(Debug, Error)]
@@ -170,7 +170,7 @@ pub struct LocalGateway {
// 'sender' part
/// Channel responsible for taking mix packets and forwarding them further into the further mixnet layers.
packet_forwarder: mpsc::UnboundedSender<MixPacket>,
packet_forwarder: nym_mixnet_client::forwarder::MixForwardingSender,
// 'receiver' part
packet_router_tx: Option<oneshot::Sender<PacketRouter>>,
@@ -180,7 +180,7 @@ pub struct LocalGateway {
impl LocalGateway {
pub fn new(
local_identity: identity::PublicKey,
packet_forwarder: mpsc::UnboundedSender<MixPacket>,
packet_forwarder: nym_mixnet_client::forwarder::MixForwardingSender,
packet_router_tx: oneshot::Sender<PacketRouter>,
) -> Self {
LocalGateway {
@@ -208,8 +208,7 @@ mod nonwasm_sealed {
impl GatewaySender for LocalGateway {
async fn send_mix_packet(&mut self, packet: MixPacket) -> Result<(), ErasedGatewayError> {
self.packet_forwarder
.unbounded_send(packet)
.map_err(|err| err.into_send_error())
.forward_packet(packet)
.map_err(erase_err)
}
}
+8 -4
View File
@@ -9,10 +9,14 @@ license.workspace = true
[dependencies]
futures = { workspace = true }
log = { workspace = true }
tokio = { workspace = true, features = ["time", "net", "rt"] }
tokio-util = { workspace = true, features = ["codec"] }
tracing = { workspace = true }
tokio = { workspace = true, features = ["time"] }
tokio-util = { workspace = true, features = ["codec"], optional = true }
# internal
nym-sphinx = { path = "../../nymsphinx" }
nym-task = { path = "../../task" }
nym-task = { path = "../../task", optional = true }
[features]
default = ["client"]
client = ["tokio-util", "nym-task", "tokio/net", "tokio/rt"]
@@ -3,7 +3,6 @@
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
use nym_sphinx::framing::codec::NymCodec;
use nym_sphinx::framing::packet::FramedNymPacket;
@@ -18,13 +17,14 @@ use std::time::Duration;
use tokio::net::TcpStream;
use tokio::time::sleep;
use tokio_util::codec::Framed;
use tracing::*;
#[derive(Clone, Copy)]
pub struct Config {
initial_reconnection_backoff: Duration,
maximum_reconnection_backoff: Duration,
initial_connection_timeout: Duration,
maximum_connection_buffer_size: usize,
use_legacy_version: bool,
}
impl Config {
@@ -33,14 +33,12 @@ impl Config {
maximum_reconnection_backoff: Duration,
initial_connection_timeout: Duration,
maximum_connection_buffer_size: usize,
use_legacy_version: bool,
) -> Self {
Config {
initial_reconnection_backoff,
maximum_reconnection_backoff,
initial_connection_timeout,
maximum_connection_buffer_size,
use_legacy_version,
}
}
}
@@ -200,9 +198,8 @@ impl SendWithoutResponse for Client {
packet: NymPacket,
packet_type: PacketType,
) -> io::Result<()> {
trace!("Sending packet to {:?}", address);
let framed_packet =
FramedNymPacket::new(packet, packet_type, self.config.use_legacy_version);
trace!("Sending packet to {address:?}");
let framed_packet = FramedNymPacket::new(packet, packet_type);
if let Some(sender) = self.conn_new.get_mut(&address) {
if let Err(err) = sender.channel.try_send(framed_packet) {
@@ -260,7 +257,6 @@ mod tests {
maximum_reconnection_backoff: Duration::from_millis(300_000),
initial_connection_timeout: Duration::from_millis(1_500),
maximum_connection_buffer_size: 128,
use_legacy_version: false,
})
}
@@ -1,77 +1,72 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::{Client, Config, SendWithoutResponse};
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use futures::channel::mpsc::SendError;
use nym_sphinx::forwarding::packet::MixPacket;
use std::time::Duration;
use tokio::time::Instant;
pub type MixForwardingSender = mpsc::UnboundedSender<MixPacket>;
type MixForwardingReceiver = mpsc::UnboundedReceiver<MixPacket>;
/// A specialisation of client such that it forwards any received packets on the channel into the
/// mix network immediately, i.e. will not try to listen for any responses.
pub struct PacketForwarder {
mixnet_client: Client,
packet_receiver: MixForwardingReceiver,
shutdown: nym_task::TaskClient,
pub fn mix_forwarding_channels() -> (MixForwardingSender, MixForwardingReceiver) {
let (tx, rx) = mpsc::unbounded();
(tx.into(), rx)
}
impl PacketForwarder {
pub fn new(
initial_reconnection_backoff: Duration,
maximum_reconnection_backoff: Duration,
initial_connection_timeout: Duration,
maximum_connection_buffer_size: usize,
use_legacy_version: bool,
shutdown: nym_task::TaskClient,
) -> (PacketForwarder, MixForwardingSender) {
let client_config = Config::new(
initial_reconnection_backoff,
maximum_reconnection_backoff,
initial_connection_timeout,
maximum_connection_buffer_size,
use_legacy_version,
);
#[derive(Clone)]
pub struct MixForwardingSender(mpsc::UnboundedSender<PacketToForward>);
let (packet_sender, packet_receiver) = mpsc::unbounded();
impl From<mpsc::UnboundedSender<PacketToForward>> for MixForwardingSender {
fn from(tx: mpsc::UnboundedSender<PacketToForward>) -> Self {
MixForwardingSender(tx)
}
}
(
PacketForwarder {
mixnet_client: Client::new(client_config),
packet_receiver,
shutdown,
},
packet_sender,
)
impl MixForwardingSender {
pub fn forward_packet(&self, packet: impl Into<PacketToForward>) -> Result<(), SendError> {
self.0
.unbounded_send(packet.into())
.map_err(|err| err.into_send_error())
}
pub async fn run(&mut self) {
while !self.shutdown.is_shutdown() {
tokio::select! {
biased;
_ = self.shutdown.recv() => {
log::trace!("PacketForwarder: Received shutdown");
}
Some(mix_packet) = self.packet_receiver.next() => {
trace!("Going to forward packet to {}", mix_packet.next_hop());
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.0.len()
}
}
let next_hop = mix_packet.next_hop();
let packet_type = mix_packet.packet_type();
let packet = mix_packet.into_packet();
// we don't care about responses, we just want to fire packets
// as quickly as possible
pub type MixForwardingReceiver = mpsc::UnboundedReceiver<PacketToForward>;
if let Err(err) =
self.mixnet_client
.send_without_response(next_hop, packet, packet_type)
{
debug!("failed to forward the packet - {err}")
}
}
}
pub struct PacketToForward {
pub packet: MixPacket,
pub forward_delay_target: Option<Instant>,
}
impl From<MixPacket> for PacketToForward {
fn from(packet: MixPacket) -> Self {
PacketToForward::new_no_delay(packet)
}
}
impl From<(MixPacket, Option<Instant>)> for PacketToForward {
fn from((packet, delay_until): (MixPacket, Option<Instant>)) -> Self {
PacketToForward::new(packet, delay_until)
}
}
impl From<(MixPacket, Instant)> for PacketToForward {
fn from((packet, delay_until): (MixPacket, Instant)) -> Self {
PacketToForward::new(packet, Some(delay_until))
}
}
impl PacketToForward {
pub fn new(packet: MixPacket, forward_delay_target: Option<Instant>) -> Self {
PacketToForward {
packet,
forward_delay_target,
}
}
pub fn new_no_delay(packet: MixPacket) -> Self {
Self::new(packet, None)
}
}
@@ -1,7 +1,9 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(feature = "client")]
pub mod client;
pub mod forwarder;
#[cfg(feature = "client")]
pub use client::{Client, Config, SendWithoutResponse};
@@ -7,7 +7,7 @@ use crate::ClientBandwidth;
use nym_credentials::ecash::utils::ecash_today;
use nym_credentials_interface::Bandwidth;
use nym_gateway_requests::ServerResponse;
use nym_gateway_storage::Storage;
use nym_gateway_storage::GatewayStorage;
use si_scale::helpers::bibytes2;
use time::OffsetDateTime;
use tracing::*;
@@ -15,17 +15,17 @@ use tracing::*;
const FREE_TESTNET_BANDWIDTH_VALUE: Bandwidth = Bandwidth::new_unchecked(64 * 1024 * 1024 * 1024); // 64GB
#[derive(Clone)]
pub struct BandwidthStorageManager<S> {
pub(crate) storage: S,
pub struct BandwidthStorageManager {
pub(crate) storage: GatewayStorage,
pub(crate) client_bandwidth: ClientBandwidth,
pub(crate) client_id: i64,
pub(crate) bandwidth_cfg: BandwidthFlushingBehaviourConfig,
pub(crate) only_coconut_credentials: bool,
}
impl<S: Storage + Clone + 'static> BandwidthStorageManager<S> {
impl BandwidthStorageManager {
pub fn new(
storage: S,
storage: GatewayStorage,
client_bandwidth: ClientBandwidth,
client_id: i64,
bandwidth_cfg: BandwidthFlushingBehaviourConfig,
@@ -13,7 +13,6 @@ use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY;
use nym_api_requests::ecash::models::{BatchRedeemTicketsBody, VerifyEcashTicketBody};
use nym_credentials_interface::Bandwidth;
use nym_credentials_interface::{ClientTicket, TicketType};
use nym_gateway_storage::Storage;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nyxd::contract_traits::{
EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient,
@@ -126,21 +125,18 @@ pub struct CredentialHandlerConfig {
pub maximum_time_between_redemption: Duration,
}
pub(crate) struct CredentialHandler<St: Storage> {
pub(crate) struct CredentialHandler {
config: CredentialHandlerConfig,
multisig_threshold: f32,
ticket_receiver: UnboundedReceiver<ClientTicket>,
shared_state: SharedState<St>,
shared_state: SharedState,
pending_tickets: Vec<PendingVerification>,
pending_redemptions: Vec<PendingRedemptionVote>,
}
impl<St> CredentialHandler<St>
where
St: Storage + Clone + 'static,
{
impl CredentialHandler {
async fn rebuild_pending_tickets(
shared_state: &SharedState<St>,
shared_state: &SharedState,
) -> Result<Vec<PendingVerification>, EcashTicketError> {
// 1. get all tickets that were not fully verified
let unverified = shared_state.storage.get_all_unverified_tickets().await?;
@@ -188,7 +184,7 @@ where
}
async fn rebuild_pending_votes(
shared_state: &SharedState<St>,
shared_state: &SharedState,
) -> Result<Vec<PendingRedemptionVote>, EcashTicketError> {
// 1. get all tickets that were not fully verified
let unverified = shared_state.storage.get_all_unresolved_proposals().await?;
@@ -259,7 +255,7 @@ where
pub(crate) async fn new(
config: CredentialHandlerConfig,
ticket_receiver: UnboundedReceiver<ClientTicket>,
shared_state: SharedState<St>,
shared_state: SharedState,
) -> Result<Self, Error> {
let multisig_threshold = shared_state
.nyxd_client
@@ -1,7 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_gateway_storage::error::StorageError;
use nym_gateway_storage::error::GatewayStorageError;
use nym_validator_client::coconut::EcashApiError;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nyxd::error::NyxdError;
@@ -37,7 +37,7 @@ pub enum EcashTicketError {
#[error("could not handle the ecash ticket due to internal storage failure: {source}")]
InternalStorageFailure {
#[from]
source: StorageError,
source: GatewayStorageError,
},
#[error("failed to create ticket redemption proposal: {source}")]
@@ -8,7 +8,7 @@ use error::EcashTicketError;
use futures::channel::mpsc::{self, UnboundedSender};
use nym_credentials::CredentialSpendingData;
use nym_credentials_interface::{ClientTicket, CompactEcashError, NymPayInfo, VerificationKeyAuth};
use nym_gateway_storage::Storage;
use nym_gateway_storage::GatewayStorage;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::DirectSigningHttpRpcNyxdClient;
use state::SharedState;
@@ -23,24 +23,21 @@ mod state;
pub const TIME_RANGE_SEC: i64 = 30;
pub struct EcashManager<S> {
shared_state: SharedState<S>,
pub struct EcashManager {
shared_state: SharedState,
pk_bytes: [u8; 32], // bytes representation of a pub key representing the verifier
pay_infos: Mutex<Vec<NymPayInfo>>,
cred_sender: UnboundedSender<ClientTicket>,
}
impl<S> EcashManager<S>
where
S: Storage + Clone + 'static,
{
impl EcashManager {
pub async fn new(
credential_handler_cfg: CredentialHandlerConfig,
nyxd_client: DirectSigningHttpRpcNyxdClient,
pk_bytes: [u8; 32],
shutdown: nym_task::TaskClient,
storage: S,
storage: GatewayStorage,
) -> Result<Self, Error> {
let shared_state = SharedState::new(nyxd_client, storage).await?;
@@ -66,7 +63,7 @@ where
self.shared_state.verification_key(epoch_id).await
}
pub fn storage(&self) -> &S {
pub fn storage(&self) -> &GatewayStorage {
&self.shared_state.storage
}
@@ -6,7 +6,7 @@ use crate::Error;
use cosmwasm_std::{from_binary, CosmosMsg, WasmMsg};
use nym_credentials_interface::VerificationKeyAuth;
use nym_ecash_contract_common::msg::ExecuteMsg;
use nym_gateway_storage::Storage;
use nym_gateway_storage::GatewayStorage;
use nym_validator_client::coconut::all_ecash_api_clients;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nyxd::contract_traits::{
@@ -23,20 +23,17 @@ use tracing::{error, trace, warn};
// state shared by different subtasks dealing with credentials
#[derive(Clone)]
pub(crate) struct SharedState<S> {
pub(crate) struct SharedState {
pub(crate) nyxd_client: Arc<RwLock<DirectSigningHttpRpcNyxdClient>>,
pub(crate) address: AccountId,
pub(crate) epoch_data: Arc<RwLock<BTreeMap<EpochId, EpochState>>>,
pub(crate) storage: S,
pub(crate) storage: GatewayStorage,
}
impl<S> SharedState<S>
where
S: Storage + Clone,
{
impl SharedState {
pub(crate) async fn new(
nyxd_client: DirectSigningHttpRpcNyxdClient,
storage: S,
storage: GatewayStorage,
) -> Result<Self, Error> {
let address = nyxd_client.address();
+1 -1
View File
@@ -39,7 +39,7 @@ pub enum Error {
OutOfBandwidth { required: i64, available: i64 },
#[error("Internal gateway storage error")]
StorageError(#[from] nym_gateway_storage::error::StorageError),
StorageError(#[from] nym_gateway_storage::error::GatewayStorageError),
#[error("{0}")]
UnknownTicketType(#[from] nym_credentials_interface::UnknownTicketType),
+10 -12
View File
@@ -2,17 +2,15 @@
// SPDX-License-Identifier: Apache-2.0
use bandwidth_storage_manager::BandwidthStorageManager;
use ecash::EcashManager;
use nym_credentials::ecash::utils::{cred_exp_date, ecash_today, EcashTime};
use nym_credentials_interface::{Bandwidth, ClientTicket, TicketType};
use nym_gateway_requests::models::CredentialSpendingRequest;
use std::sync::Arc;
use time::{Date, OffsetDateTime};
use tracing::*;
use nym_credentials::ecash::utils::{cred_exp_date, ecash_today, EcashTime};
use nym_credentials_interface::{Bandwidth, ClientTicket, TicketType};
use nym_gateway_requests::models::CredentialSpendingRequest;
use nym_gateway_storage::Storage;
pub use client_bandwidth::*;
use ecash::EcashManager;
pub use error::*;
pub mod bandwidth_storage_manager;
@@ -20,17 +18,17 @@ mod client_bandwidth;
pub mod ecash;
pub mod error;
pub struct CredentialVerifier<S> {
pub struct CredentialVerifier {
credential: CredentialSpendingRequest,
ecash_verifier: Arc<EcashManager<S>>,
bandwidth_storage_manager: BandwidthStorageManager<S>,
ecash_verifier: Arc<EcashManager>,
bandwidth_storage_manager: BandwidthStorageManager,
}
impl<S: Storage + Clone + 'static> CredentialVerifier<S> {
impl CredentialVerifier {
pub fn new(
credential: CredentialSpendingRequest,
ecash_verifier: Arc<EcashManager<S>>,
bandwidth_storage_manager: BandwidthStorageManager<S>,
ecash_verifier: Arc<EcashManager>,
bandwidth_storage_manager: BandwidthStorageManager,
) -> Self {
CredentialVerifier {
credential,
+2
View File
@@ -16,12 +16,14 @@ sqlx = { workspace = true, features = [
"migrate",
"time",
] }
strum = { workspace = true }
time = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
nym-sphinx = { path = "../nymsphinx" }
nym-credentials-interface = { path = "../credentials-interface" }
nym-node-metrics = { path = "../../nym-node/nym-node-metrics" }
nym-statistics-common = { path = "../statistics" }
+6 -9
View File
@@ -2,9 +2,9 @@
// SPDX-License-Identifier: GPL-3.0-only
use error::StatsStorageError;
use models::{ActiveSession, FinishedSession, StoredFinishedSession};
use models::StoredFinishedSession;
use nym_node_metrics::entry::{ActiveSession, FinishedSession, SessionType};
use nym_sphinx::DestinationAddressBytes;
use nym_statistics_common::gateways::SessionType;
use sessions::SessionManager;
use sqlx::ConnectOptions;
use std::path::Path;
@@ -71,8 +71,8 @@ impl PersistentStatsStorage {
.session_manager
.insert_finished_session(
date,
session.duration.whole_milliseconds() as i64,
session.typ.to_string().into(),
session.duration.as_millis() as i64,
session.typ.to_string(),
)
.await?)
}
@@ -126,7 +126,7 @@ impl PersistentStatsStorage {
.insert_active_session(
client_address.as_base58_string(),
session.start,
session.typ.to_string().into(),
session.typ.to_string(),
)
.await?)
}
@@ -138,10 +138,7 @@ impl PersistentStatsStorage {
) -> Result<(), StatsStorageError> {
Ok(self
.session_manager
.update_active_session_type(
client_address.as_base58_string(),
session_type.to_string().into(),
)
.update_active_session_type(client_address.as_base58_string(), session_type.to_string())
.await?)
}
+23 -44
View File
@@ -1,10 +1,11 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_credentials_interface::TicketType;
use nym_statistics_common::gateways::SessionType;
use nym_node_metrics::entry::{ActiveSession, FinishedSession, SessionType};
use sqlx::prelude::FromRow;
use time::{Duration, OffsetDateTime};
use time::OffsetDateTime;
pub use nym_credentials_interface::TicketType;
#[derive(FromRow)]
pub struct StoredFinishedSession {
@@ -12,18 +13,28 @@ pub struct StoredFinishedSession {
typ: String,
}
impl StoredFinishedSession {
pub fn serialize(&self) -> (u64, String) {
(
self.duration_ms as u64, //we are sure that it fits in a u64, see `fn end_at`
self.typ.clone(),
)
impl From<StoredFinishedSession> for FinishedSession {
fn from(value: StoredFinishedSession) -> Self {
FinishedSession {
duration: std::time::Duration::from_millis(value.duration_ms as u64),
typ: SessionType::from_string(value.typ),
}
}
}
pub struct FinishedSession {
pub duration: Duration,
pub typ: SessionType,
pub trait ToSessionType {
fn to_session_type(&self) -> SessionType;
}
impl ToSessionType for TicketType {
fn to_session_type(&self) -> SessionType {
match self {
TicketType::V1MixnetEntry => SessionType::Mixnet,
TicketType::V1MixnetExit => SessionType::Mixnet,
TicketType::V1WireguardEntry => SessionType::Vpn,
TicketType::V1WireguardExit => SessionType::Vpn,
}
}
}
#[derive(FromRow)]
@@ -32,38 +43,6 @@ pub(crate) struct StoredActiveSession {
typ: String,
}
pub struct ActiveSession {
pub start: OffsetDateTime,
pub typ: SessionType,
}
impl ActiveSession {
pub fn new(start_time: OffsetDateTime) -> Self {
ActiveSession {
start: start_time,
typ: SessionType::Unknown,
}
}
pub fn set_type(&mut self, ticket_type: TicketType) {
self.typ = ticket_type.into();
}
pub fn end_at(self, stop_time: OffsetDateTime) -> Option<FinishedSession> {
let session_duration = stop_time - self.start;
//ensure duration is positive to fit in a u64
//u64::max milliseconds is 500k millenia so no overflow issue
if session_duration > Duration::ZERO {
Some(FinishedSession {
duration: session_duration,
typ: self.typ,
})
} else {
None
}
}
}
impl From<StoredActiveSession> for ActiveSession {
fn from(value: StoredActiveSession) -> Self {
ActiveSession {
-1
View File
@@ -9,7 +9,6 @@ edition.workspace = true
license.workspace = true
[dependencies]
async-trait = { workspace = true }
bincode = { workspace = true }
defguard_wireguard_rs = { workspace = true }
log = { workspace = true }
+1 -1
View File
@@ -4,7 +4,7 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum StorageError {
pub enum GatewayStorageError {
#[error("Database experienced an internal error: {0}")]
InternalDatabaseError(#[from] sqlx::Error),
+95 -287
View File
@@ -1,10 +1,8 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use async_trait::async_trait;
use bandwidth::BandwidthManager;
use clients::{ClientManager, ClientType};
use error::StorageError;
use inboxes::InboxManager;
use models::{
Client, PersistedBandwidth, PersistedSharedKeys, RedemptionProposal, StoredMessage,
@@ -29,237 +27,11 @@ mod shared_keys;
mod tickets;
mod wireguard_peers;
#[async_trait]
pub trait Storage: Send + Sync {
async fn get_mixnet_client_id(
&self,
client_address: DestinationAddressBytes,
) -> Result<i64, StorageError>;
/// Inserts provided derived shared keys into the database.
/// If keys previously existed for the provided client, they are overwritten with the new data.
///
/// # Arguments
///
/// * `client_address`: base58-encoded address of the client
/// * `shared_keys`:
/// - legacy: shared encryption (AES128CTR) and mac (hmac-blake3) derived shared keys to store.
/// - current: shared AES256-GCM-SIV keys
async fn insert_shared_keys(
&self,
client_address: DestinationAddressBytes,
shared_keys: &SharedGatewayKey,
) -> Result<i64, StorageError>;
/// Tries to retrieve shared keys stored for the particular client.
///
/// # Arguments
///
/// * `client_address`: address of the client
async fn get_shared_keys(
&self,
client_address: DestinationAddressBytes,
) -> Result<Option<PersistedSharedKeys>, StorageError>;
/// Removes from the database shared keys derived with the particular client.
///
/// # Arguments
///
/// * `client_address`: address of the client
// currently there is no code flow that causes removal (not overwriting)
// of the stored keys. However, retain the function for consistency and completion sake
#[allow(dead_code)]
async fn remove_shared_keys(
&self,
client_address: DestinationAddressBytes,
) -> Result<(), StorageError>;
/// Tries to retrieve a particular client.
///
/// # Arguments
///
/// * `client_id`: id of the client
#[allow(dead_code)]
async fn get_client(&self, client_id: i64) -> Result<Option<Client>, StorageError>;
/// Inserts new message to the storage for an offline client for future retrieval.
///
/// # Arguments
///
/// * `client_address`: address of the client
/// * `message`: raw message to store.
async fn store_message(
&self,
client_address: DestinationAddressBytes,
message: Vec<u8>,
) -> Result<(), StorageError>;
/// Retrieves messages stored for the particular client specified by the provided address.
///
/// # Arguments
///
/// * `client_address`: address of the client
/// * `start_after`: optional starting id of the messages to grab
///
/// returns the retrieved messages alongside optional id of the last message retrieved if
/// there are more messages to retrieve.
async fn retrieve_messages(
&self,
client_address: DestinationAddressBytes,
start_after: Option<i64>,
) -> Result<(Vec<StoredMessage>, Option<i64>), StorageError>;
/// Removes messages with the specified ids
///
/// # Arguments
///
/// * `ids`: ids of the messages to remove
async fn remove_messages(&self, ids: Vec<i64>) -> Result<(), StorageError>;
/// Creates a new bandwidth entry for the particular client.
async fn create_bandwidth_entry(&self, client_id: i64) -> Result<(), StorageError>;
/// Set the freepass expiration date of the particular client to the provided date.
///
/// # Arguments
///
/// * `client_address`: address of the client
/// * `expiration`: the expiration date of the associated free pass.
async fn set_expiration(
&self,
client_id: i64,
expiration: OffsetDateTime,
) -> Result<(), StorageError>;
/// Reset all the bandwidth
///
/// # Arguments
///
/// * `client_address`: address of the client
async fn reset_bandwidth(&self, client_id: i64) -> Result<(), StorageError>;
/// Tries to retrieve available bandwidth for the particular client.
async fn get_available_bandwidth(
&self,
client_id: i64,
) -> Result<Option<PersistedBandwidth>, StorageError>;
/// Increases specified client's bandwidth by the provided amount and returns the current value.
async fn increase_bandwidth(&self, client_id: i64, amount: i64) -> Result<i64, StorageError>;
async fn revoke_ticket_bandwidth(
&self,
ticket_id: i64,
amount: i64,
) -> Result<(), StorageError>;
#[allow(dead_code)]
/// Decreases specified client's bandwidth by the provided amount and returns the current value.
async fn decrease_bandwidth(&self, client_id: i64, amount: i64) -> Result<i64, StorageError>;
async fn insert_epoch_signers(
&self,
epoch_id: i64,
signer_ids: Vec<i64>,
) -> Result<(), StorageError>;
async fn insert_received_ticket(
&self,
client_id: i64,
received_at: OffsetDateTime,
serial_number: Vec<u8>,
data: Vec<u8>,
) -> Result<i64, StorageError>;
// note: this only checks very recent tickets that haven't yet been redeemed
// (but it's better than nothing)
/// Check if the ticket with the provided serial number if already present in the storage.
///
/// # Arguments
///
/// * `serial_number`: the unique serial number embedded in the ticket
async fn contains_ticket(&self, serial_number: &[u8]) -> Result<bool, StorageError>;
async fn insert_ticket_verification(
&self,
ticket_id: i64,
signer_id: i64,
verified_at: OffsetDateTime,
accepted: bool,
) -> Result<(), StorageError>;
async fn update_rejected_ticket(&self, ticket_id: i64) -> Result<(), StorageError>;
async fn update_verified_ticket(&self, ticket_id: i64) -> Result<(), StorageError>;
async fn remove_verified_ticket_binary_data(&self, ticket_id: i64) -> Result<(), StorageError>;
async fn get_all_verified_tickets_with_sn(&self) -> Result<Vec<VerifiedTicket>, StorageError>;
async fn get_all_proposed_tickets_with_sn(
&self,
proposal_id: u32,
) -> Result<Vec<VerifiedTicket>, StorageError>;
async fn insert_redemption_proposal(
&self,
tickets: &[VerifiedTicket],
proposal_id: u32,
created_at: OffsetDateTime,
) -> Result<(), StorageError>;
async fn clear_post_proposal_data(
&self,
proposal_id: u32,
resolved_at: OffsetDateTime,
rejected: bool,
) -> Result<(), StorageError>;
async fn latest_proposal(&self) -> Result<Option<RedemptionProposal>, StorageError>;
async fn get_all_unverified_tickets(&self) -> Result<Vec<ClientTicket>, StorageError>;
async fn get_all_unresolved_proposals(&self) -> Result<Vec<i64>, StorageError>;
async fn get_votes(&self, ticket_id: i64) -> Result<Vec<i64>, StorageError>;
async fn get_signers(&self, epoch_id: i64) -> Result<Vec<i64>, StorageError>;
/// Insert a wireguard peer in the storage.
///
/// # Arguments
///
/// * `peer`: wireguard peer data to be stored
/// * `with_client_id`: if the peer should have a corresponding client_id
/// (created with entry wireguard ticket) or live without one (or with an
/// exiting one), for temporary backwards compatibility.
async fn insert_wireguard_peer(
&self,
peer: &defguard_wireguard_rs::host::Peer,
with_client_id: bool,
) -> Result<Option<i64>, StorageError>;
/// Tries to retrieve available bandwidth for the particular peer.
///
/// # Arguments
///
/// * `peer_public_key`: wireguard public key of the peer to be retrieved.
async fn get_wireguard_peer(
&self,
peer_public_key: &str,
) -> Result<Option<WireguardPeer>, StorageError>;
/// Retrieves all wireguard peers.
async fn get_all_wireguard_peers(&self) -> Result<Vec<WireguardPeer>, StorageError>;
/// Remove a wireguard peer from the storage.
///
/// # Arguments
///
/// * `peer_public_key`: wireguard public key of the peer to be removed.
async fn remove_wireguard_peer(&self, peer_public_key: &str) -> Result<(), StorageError>;
}
pub use error::GatewayStorageError;
// note that clone here is fine as upon cloning the same underlying pool will be used
#[derive(Clone)]
pub struct PersistentStorage {
pub struct GatewayStorage {
client_manager: ClientManager,
shared_key_manager: SharedKeysManager,
inbox_manager: InboxManager,
@@ -268,7 +40,7 @@ pub struct PersistentStorage {
wireguard_peer_manager: wireguard_peers::WgPeerManager,
}
impl PersistentStorage {
impl GatewayStorage {
/// Initialises `PersistentStorage` using the provided path.
///
/// # Arguments
@@ -278,7 +50,7 @@ impl PersistentStorage {
pub async fn init<P: AsRef<Path> + Send>(
database_path: P,
message_retrieval_limit: i64,
) -> Result<Self, StorageError> {
) -> Result<Self, GatewayStorageError> {
debug!(
"Attempting to connect to database {:?}",
database_path.as_ref().as_os_str()
@@ -307,7 +79,7 @@ impl PersistentStorage {
}
// the cloning here are cheap as connection pool is stored behind an Arc
Ok(PersistentStorage {
Ok(GatewayStorage {
client_manager: clients::ClientManager::new(connection_pool.clone()),
wireguard_peer_manager: wireguard_peers::WgPeerManager::new(connection_pool.clone()),
shared_key_manager: SharedKeysManager::new(connection_pool.clone()),
@@ -318,23 +90,22 @@ impl PersistentStorage {
}
}
#[async_trait]
impl Storage for PersistentStorage {
async fn get_mixnet_client_id(
impl GatewayStorage {
pub async fn get_mixnet_client_id(
&self,
client_address: DestinationAddressBytes,
) -> Result<i64, StorageError> {
) -> Result<i64, GatewayStorageError> {
Ok(self
.shared_key_manager
.client_id(&client_address.as_base58_string())
.await?)
}
async fn insert_shared_keys(
pub async fn insert_shared_keys(
&self,
client_address: DestinationAddressBytes,
shared_keys: &SharedGatewayKey,
) -> Result<i64, StorageError> {
) -> Result<i64, GatewayStorageError> {
let client_address_bs58 = client_address.as_base58_string();
let client_id = match self
.shared_key_manager
@@ -359,10 +130,10 @@ impl Storage for PersistentStorage {
Ok(client_id)
}
async fn get_shared_keys(
pub async fn get_shared_keys(
&self,
client_address: DestinationAddressBytes,
) -> Result<Option<PersistedSharedKeys>, StorageError> {
) -> Result<Option<PersistedSharedKeys>, GatewayStorageError> {
let keys = self
.shared_key_manager
.get_shared_keys(&client_address.as_base58_string())
@@ -371,37 +142,37 @@ impl Storage for PersistentStorage {
}
#[allow(dead_code)]
async fn remove_shared_keys(
pub async fn remove_shared_keys(
&self,
client_address: DestinationAddressBytes,
) -> Result<(), StorageError> {
) -> Result<(), GatewayStorageError> {
self.shared_key_manager
.remove_shared_keys(&client_address.as_base58_string())
.await?;
Ok(())
}
async fn get_client(&self, client_id: i64) -> Result<Option<Client>, StorageError> {
pub async fn get_client(&self, client_id: i64) -> Result<Option<Client>, GatewayStorageError> {
let client = self.client_manager.get_client(client_id).await?;
Ok(client)
}
async fn store_message(
pub async fn store_message(
&self,
client_address: DestinationAddressBytes,
message: Vec<u8>,
) -> Result<(), StorageError> {
) -> Result<(), GatewayStorageError> {
self.inbox_manager
.insert_message(&client_address.as_base58_string(), message)
.await?;
Ok(())
}
async fn retrieve_messages(
pub async fn retrieve_messages(
&self,
client_address: DestinationAddressBytes,
start_after: Option<i64>,
) -> Result<(Vec<StoredMessage>, Option<i64>), StorageError> {
) -> Result<(Vec<StoredMessage>, Option<i64>), GatewayStorageError> {
let messages = self
.inbox_manager
.get_messages(&client_address.as_base58_string(), start_after)
@@ -409,87 +180,95 @@ impl Storage for PersistentStorage {
Ok(messages)
}
async fn remove_messages(&self, ids: Vec<i64>) -> Result<(), StorageError> {
pub async fn remove_messages(&self, ids: Vec<i64>) -> Result<(), GatewayStorageError> {
for id in ids {
self.inbox_manager.remove_message(id).await?;
}
Ok(())
}
async fn create_bandwidth_entry(&self, client_id: i64) -> Result<(), StorageError> {
pub async fn create_bandwidth_entry(&self, client_id: i64) -> Result<(), GatewayStorageError> {
self.bandwidth_manager.insert_new_client(client_id).await?;
Ok(())
}
async fn set_expiration(
pub async fn set_expiration(
&self,
client_id: i64,
expiration: OffsetDateTime,
) -> Result<(), StorageError> {
) -> Result<(), GatewayStorageError> {
self.bandwidth_manager
.set_expiration(client_id, expiration)
.await?;
Ok(())
}
async fn reset_bandwidth(&self, client_id: i64) -> Result<(), StorageError> {
pub async fn reset_bandwidth(&self, client_id: i64) -> Result<(), GatewayStorageError> {
self.bandwidth_manager.reset_bandwidth(client_id).await?;
Ok(())
}
async fn get_available_bandwidth(
pub async fn get_available_bandwidth(
&self,
client_id: i64,
) -> Result<Option<PersistedBandwidth>, StorageError> {
) -> Result<Option<PersistedBandwidth>, GatewayStorageError> {
Ok(self
.bandwidth_manager
.get_available_bandwidth(client_id)
.await?)
}
async fn increase_bandwidth(&self, client_id: i64, amount: i64) -> Result<i64, StorageError> {
pub async fn increase_bandwidth(
&self,
client_id: i64,
amount: i64,
) -> Result<i64, GatewayStorageError> {
Ok(self
.bandwidth_manager
.increase_bandwidth(client_id, amount)
.await?)
}
async fn revoke_ticket_bandwidth(
pub async fn revoke_ticket_bandwidth(
&self,
ticket_id: i64,
amount: i64,
) -> Result<(), StorageError> {
) -> Result<(), GatewayStorageError> {
Ok(self
.bandwidth_manager
.revoke_ticket_bandwidth(ticket_id, amount)
.await?)
}
async fn decrease_bandwidth(&self, client_id: i64, amount: i64) -> Result<i64, StorageError> {
pub async fn decrease_bandwidth(
&self,
client_id: i64,
amount: i64,
) -> Result<i64, GatewayStorageError> {
Ok(self
.bandwidth_manager
.decrease_bandwidth(client_id, amount)
.await?)
}
async fn insert_epoch_signers(
pub async fn insert_epoch_signers(
&self,
epoch_id: i64,
signer_ids: Vec<i64>,
) -> Result<(), StorageError> {
) -> Result<(), GatewayStorageError> {
self.ticket_manager
.insert_ecash_signers(epoch_id, signer_ids)
.await?;
Ok(())
}
async fn insert_received_ticket(
pub async fn insert_received_ticket(
&self,
client_id: i64,
received_at: OffsetDateTime,
serial_number: Vec<u8>,
data: Vec<u8>,
) -> Result<i64, StorageError> {
) -> Result<i64, GatewayStorageError> {
// technically if we crash between those 2 calls we'll have a bit of data inconsistency,
// but nothing too tragic. we just won't get paid for a single ticket
let ticket_id = self
@@ -503,24 +282,24 @@ impl Storage for PersistentStorage {
Ok(ticket_id)
}
async fn contains_ticket(&self, serial_number: &[u8]) -> Result<bool, StorageError> {
pub async fn contains_ticket(&self, serial_number: &[u8]) -> Result<bool, GatewayStorageError> {
Ok(self.ticket_manager.has_ticket_data(serial_number).await?)
}
async fn insert_ticket_verification(
pub async fn insert_ticket_verification(
&self,
ticket_id: i64,
signer_id: i64,
verified_at: OffsetDateTime,
accepted: bool,
) -> Result<(), StorageError> {
) -> Result<(), GatewayStorageError> {
self.ticket_manager
.insert_ticket_verification(ticket_id, signer_id, verified_at, accepted)
.await?;
Ok(())
}
async fn update_rejected_ticket(&self, ticket_id: i64) -> Result<(), StorageError> {
pub async fn update_rejected_ticket(&self, ticket_id: i64) -> Result<(), GatewayStorageError> {
// set the ticket as rejected
self.ticket_manager.set_rejected_ticket(ticket_id).await?;
@@ -531,7 +310,7 @@ impl Storage for PersistentStorage {
Ok(())
}
async fn update_verified_ticket(&self, ticket_id: i64) -> Result<(), StorageError> {
pub async fn update_verified_ticket(&self, ticket_id: i64) -> Result<(), GatewayStorageError> {
// 1. insert into verified table
self.ticket_manager
.insert_verified_ticket(ticket_id)
@@ -545,36 +324,41 @@ impl Storage for PersistentStorage {
Ok(())
}
async fn remove_verified_ticket_binary_data(&self, ticket_id: i64) -> Result<(), StorageError> {
pub async fn remove_verified_ticket_binary_data(
&self,
ticket_id: i64,
) -> Result<(), GatewayStorageError> {
self.ticket_manager
.remove_binary_ticket_data(ticket_id)
.await?;
Ok(())
}
async fn get_all_verified_tickets_with_sn(&self) -> Result<Vec<VerifiedTicket>, StorageError> {
pub async fn get_all_verified_tickets_with_sn(
&self,
) -> Result<Vec<VerifiedTicket>, GatewayStorageError> {
Ok(self
.ticket_manager
.get_all_verified_tickets_with_sn()
.await?)
}
async fn get_all_proposed_tickets_with_sn(
pub async fn get_all_proposed_tickets_with_sn(
&self,
proposal_id: u32,
) -> Result<Vec<VerifiedTicket>, StorageError> {
) -> Result<Vec<VerifiedTicket>, GatewayStorageError> {
Ok(self
.ticket_manager
.get_all_proposed_tickets_with_sn(proposal_id as i64)
.await?)
}
async fn insert_redemption_proposal(
pub async fn insert_redemption_proposal(
&self,
tickets: &[VerifiedTicket],
proposal_id: u32,
created_at: OffsetDateTime,
) -> Result<(), StorageError> {
) -> Result<(), GatewayStorageError> {
// if we crash between those, there might a bit of an issue. we should revisit it later
// 1. insert the actual proposal
@@ -592,12 +376,12 @@ impl Storage for PersistentStorage {
Ok(())
}
async fn clear_post_proposal_data(
pub async fn clear_post_proposal_data(
&self,
proposal_id: u32,
resolved_at: OffsetDateTime,
rejected: bool,
) -> Result<(), StorageError> {
) -> Result<(), GatewayStorageError> {
// 1. update proposal metadata
self.ticket_manager
.update_redemption_proposal(proposal_id as i64, resolved_at, rejected)
@@ -616,11 +400,13 @@ impl Storage for PersistentStorage {
Ok(())
}
async fn latest_proposal(&self) -> Result<Option<RedemptionProposal>, StorageError> {
pub async fn latest_proposal(&self) -> Result<Option<RedemptionProposal>, GatewayStorageError> {
Ok(self.ticket_manager.get_latest_redemption_proposal().await?)
}
async fn get_all_unverified_tickets(&self) -> Result<Vec<ClientTicket>, StorageError> {
pub async fn get_all_unverified_tickets(
&self,
) -> Result<Vec<ClientTicket>, GatewayStorageError> {
self.ticket_manager
.get_unverified_tickets()
.await?
@@ -629,29 +415,37 @@ impl Storage for PersistentStorage {
.collect()
}
async fn get_all_unresolved_proposals(&self) -> Result<Vec<i64>, StorageError> {
pub async fn get_all_unresolved_proposals(&self) -> Result<Vec<i64>, GatewayStorageError> {
Ok(self
.ticket_manager
.get_all_unresolved_redemption_proposal_ids()
.await?)
}
async fn get_votes(&self, ticket_id: i64) -> Result<Vec<i64>, StorageError> {
pub async fn get_votes(&self, ticket_id: i64) -> Result<Vec<i64>, GatewayStorageError> {
Ok(self
.ticket_manager
.get_verification_votes(ticket_id)
.await?)
}
async fn get_signers(&self, epoch_id: i64) -> Result<Vec<i64>, StorageError> {
pub async fn get_signers(&self, epoch_id: i64) -> Result<Vec<i64>, GatewayStorageError> {
Ok(self.ticket_manager.get_epoch_signers(epoch_id).await?)
}
async fn insert_wireguard_peer(
/// Insert a wireguard peer in the storage.
///
/// # Arguments
///
/// * `peer`: wireguard peer data to be stored
/// * `with_client_id`: if the peer should have a corresponding client_id
/// (created with entry wireguard ticket) or live without one (or with an
/// exiting one), for temporary backwards compatibility.
pub async fn insert_wireguard_peer(
&self,
peer: &defguard_wireguard_rs::host::Peer,
with_client_id: bool,
) -> Result<Option<i64>, StorageError> {
) -> Result<Option<i64>, GatewayStorageError> {
let client_id = match self
.wireguard_peer_manager
.retrieve_peer(&peer.public_key.to_string())
@@ -676,10 +470,15 @@ impl Storage for PersistentStorage {
Ok(client_id)
}
async fn get_wireguard_peer(
/// Tries to retrieve available bandwidth for the particular peer.
///
/// # Arguments
///
/// * `peer_public_key`: wireguard public key of the peer to be retrieved.
pub async fn get_wireguard_peer(
&self,
peer_public_key: &str,
) -> Result<Option<WireguardPeer>, StorageError> {
) -> Result<Option<WireguardPeer>, GatewayStorageError> {
let peer = self
.wireguard_peer_manager
.retrieve_peer(peer_public_key)
@@ -687,12 +486,21 @@ impl Storage for PersistentStorage {
Ok(peer)
}
async fn get_all_wireguard_peers(&self) -> Result<Vec<WireguardPeer>, StorageError> {
/// Retrieves all wireguard peers.
pub async fn get_all_wireguard_peers(&self) -> Result<Vec<WireguardPeer>, GatewayStorageError> {
let ret = self.wireguard_peer_manager.retrieve_all_peers().await?;
Ok(ret)
}
async fn remove_wireguard_peer(&self, peer_public_key: &str) -> Result<(), StorageError> {
/// Remove a wireguard peer from the storage.
///
/// # Arguments
///
/// * `peer_public_key`: wireguard public key of the peer to be removed.
pub async fn remove_wireguard_peer(
&self,
peer_public_key: &str,
) -> Result<(), GatewayStorageError> {
self.wireguard_peer_manager
.remove_peer(peer_public_key)
.await?;
+8 -8
View File
@@ -1,7 +1,7 @@
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::error::StorageError;
use crate::error::GatewayStorageError;
use nym_credentials_interface::{AvailableBandwidth, ClientTicket, CredentialSpendingData};
use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey};
use sqlx::FromRow;
@@ -24,24 +24,24 @@ pub struct PersistedSharedKeys {
}
impl TryFrom<PersistedSharedKeys> for SharedGatewayKey {
type Error = StorageError;
type Error = GatewayStorageError;
fn try_from(value: PersistedSharedKeys) -> Result<Self, Self::Error> {
match (
&value.derived_aes256_gcm_siv_key,
&value.derived_aes128_ctr_blake3_hmac_keys_bs58,
) {
(None, None) => Err(StorageError::MissingSharedKey {
(None, None) => Err(GatewayStorageError::MissingSharedKey {
id: value.client_id,
}),
(Some(aes256gcm_siv), _) => {
let current_key = SharedSymmetricKey::try_from_bytes(aes256gcm_siv)
.map_err(|source| StorageError::DataCorruption(source.to_string()))?;
.map_err(|source| GatewayStorageError::DataCorruption(source.to_string()))?;
Ok(SharedGatewayKey::Current(current_key))
}
(None, Some(aes128ctr_hmac)) => {
let legacy_key = LegacySharedKeys::try_from_base58_string(aes128ctr_hmac)
.map_err(|source| StorageError::DataCorruption(source.to_string()))?;
.map_err(|source| GatewayStorageError::DataCorruption(source.to_string()))?;
Ok(SharedGatewayKey::Legacy(legacy_key))
}
}
@@ -91,12 +91,12 @@ pub struct UnverifiedTicketData {
}
impl TryFrom<UnverifiedTicketData> for ClientTicket {
type Error = StorageError;
type Error = GatewayStorageError;
fn try_from(value: UnverifiedTicketData) -> Result<Self, Self::Error> {
Ok(ClientTicket {
spending_data: CredentialSpendingData::try_from_bytes(&value.data).map_err(|_| {
StorageError::MalformedStoredTicketData {
GatewayStorageError::MalformedStoredTicketData {
ticket_id: value.ticket_id,
}
})?,
@@ -152,7 +152,7 @@ impl From<defguard_wireguard_rs::host::Peer> for WireguardPeer {
}
impl TryFrom<WireguardPeer> for defguard_wireguard_rs::host::Peer {
type Error = crate::error::StorageError;
type Error = crate::error::GatewayStorageError;
fn try_from(value: WireguardPeer) -> Result<Self, Self::Error> {
Ok(Self {
+2 -4
View File
@@ -26,7 +26,7 @@ url = { workspace = true }
time.workspace = true
thiserror = { workspace = true }
nym-crypto = { path = "../crypto" }
nym-crypto = { path = "../crypto", features = ["asymmetric"] }
nym-network-defaults = { path = "../network-defaults" }
nym-sphinx-acknowledgements = { path = "../nymsphinx/acknowledgements" }
nym-sphinx-addressing = { path = "../nymsphinx/addressing" }
@@ -35,7 +35,5 @@ nym-sphinx-framing = { path = "../nymsphinx/framing" }
nym-sphinx-params = { path = "../nymsphinx/params" }
nym-sphinx-types = { path = "../nymsphinx/types" }
nym-task = { path = "../task" }
nym-validator-client = { path = "../client-libs/validator-client" }
nym-bin-common = { path = "../bin-common" }
nym-metrics = { path = "../nym-metrics" }
nym-node-http-api = { path = "../../nym-node/nym-node-http-api" }
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod packet_processor;
pub mod verloc;
-81
View File
@@ -1,81 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::fmt::{self, Display, Formatter};
use std::io;
#[derive(Debug)]
pub enum RttError {
UnexpectedEchoPacketSize,
UnexpectedReplyPacketSize,
MalformedSenderIdentity,
MalformedEchoSignature,
MalformedReplySignature,
InvalidEchoSignature,
InvalidReplySignature,
UnreachableNode(String, io::Error),
UnexpectedConnectionFailureWrite(String, io::Error),
UnexpectedConnectionFailureRead(String, io::Error),
ConnectionReadTimeout(String),
ConnectionWriteTimeout(String),
UnexpectedReplySequence,
ShutdownReceived,
}
impl Display for RttError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
RttError::UnexpectedEchoPacketSize => {
write!(f, "The received echo packet had unexpected size")
}
RttError::UnexpectedReplyPacketSize => {
write!(f, "The received reply packet had unexpected size")
}
RttError::MalformedSenderIdentity => {
write!(f, "The received echo packet had malformed sender")
}
RttError::MalformedEchoSignature => {
write!(f, "The received echo packet had malformed signature")
}
RttError::MalformedReplySignature => {
write!(f, "The received reply packet had malformed signature")
}
RttError::InvalidEchoSignature => {
write!(f, "The received echo packet had invalid signature")
}
RttError::InvalidReplySignature => {
write!(f, "The received reply packet had invalid signature")
}
RttError::UnreachableNode(id, err) => {
write!(f, "Could not establish connection to {id} - {err}")
}
RttError::UnexpectedConnectionFailureWrite(id, err) => {
write!(f, "Failed to write echo packet to {id} - {err}")
}
RttError::UnexpectedConnectionFailureRead(id, err) => {
write!(f, "Failed to read reply packet from {id} - {err}")
}
RttError::ConnectionReadTimeout(id) => {
write!(f, "Timed out while trying to read reply packet from {id}")
}
RttError::ConnectionWriteTimeout(id) => {
write!(f, "Timed out while trying to write echo packet to {id}")
}
RttError::UnexpectedReplySequence => write!(
f,
"The received reply packet had an unexpected sequence number"
),
RttError::ShutdownReceived => {
write!(f, "Shutdown signal received")
}
}
}
}
impl std::error::Error for RttError {}
@@ -1,35 +0,0 @@
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_node_http_api::state::metrics::{SharedVerlocStats, VerlocNodeResult};
use std::mem;
use time::OffsetDateTime;
pub(crate) trait VerlocStatsUpdateExt {
async fn start_new_measurements(&self, nodes_to_test: usize);
async fn append_measurement_results(&self, new_data: Vec<VerlocNodeResult>);
async fn finish_measurements(&self);
}
impl VerlocStatsUpdateExt for SharedVerlocStats {
async fn start_new_measurements(&self, nodes_to_test: usize) {
let mut guard = self.write().await;
guard.previous_run_data = mem::take(&mut guard.current_run_data);
guard.current_run_data.nodes_tested = nodes_to_test;
}
async fn append_measurement_results(&self, mut new_data: Vec<VerlocNodeResult>) {
let mut write_permit = self.write().await;
write_permit.current_run_data.results.append(&mut new_data);
// make sure the data always stays in order.
// TODO: considering the front of the results is guaranteed to be sorted, should perhaps
// a non-default sorting algorithm be used?
write_permit.current_run_data.results.sort()
}
async fn finish_measurements(&self) {
self.write().await.current_run_data.run_finished = Some(OffsetDateTime::now_utc())
}
}
-383
View File
@@ -1,383 +0,0 @@
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::verloc::listener::PacketListener;
use crate::verloc::sender::{PacketSender, TestedNode};
use futures::stream::FuturesUnordered;
use futures::StreamExt;
use log::*;
use nym_bin_common::version_checker::{self, parse_version};
use nym_crypto::asymmetric::identity;
use nym_network_defaults::mainnet::NYM_API;
use nym_node_http_api::state::metrics::{SharedVerlocStats, VerlocNodeResult};
use nym_task::TaskClient;
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use tokio::task::JoinHandle;
use tokio::time::sleep;
use url::Url;
use measurement::VerlocStatsUpdateExt;
// pub use crate::verloc::measurement::{AtomicVerlocResult, Verloc, VerlocResult};
pub mod error;
pub(crate) mod listener;
pub(crate) mod measurement;
pub(crate) mod packet;
pub(crate) mod sender;
// TODO: MUST BE UPDATED BEFORE ACTUAL RELEASE!!
pub const MINIMUM_NODE_VERSION: &str = "0.10.1";
// by default all of those are overwritten by config data from mixnodes directly
const DEFAULT_VERLOC_PORT: u16 = 1790;
const DEFAULT_PACKETS_PER_NODE: usize = 100;
const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500);
const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000);
const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50);
const DEFAULT_BATCH_SIZE: usize = 50;
const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12);
const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30);
#[derive(Clone, Debug)]
pub struct Config {
/// Minimum semver version of a node (gateway or mixnode) that is capable of replying to echo packets.
minimum_compatible_node_version: version_checker::Version,
/// Socket address of this node on which it will be listening for the measurement packets.
listening_address: SocketAddr,
/// Specifies number of echo packets sent to each node during a measurement run.
packets_per_node: usize,
/// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test.
packet_timeout: Duration,
/// Specifies maximum amount of time to wait for the connection to get established.
connection_timeout: Duration,
/// Specifies delay between subsequent test packets being sent (after receiving a reply).
delay_between_packets: Duration,
/// Specifies number of nodes being tested at once.
tested_nodes_batch_size: usize,
/// Specifies delay between subsequent test runs.
testing_interval: Duration,
/// Specifies delay between attempting to run the measurement again if the previous run failed
/// due to being unable to get the list of nodes.
retry_timeout: Duration,
/// URLs to the nym apis for obtaining network topology.
nym_api_urls: Vec<Url>,
}
impl Config {
pub fn build() -> ConfigBuilder {
ConfigBuilder::new()
}
}
#[must_use]
pub struct ConfigBuilder(Config);
impl ConfigBuilder {
pub fn new() -> ConfigBuilder {
Self::default()
}
pub fn minimum_compatible_node_version(mut self, version: version_checker::Version) -> Self {
self.0.minimum_compatible_node_version = version;
self
}
pub fn listening_address(mut self, listening_address: SocketAddr) -> Self {
self.0.listening_address = listening_address;
self
}
pub fn packets_per_node(mut self, packets_per_node: usize) -> Self {
self.0.packets_per_node = packets_per_node;
self
}
pub fn packet_timeout(mut self, packet_timeout: Duration) -> Self {
self.0.packet_timeout = packet_timeout;
self
}
pub fn connection_timeout(mut self, connection_timeout: Duration) -> Self {
self.0.connection_timeout = connection_timeout;
self
}
pub fn delay_between_packets(mut self, delay_between_packets: Duration) -> Self {
self.0.delay_between_packets = delay_between_packets;
self
}
pub fn tested_nodes_batch_size(mut self, tested_nodes_batch_size: usize) -> Self {
self.0.tested_nodes_batch_size = tested_nodes_batch_size;
self
}
pub fn testing_interval(mut self, testing_interval: Duration) -> Self {
self.0.testing_interval = testing_interval;
self
}
pub fn retry_timeout(mut self, retry_timeout: Duration) -> Self {
self.0.retry_timeout = retry_timeout;
self
}
pub fn nym_api_urls(mut self, nym_api_urls: Vec<Url>) -> Self {
self.0.nym_api_urls = nym_api_urls;
self
}
pub fn build(self) -> Config {
// panics here are fine as those are only ever constructed at the initial setup
assert!(
!self.0.nym_api_urls.is_empty(),
"at least one validator endpoint must be provided",
);
self.0
}
}
impl Default for ConfigBuilder {
fn default() -> Self {
ConfigBuilder(Config {
minimum_compatible_node_version: parse_version(MINIMUM_NODE_VERSION).unwrap(),
listening_address: format!("[::]:{DEFAULT_VERLOC_PORT}").parse().unwrap(),
packets_per_node: DEFAULT_PACKETS_PER_NODE,
packet_timeout: DEFAULT_PACKET_TIMEOUT,
connection_timeout: DEFAULT_CONNECTION_TIMEOUT,
delay_between_packets: DEFAULT_DELAY_BETWEEN_PACKETS,
tested_nodes_batch_size: DEFAULT_BATCH_SIZE,
testing_interval: DEFAULT_TESTING_INTERVAL,
retry_timeout: DEFAULT_RETRY_TIMEOUT,
nym_api_urls: vec![NYM_API.parse().expect("Invalid default API URL")],
})
}
}
pub struct VerlocMeasurer {
config: Config,
packet_sender: Arc<PacketSender>,
packet_listener: Arc<PacketListener>,
shutdown_listener: TaskClient,
currently_used_api: usize,
// Note: this client is only fine here as it does not maintain constant connection to the validator.
// It only does bunch of REST queries. If we update it at some point to a more sophisticated (maybe signing) client,
// then it definitely cannot be constructed here and probably will need to be passed from outside,
// as mixnodes/gateways would already be using an instance of said client.
validator_client: nym_validator_client::NymApiClient,
state: SharedVerlocStats,
}
impl VerlocMeasurer {
pub fn new(
mut config: Config,
identity: Arc<identity::KeyPair>,
shutdown_listener: TaskClient,
) -> Self {
config.nym_api_urls.shuffle(&mut thread_rng());
VerlocMeasurer {
packet_sender: Arc::new(PacketSender::new(
Arc::clone(&identity),
config.packets_per_node,
config.packet_timeout,
config.connection_timeout,
config.delay_between_packets,
shutdown_listener.clone().named("VerlocPacketSender"),
)),
packet_listener: Arc::new(PacketListener::new(
config.listening_address,
Arc::clone(&identity),
shutdown_listener.clone().named("VerlocPacketListener"),
)),
shutdown_listener,
currently_used_api: 0,
validator_client: nym_validator_client::NymApiClient::new(
config.nym_api_urls[0].clone(),
),
config,
state: SharedVerlocStats::default(),
}
}
pub fn set_shared_state(&mut self, state: SharedVerlocStats) {
self.state = state;
}
fn use_next_nym_api(&mut self) {
if self.config.nym_api_urls.len() == 1 {
warn!("There's only a single validator API available - it won't be possible to use a different one");
return;
}
self.currently_used_api = (self.currently_used_api + 1) % self.config.nym_api_urls.len();
self.validator_client
.change_nym_api(self.config.nym_api_urls[self.currently_used_api].clone())
}
fn start_listening(&self) -> JoinHandle<()> {
let packet_listener = Arc::clone(&self.packet_listener);
tokio::spawn(packet_listener.run())
}
async fn perform_measurement(&self, nodes_to_test: Vec<TestedNode>) -> MeasurementOutcome {
log::trace!("Performing measurements");
if nodes_to_test.is_empty() {
log::debug!("there are no nodes to measure");
return MeasurementOutcome::Done;
}
let mut shutdown_listener = self.shutdown_listener.clone().named("VerlocMeasurement");
shutdown_listener.disarm();
for chunk in nodes_to_test.chunks(self.config.tested_nodes_batch_size) {
let mut chunk_results = Vec::with_capacity(chunk.len());
let mut measurement_chunk = chunk
.iter()
.map(|node| {
let node = *node;
let packet_sender = Arc::clone(&self.packet_sender);
// TODO: there's a potential issue here. if we make the measurement go into separate
// task, we risk biasing results with the bunch of context switches overhead
// but if we don't do it, it will take ages to complete
// TODO: check performance difference when it's not spawned as a separate task
tokio::spawn(async move {
(
packet_sender.send_packets_to_node(node).await,
node.identity,
)
})
})
.collect::<FuturesUnordered<_>>();
// exhaust the results
while !shutdown_listener.is_shutdown() {
tokio::select! {
measurement_result = measurement_chunk.next() => {
let Some(result) = measurement_result else {
// if the stream has finished, it means we got everything we could have gotten
break
};
// if we receive JoinError it means the task failed to get executed, so either there's a bigger issue with tokio
// or there was a panic inside the task itself. In either case, we should just terminate ourselves.
let execution_result = result.expect("the measurement task panicked!");
let identity = execution_result.1;
let measurement_result = match execution_result.0 {
Err(err) => {
debug!("Failed to perform measurement for {identity}: {err}");
None
}
Ok(result) => Some(result),
};
chunk_results.push(VerlocNodeResult::new(identity, measurement_result));
},
_ = shutdown_listener.recv() => {
trace!("Shutdown received while measuring");
return MeasurementOutcome::Shutdown;
}
}
}
// update the results vector with chunks as they become available (by default every 50 nodes)
self.state.append_measurement_results(chunk_results).await;
}
MeasurementOutcome::Done
}
pub async fn run(&mut self) {
self.start_listening();
while !self.shutdown_listener.is_shutdown() {
info!("Starting verloc measurements");
// TODO: should we also measure gateways?
let all_mixes = match self.validator_client.get_all_described_nodes().await {
Ok(nodes) => nodes,
Err(err) => {
error!(
"failed to obtain list of mixnodes from the validator - {}. Going to attempt to use another validator API in the next run",
err
);
self.use_next_nym_api();
sleep(self.config.retry_timeout).await;
continue;
}
};
if all_mixes.is_empty() {
warn!("There does not seem there are any nodes to measure...")
}
// we only care about address and identity
let tested_nodes = all_mixes
.into_iter()
.filter(|n| n.description.declared_role.mixnode)
.filter_map(|node| {
// try to parse the identity and host
let node_identity = node.ed25519_identity_key();
let ip = node.description.host_information.ip_address.first()?;
let verloc_port = node.description.verloc_port();
let verloc_host = SocketAddr::new(*ip, verloc_port);
// TODO: possible problem in the future, this does name resolution and theoretically
// if a lot of nodes maliciously mis-configured themselves, it might take a while to resolve them all
// However, maybe it's not a problem as if they are misconfigured, they will eventually be
// pushed out of the network and on top of that, verloc is done in separate task that runs
// only every few hours.
Some(TestedNode::new(verloc_host, node_identity))
})
.collect::<Vec<_>>();
// on start of each run remove old results
self.state.start_new_measurements(tested_nodes.len()).await;
if let MeasurementOutcome::Shutdown = self.perform_measurement(tested_nodes).await {
log::trace!("Shutting down after aborting measurements");
break;
}
// write current time to "run finished" field
self.state.finish_measurements().await;
info!(
"Finished performing verloc measurements. The next one will happen in {:?}",
self.config.testing_interval
);
tokio::select! {
_ = sleep(self.config.testing_interval) => {},
_ = self.shutdown_listener.recv() => {
log::trace!("Shutdown received while sleeping");
}
}
}
log::trace!("Verloc: Exiting");
}
}
enum MeasurementOutcome {
Done,
Shutdown,
}
+2 -8
View File
@@ -18,19 +18,13 @@ pub struct FramedNymPacket {
}
impl FramedNymPacket {
pub fn new(packet: NymPacket, packet_type: PacketType, use_legacy_version: bool) -> Self {
pub fn new(packet: NymPacket, packet_type: PacketType) -> Self {
// If this fails somebody is using the library in a super incorrect way, because they
// already managed to somehow create a sphinx packet
let packet_size = PacketSize::get_type(packet.len()).unwrap();
let use_legacy = if packet_type == PacketType::Outfox {
false
} else {
use_legacy_version
};
let header = Header {
packet_version: PacketVersion::new(use_legacy),
packet_version: PacketVersion::new(),
packet_size,
packet_type,
};
@@ -13,12 +13,8 @@ pub enum PacketVersion {
}
impl PacketVersion {
pub fn new(use_legacy: bool) -> Self {
if use_legacy {
Self::new_legacy()
} else {
Self::new_versioned(CURRENT_PACKET_VERSION_NUMBER)
}
pub fn new() -> Self {
Self::new_versioned(CURRENT_PACKET_VERSION_NUMBER)
}
pub fn new_legacy() -> Self {
+1
View File
@@ -26,3 +26,4 @@ nym-sphinx = { path = "../nymsphinx" }
nym-credentials-interface = { path = "../credentials-interface" }
nym-metrics = { path = "../nym-metrics" }
nym-task = { path = "../task" }
+22 -57
View File
@@ -31,40 +31,12 @@ impl GatewayStatsReporter {
/// Gateway Statistics events
pub enum GatewayStatsEvent {
/// Events in the lifecycle of an established client tunnel
SessionStatsEvent(SessionEvent),
}
impl GatewayStatsEvent {
/// A new session between this gateway and the client remote has successfully opened
pub fn new_session_start(client: DestinationAddressBytes) -> GatewayStatsEvent {
GatewayStatsEvent::SessionStatsEvent(SessionEvent::SessionStart {
start_time: OffsetDateTime::now_utc(),
client,
})
}
/// An existing session with the client remote has ended
pub fn new_session_stop(client: DestinationAddressBytes) -> GatewayStatsEvent {
GatewayStatsEvent::SessionStatsEvent(SessionEvent::SessionStop {
stop_time: OffsetDateTime::now_utc(),
client,
})
}
/// A new ecash ticket has been added / requested
pub fn new_ecash_ticket(
client: DestinationAddressBytes,
ticket_type: TicketType,
) -> GatewayStatsEvent {
GatewayStatsEvent::SessionStatsEvent(SessionEvent::EcashTicket {
ticket_type,
client,
})
}
SessionStatsEvent(GatewaySessionEvent),
}
/// Events in the lifecycle of an established client tunnel
pub enum SessionEvent {
#[derive(Debug, Clone, Copy)]
pub enum GatewaySessionEvent {
/// A new session between this gateway and the client remote has successfully opened
SessionStart {
/// The timestamp of the session open event
@@ -88,38 +60,31 @@ pub enum SessionEvent {
},
}
#[derive(PartialEq)]
pub enum SessionType {
Vpn,
Mixnet,
Unknown,
}
impl SessionType {
pub fn to_string(&self) -> &str {
match self {
Self::Vpn => "vpn",
Self::Mixnet => "mixnet",
Self::Unknown => "unknown",
impl GatewaySessionEvent {
/// A new session between this gateway and the client remote has successfully opened
pub fn new_session_start(client: DestinationAddressBytes) -> GatewaySessionEvent {
GatewaySessionEvent::SessionStart {
start_time: OffsetDateTime::now_utc(),
client,
}
}
pub fn from_string(s: &str) -> Self {
match s {
"vpn" => Self::Vpn,
"mixnet" => Self::Mixnet,
_ => Self::Unknown,
/// An existing session with the client remote has ended
pub fn new_session_stop(client: DestinationAddressBytes) -> GatewaySessionEvent {
GatewaySessionEvent::SessionStop {
stop_time: OffsetDateTime::now_utc(),
client,
}
}
}
impl From<TicketType> for SessionType {
fn from(value: TicketType) -> Self {
match value {
TicketType::V1MixnetEntry => Self::Mixnet,
TicketType::V1MixnetExit => Self::Mixnet,
TicketType::V1WireguardEntry => Self::Vpn,
TicketType::V1WireguardExit => Self::Vpn,
/// A new ecash ticket has been added / requested
pub fn new_ecash_ticket(
client: DestinationAddressBytes,
ticket_type: TicketType,
) -> GatewaySessionEvent {
GatewaySessionEvent::EcashTicket {
ticket_type,
client,
}
}
}
+5 -1
View File
@@ -12,7 +12,7 @@
#![warn(clippy::dbg_macro)]
use nym_crypto::asymmetric::ed25519;
use sha2::Digest;
use sha2::{Digest, Sha256};
/// Client specific statistics interfaces and events.
pub mod clients;
@@ -36,3 +36,7 @@ fn generate_stats_id<M: AsRef<[u8]>>(prefix: &str, id_seed: M) -> String {
let output = hasher.finalize();
format!("{:x}", output)
}
pub fn hash_identifier<M: AsRef<[u8]>>(identifier: M) -> String {
format!("{:x}", Sha256::digest(identifier))
}
+13 -1
View File
@@ -455,6 +455,10 @@ impl TaskClient {
self.mode.set_should_not_signal_on_drop();
}
pub fn rearm(&mut self) {
self.mode.set_should_signal_on_drop();
}
pub fn send_we_stopped(&mut self, err: SentError) {
if self.mode.is_dummy() {
return;
@@ -482,7 +486,7 @@ impl Drop for TaskClient {
if !self.mode.should_signal_on_drop() {
self.log(
Level::Trace,
"the task client is getting dropped but inststructed to not signal: this is expected during client shutdown",
"the task client is getting dropped but instructed to not signal: this is expected during client shutdown",
);
return;
} else {
@@ -527,6 +531,14 @@ impl ClientOperatingMode {
}
}
fn set_should_signal_on_drop(&mut self) {
use ClientOperatingMode::{Dummy, Listening, ListeningButDontReportHalt};
*self = match &self {
ListeningButDontReportHalt | Listening => Listening,
Dummy => Dummy,
};
}
fn set_should_not_signal_on_drop(&mut self) {
use ClientOperatingMode::{Dummy, Listening, ListeningButDontReportHalt};
*self = match &self {
+27
View File
@@ -0,0 +1,27 @@
[package]
name = "nym-verloc"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
readme.workspace = true
[dependencies]
bytes = { workspace = true }
futures = { workspace = true }
humantime = { workspace = true }
tracing = { workspace = true }
time = { workspace = true }
tokio = { workspace = true, features = ["sync", "net", "rt-multi-thread", "io-util"] }
tokio-util = { workspace = true, features = ["codec"] }
thiserror = { workspace = true }
rand = { workspace = true }
url = { workspace = true }
nym-crypto = { path = "../crypto", features = ["asymmetric"] }
nym-task = { path = "../task" }
nym-validator-client = { path = "../client-libs/validator-client" }
+72
View File
@@ -0,0 +1,72 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::io;
use std::net::SocketAddr;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum VerlocError {
#[error("the received echo packet had unexpected size")]
UnexpectedEchoPacketSize,
#[error("the received reply packet had unexpected size")]
UnexpectedReplyPacketSize,
#[error("the received echo packet had malformed sender")]
MalformedSenderIdentity,
#[error("the received echo packet had malformed signature")]
MalformedEchoSignature,
#[error("the received reply packet had malformed signature")]
MalformedReplySignature,
#[error("the received echo packet had invalid signature")]
InvalidEchoSignature,
#[error("the received reply packet had invalid signature")]
InvalidReplySignature,
#[error("could not establish connection to {identity} on {address}: {err}")]
UnreachableNode {
identity: String,
address: SocketAddr,
#[source]
err: io::Error,
},
#[error("failed to write echo packet to {identity} on {address}: {err}")]
UnexpectedConnectionFailureWrite {
identity: String,
address: SocketAddr,
#[source]
err: io::Error,
},
#[error("failed to read reply packet from {identity} on {address}: {err}")]
UnexpectedConnectionFailureRead {
identity: String,
address: SocketAddr,
#[source]
err: io::Error,
},
#[error("timed out while trying to read reply packet from {identity} on {address}")]
ConnectionReadTimeout {
identity: String,
address: SocketAddr,
},
#[error("timed out while trying to write echo packet to {identity} on {address}")]
ConnectionWriteTimeout {
identity: String,
address: SocketAddr,
},
#[error("the received reply packet had an unexpected sequence number")]
UnexpectedReplySequence,
#[error("shutdown signal received")]
ShutdownReceived,
}
+11
View File
@@ -0,0 +1,11 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
#![warn(clippy::todo)]
#![warn(clippy::dbg_macro)]
pub mod error;
pub mod measurements;
pub mod models;
+135
View File
@@ -0,0 +1,135 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_validator_client::UserAgent;
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
use std::time::Duration;
use url::Url;
// by default all of those are overwritten by config data from nym-node directly
const DEFAULT_VERLOC_PORT: u16 = 1790;
const DEFAULT_PACKETS_PER_NODE: usize = 100;
const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500);
const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000);
const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50);
const DEFAULT_BATCH_SIZE: usize = 50;
const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12);
const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30);
#[derive(Clone, Debug)]
pub struct Config {
/// Socket address of this node on which it will be listening for the measurement packets.
pub listening_address: SocketAddr,
/// Specifies number of echo packets sent to each node during a measurement run.
pub packets_per_node: usize,
/// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test.
pub packet_timeout: Duration,
/// Specifies maximum amount of time to wait for the connection to get established.
pub connection_timeout: Duration,
/// Specifies delay between subsequent test packets being sent (after receiving a reply).
pub delay_between_packets: Duration,
/// Specifies number of nodes being tested at once.
pub tested_nodes_batch_size: usize,
/// Specifies delay between subsequent test runs.
pub testing_interval: Duration,
/// Specifies delay between attempting to run the measurement again if the previous run failed
/// due to being unable to get the list of nodes.
pub retry_timeout: Duration,
/// URLs to the nym apis for obtaining network topology.
pub nym_api_urls: Vec<Url>,
/// User agent used for querying the nym-api
pub user_agent: UserAgent,
}
impl Config {
pub fn build(nym_api_urls: Vec<Url>, user_agent: impl Into<UserAgent>) -> ConfigBuilder {
ConfigBuilder::new(nym_api_urls, user_agent)
}
}
#[must_use]
pub struct ConfigBuilder(Config);
impl ConfigBuilder {
pub fn new(nym_api_urls: Vec<Url>, user_agent: impl Into<UserAgent>) -> ConfigBuilder {
ConfigBuilder(Config {
// '[::]:port'
listening_address: SocketAddr::new(
IpAddr::V6(Ipv6Addr::UNSPECIFIED),
DEFAULT_VERLOC_PORT,
),
packets_per_node: DEFAULT_PACKETS_PER_NODE,
packet_timeout: DEFAULT_PACKET_TIMEOUT,
connection_timeout: DEFAULT_CONNECTION_TIMEOUT,
delay_between_packets: DEFAULT_DELAY_BETWEEN_PACKETS,
tested_nodes_batch_size: DEFAULT_BATCH_SIZE,
testing_interval: DEFAULT_TESTING_INTERVAL,
retry_timeout: DEFAULT_RETRY_TIMEOUT,
nym_api_urls,
user_agent: user_agent.into(),
})
}
pub fn listening_address(mut self, listening_address: SocketAddr) -> Self {
self.0.listening_address = listening_address;
self
}
pub fn packets_per_node(mut self, packets_per_node: usize) -> Self {
self.0.packets_per_node = packets_per_node;
self
}
pub fn packet_timeout(mut self, packet_timeout: Duration) -> Self {
self.0.packet_timeout = packet_timeout;
self
}
pub fn connection_timeout(mut self, connection_timeout: Duration) -> Self {
self.0.connection_timeout = connection_timeout;
self
}
pub fn delay_between_packets(mut self, delay_between_packets: Duration) -> Self {
self.0.delay_between_packets = delay_between_packets;
self
}
pub fn tested_nodes_batch_size(mut self, tested_nodes_batch_size: usize) -> Self {
self.0.tested_nodes_batch_size = tested_nodes_batch_size;
self
}
pub fn testing_interval(mut self, testing_interval: Duration) -> Self {
self.0.testing_interval = testing_interval;
self
}
pub fn retry_timeout(mut self, retry_timeout: Duration) -> Self {
self.0.retry_timeout = retry_timeout;
self
}
pub fn nym_api_urls(mut self, nym_api_urls: Vec<Url>) -> Self {
self.0.nym_api_urls = nym_api_urls;
self
}
pub fn build(self) -> Config {
// panics here are fine as those are only ever constructed at the initial setup
assert!(
!self.0.nym_api_urls.is_empty(),
"at least one validator endpoint must be provided",
);
self.0
}
}
@@ -1,29 +1,29 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::verloc::error::RttError;
use crate::verloc::packet::{EchoPacket, ReplyPacket};
use crate::error::VerlocError;
use crate::measurements::packet::{EchoPacket, ReplyPacket};
use bytes::{BufMut, BytesMut};
use futures::StreamExt;
use log::*;
use nym_crypto::asymmetric::identity;
use nym_task::TaskClient;
use std::fmt::{Display, Formatter};
use std::net::SocketAddr;
use std::sync::Arc;
use std::{fmt, io, process};
use std::{io, process};
use thiserror::Error;
use tokio::io::AsyncWriteExt;
use tokio::net::{TcpListener, TcpStream};
use tokio_util::codec::{Decoder, Encoder, Framed};
use tracing::{debug, error, info, trace, warn};
pub(crate) struct PacketListener {
pub struct PacketListener {
address: SocketAddr,
connection_handler: Arc<ConnectionHandler>,
shutdown: TaskClient,
}
impl PacketListener {
pub(crate) fn new(
pub fn new(
address: SocketAddr,
identity: Arc<identity::KeyPair>,
shutdown: TaskClient,
@@ -37,13 +37,13 @@ impl PacketListener {
}
impl PacketListener {
pub(super) async fn run(self: Arc<Self>) {
pub async fn run(self: Arc<Self>) {
let listener = match TcpListener::bind(self.address).await {
Ok(listener) => listener,
Err(err) => {
error!(
"Failed to bind to {} - {}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?",
self.address, err
"Failed to bind to {}: {err}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?",
self.address
);
process::exit(1);
}
@@ -71,7 +71,7 @@ impl PacketListener {
}
},
_ = shutdown_listener.recv() => {
log::trace!("PacketListener: Received shutdown");
trace!("PacketListener: Received shutdown");
}
}
}
@@ -137,28 +137,18 @@ impl ConnectionHandler {
}
}
#[derive(Debug)]
#[derive(Debug, Error)]
enum EchoPacketCodecError {
IoError(io::Error),
PacketRecoveryError(RttError),
#[error("encountered io error {0}")]
IoError(#[from] io::Error),
#[error("failed to correctly decode an echo packet: {0}")]
PacketRecoveryError(Box<VerlocError>),
}
impl Display for EchoPacketCodecError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
EchoPacketCodecError::IoError(err) => write!(f, "encountered io error - {err}"),
EchoPacketCodecError::PacketRecoveryError(err) => {
write!(f, "failed to correctly decode an echo packet - {err}")
}
}
}
}
impl std::error::Error for EchoPacketCodecError {}
impl From<io::Error> for EchoPacketCodecError {
fn from(err: io::Error) -> Self {
EchoPacketCodecError::IoError(err)
impl From<VerlocError> for EchoPacketCodecError {
fn from(value: VerlocError) -> Self {
EchoPacketCodecError::PacketRecoveryError(Box::new(value))
}
}
@@ -188,10 +178,7 @@ impl Decoder for EchoPacketCodec {
let packet_bytes = src.split_to(EchoPacket::SIZE);
let echo_packet = match EchoPacket::try_from_bytes(&packet_bytes) {
Ok(packet) => packet,
Err(err) => return Err(EchoPacketCodecError::PacketRecoveryError(err)),
};
let echo_packet = EchoPacket::try_from_bytes(&packet_bytes)?;
// reserve enough bytes for the next frame
src.reserve(EchoPacket::SIZE);
+225
View File
@@ -0,0 +1,225 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::measurements::metrics::SharedVerlocStats;
use crate::measurements::sender::TestedNode;
use crate::measurements::{Config, PacketListener, PacketSender};
use crate::models::VerlocNodeResult;
use futures::stream::FuturesUnordered;
use futures::StreamExt;
use nym_crypto::asymmetric::identity;
use nym_task::TaskClient;
use nym_validator_client::models::NymNodeDescription;
use nym_validator_client::NymApiClient;
use rand::prelude::SliceRandom;
use rand::thread_rng;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::task::JoinHandle;
use tokio::time::sleep;
use tracing::{debug, error, info, trace, warn};
pub struct VerlocMeasurer {
config: Config,
packet_sender: Arc<PacketSender>,
packet_listener: Arc<PacketListener>,
shutdown_listener: TaskClient,
state: SharedVerlocStats,
}
impl VerlocMeasurer {
pub fn new(
config: Config,
identity: Arc<identity::KeyPair>,
shutdown_listener: TaskClient,
) -> Self {
VerlocMeasurer {
packet_sender: Arc::new(PacketSender::new(
Arc::clone(&identity),
config.packets_per_node,
config.packet_timeout,
config.connection_timeout,
config.delay_between_packets,
shutdown_listener.clone().named("VerlocPacketSender"),
)),
packet_listener: Arc::new(PacketListener::new(
config.listening_address,
Arc::clone(&identity),
shutdown_listener.clone().named("VerlocPacketListener"),
)),
shutdown_listener,
config,
state: SharedVerlocStats::default(),
}
}
pub fn set_shared_state(&mut self, state: SharedVerlocStats) {
self.state = state;
}
fn start_listening(&self) -> JoinHandle<()> {
let packet_listener = Arc::clone(&self.packet_listener);
tokio::spawn(packet_listener.run())
}
async fn perform_measurement(&self, nodes_to_test: Vec<TestedNode>) -> MeasurementOutcome {
trace!("Performing measurements");
if nodes_to_test.is_empty() {
debug!("there are no nodes to measure");
return MeasurementOutcome::Done;
}
let mut shutdown_listener = self.shutdown_listener.clone().named("VerlocMeasurement");
shutdown_listener.disarm();
for chunk in nodes_to_test.chunks(self.config.tested_nodes_batch_size) {
let mut chunk_results = Vec::with_capacity(chunk.len());
let mut measurement_chunk = chunk
.iter()
.map(|node| {
let node = *node;
let packet_sender = Arc::clone(&self.packet_sender);
// TODO: there's a potential issue here. if we make the measurement go into separate
// task, we risk biasing results with the bunch of context switches overhead
// but if we don't do it, it will take ages to complete
// TODO: check performance difference when it's not spawned as a separate task
tokio::spawn(async move {
(
packet_sender.send_packets_to_node(node).await,
node.identity,
)
})
})
.collect::<FuturesUnordered<_>>();
// exhaust the results
while !shutdown_listener.is_shutdown() {
tokio::select! {
measurement_result = measurement_chunk.next() => {
let Some(result) = measurement_result else {
// if the stream has finished, it means we got everything we could have gotten
break
};
// if we receive JoinError it means the task failed to get executed, so either there's a bigger issue with tokio
// or there was a panic inside the task itself. In either case, we should just terminate ourselves.
let Ok(execution_result) = result else {
error!("the verloc measurer has panicked!");
continue
};
let identity = execution_result.1;
let measurement_result = match execution_result.0 {
Err(err) => {
debug!("Failed to perform measurement for {identity}: {err}");
None
}
Ok(result) => Some(result),
};
chunk_results.push(VerlocNodeResult::new(identity, measurement_result));
},
_ = shutdown_listener.recv() => {
trace!("Shutdown received while measuring");
return MeasurementOutcome::Shutdown;
}
}
}
// update the results vector with chunks as they become available (by default every 50 nodes)
self.state.append_measurement_results(chunk_results).await;
}
MeasurementOutcome::Done
}
async fn get_list_of_nodes(&self) -> Option<Vec<NymNodeDescription>> {
let mut api_endpoints = self.config.nym_api_urls.clone();
api_endpoints.shuffle(&mut thread_rng());
for api_endpoint in api_endpoints {
let client = NymApiClient::new_with_user_agent(
api_endpoint.clone(),
self.config.user_agent.clone(),
);
match client.get_all_described_nodes().await {
Ok(res) => return Some(res),
Err(err) => {
warn!("failed to get described nodes from {api_endpoint}: {err}")
}
}
}
None
}
pub async fn run(&mut self) {
self.start_listening();
while !self.shutdown_listener.is_shutdown() {
info!("Starting verloc measurements");
// TODO: should we also measure gateways?
let Some(all_nodes) = self.get_list_of_nodes().await else {
error!("failed to obtain list of all nodes from any available api endpoint");
sleep(self.config.retry_timeout).await;
continue;
};
if all_nodes.is_empty() {
warn!("it does not seem there are any nodes to measure...");
sleep(self.config.retry_timeout).await;
continue;
}
// we only care about address and identity
let tested_nodes = all_nodes
.into_iter()
.filter_map(|node| {
// try to parse the identity and host
let node_identity = node.ed25519_identity_key();
let ip = node.description.host_information.ip_address.first()?;
let verloc_port = node.description.verloc_port();
let verloc_host = SocketAddr::new(*ip, verloc_port);
// TODO: possible problem in the future, this does name resolution and theoretically
// if a lot of nodes maliciously mis-configured themselves, it might take a while to resolve them all
// However, maybe it's not a problem as if they are misconfigured, they will eventually be
// pushed out of the network and on top of that, verloc is done in separate task that runs
// only every few hours.
Some(TestedNode::new(verloc_host, node_identity))
})
.collect::<Vec<_>>();
// on start of each run remove old results
self.state.start_new_measurements(tested_nodes.len()).await;
if let MeasurementOutcome::Shutdown = self.perform_measurement(tested_nodes).await {
trace!("Shutting down after aborting measurements");
break;
}
// write current time to "run finished" field
self.state.finish_measurements().await;
info!(
"Finished performing verloc measurements. The next one will happen in {:?}",
self.config.testing_interval
);
tokio::select! {
_ = sleep(self.config.testing_interval) => {},
_ = self.shutdown_listener.recv() => {
trace!("Shutdown received while sleeping");
}
}
}
trace!("Verloc: Exiting");
}
}
enum MeasurementOutcome {
Done,
Shutdown,
}
+50
View File
@@ -0,0 +1,50 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::models::{VerlocNodeResult, VerlocResultData};
use std::mem;
use std::sync::Arc;
use time::OffsetDateTime;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
#[derive(Clone, Debug, Default)]
pub struct SharedVerlocStats {
inner: Arc<RwLock<VerlocStatsState>>,
}
impl SharedVerlocStats {
pub(crate) async fn start_new_measurements(&self, nodes_to_test: usize) {
let mut guard = self.write().await;
guard.previous_run_data = mem::take(&mut guard.current_run_data);
guard.current_run_data.nodes_tested = nodes_to_test;
}
pub(crate) async fn append_measurement_results(&self, mut new_data: Vec<VerlocNodeResult>) {
let mut write_permit = self.write().await;
write_permit.current_run_data.results.append(&mut new_data);
// make sure the data always stays in order.
// TODO: considering the front of the results is guaranteed to be sorted, should perhaps
// a non-default sorting algorithm be used?
write_permit.current_run_data.results.sort()
}
pub(crate) async fn finish_measurements(&self) {
self.write().await.current_run_data.run_finished = Some(OffsetDateTime::now_utc())
}
}
#[derive(Clone, Debug, Default)]
pub struct VerlocStatsState {
pub current_run_data: VerlocResultData,
pub previous_run_data: VerlocResultData,
}
impl SharedVerlocStats {
pub async fn read(&self) -> RwLockReadGuard<'_, VerlocStatsState> {
self.inner.read().await
}
pub async fn write(&self) -> RwLockWriteGuard<'_, VerlocStatsState> {
self.inner.write().await
}
}
+16
View File
@@ -0,0 +1,16 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod config;
pub mod listener;
pub mod measurer;
pub mod metrics;
pub mod packet;
pub mod sender;
pub use config::{Config, ConfigBuilder};
pub use listener::PacketListener;
pub use measurer::VerlocMeasurer;
pub use metrics::{SharedVerlocStats, VerlocStatsState};
pub use packet::{EchoPacket, ReplyPacket};
pub use sender::PacketSender;
@@ -1,20 +1,20 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::verloc::error::RttError;
use nym_crypto::asymmetric::identity::{self, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH};
use crate::error::VerlocError;
use nym_crypto::asymmetric::ed25519::{self, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH};
pub(crate) struct EchoPacket {
pub struct EchoPacket {
sequence_number: u64,
sender: identity::PublicKey,
sender: ed25519::PublicKey,
signature: identity::Signature,
signature: ed25519::Signature,
}
impl EchoPacket {
pub(crate) const SIZE: usize = 8 + PUBLIC_KEY_LENGTH + SIGNATURE_LENGTH;
pub(crate) fn new(sequence_number: u64, keys: &identity::KeyPair) -> Self {
pub(crate) fn new(sequence_number: u64, keys: &ed25519::KeyPair) -> Self {
let bytes_to_sign = sequence_number
.to_be_bytes()
.iter()
@@ -42,20 +42,22 @@ impl EchoPacket {
.collect()
}
pub(crate) fn try_from_bytes(bytes: &[u8]) -> Result<Self, RttError> {
pub(crate) fn try_from_bytes(bytes: &[u8]) -> Result<Self, VerlocError> {
if bytes.len() != Self::SIZE {
return Err(RttError::UnexpectedEchoPacketSize);
return Err(VerlocError::UnexpectedEchoPacketSize);
}
// SAFETY: we have ensured our packet has correct size
#[allow(clippy::unwrap_used)]
let sequence_number = u64::from_be_bytes(bytes[..8].try_into().unwrap());
let sender = identity::PublicKey::from_bytes(&bytes[8..8 + PUBLIC_KEY_LENGTH])
.map_err(|_| RttError::MalformedSenderIdentity)?;
let signature = identity::Signature::from_bytes(&bytes[8 + PUBLIC_KEY_LENGTH..])
.map_err(|_| RttError::MalformedEchoSignature)?;
let sender = ed25519::PublicKey::from_bytes(&bytes[8..8 + PUBLIC_KEY_LENGTH])
.map_err(|_| VerlocError::MalformedSenderIdentity)?;
let signature = ed25519::Signature::from_bytes(&bytes[8 + PUBLIC_KEY_LENGTH..])
.map_err(|_| VerlocError::MalformedEchoSignature)?;
sender
.verify(&bytes[..Self::SIZE - SIGNATURE_LENGTH], &signature)
.map_err(|_| RttError::InvalidEchoSignature)?;
.map_err(|_| VerlocError::InvalidEchoSignature)?;
Ok(EchoPacket {
sequence_number,
@@ -64,7 +66,7 @@ impl EchoPacket {
})
}
pub(crate) fn construct_reply(self, private_key: &identity::PrivateKey) -> ReplyPacket {
pub(crate) fn construct_reply(self, private_key: &ed25519::PrivateKey) -> ReplyPacket {
let bytes = self.to_bytes();
let signature = private_key.sign(bytes);
ReplyPacket {
@@ -74,9 +76,9 @@ impl EchoPacket {
}
}
pub(crate) struct ReplyPacket {
pub struct ReplyPacket {
base_packet: EchoPacket,
signature: identity::Signature,
signature: ed25519::Signature,
}
impl ReplyPacket {
@@ -96,21 +98,21 @@ impl ReplyPacket {
pub(crate) fn try_from_bytes(
bytes: &[u8],
remote_identity: &identity::PublicKey,
) -> Result<Self, RttError> {
remote_ed25519: &ed25519::PublicKey,
) -> Result<Self, VerlocError> {
if bytes.len() != Self::SIZE {
return Err(RttError::UnexpectedReplyPacketSize);
return Err(VerlocError::UnexpectedReplyPacketSize);
}
let base_packet =
EchoPacket::try_from_bytes(&bytes[..8 + PUBLIC_KEY_LENGTH + SIGNATURE_LENGTH])?;
let signature =
identity::Signature::from_bytes(&bytes[8 + PUBLIC_KEY_LENGTH + SIGNATURE_LENGTH..])
.map_err(|_| RttError::MalformedReplySignature)?;
ed25519::Signature::from_bytes(&bytes[8 + PUBLIC_KEY_LENGTH + SIGNATURE_LENGTH..])
.map_err(|_| VerlocError::MalformedReplySignature)?;
remote_identity
remote_ed25519
.verify(&bytes[..Self::SIZE - SIGNATURE_LENGTH], &signature)
.map_err(|_| RttError::InvalidReplySignature)?;
.map_err(|_| VerlocError::InvalidReplySignature)?;
Ok(ReplyPacket {
base_packet,
@@ -1,11 +1,10 @@
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::verloc::error::RttError;
use crate::verloc::packet::{EchoPacket, ReplyPacket};
use log::*;
use nym_crypto::asymmetric::identity;
use nym_node_http_api::state::metrics::VerlocMeasurement;
use crate::error::VerlocError;
use crate::measurements::packet::{EchoPacket, ReplyPacket};
use crate::models::VerlocMeasurement;
use nym_crypto::asymmetric::ed25519;
use nym_task::TaskClient;
use rand::{thread_rng, Rng};
use std::net::SocketAddr;
@@ -15,15 +14,16 @@ use std::{fmt, io};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::time::sleep;
use tracing::{debug, trace};
#[derive(Copy, Clone)]
pub(crate) struct TestedNode {
pub(crate) address: SocketAddr,
pub(crate) identity: identity::PublicKey,
pub(crate) identity: ed25519::PublicKey,
}
impl TestedNode {
pub(crate) fn new(address: SocketAddr, identity: identity::PublicKey) -> Self {
pub(crate) fn new(address: SocketAddr, identity: ed25519::PublicKey) -> Self {
TestedNode { address, identity }
}
}
@@ -38,8 +38,8 @@ impl fmt::Display for TestedNode {
}
}
pub(crate) struct PacketSender {
identity: Arc<identity::KeyPair>,
pub struct PacketSender {
identity: Arc<ed25519::KeyPair>,
// timeout for receiving before sending new one
packets_per_node: usize,
packet_timeout: Duration,
@@ -49,8 +49,8 @@ pub(crate) struct PacketSender {
}
impl PacketSender {
pub(super) fn new(
identity: Arc<identity::KeyPair>,
pub fn new(
identity: Arc<ed25519::KeyPair>,
packets_per_node: usize,
packet_timeout: Duration,
connection_timeout: Duration,
@@ -82,7 +82,7 @@ impl PacketSender {
pub(super) async fn send_packets_to_node(
self: Arc<Self>,
tested_node: TestedNode,
) -> Result<VerlocMeasurement, RttError> {
) -> Result<VerlocMeasurement, VerlocError> {
let mut shutdown_listener = self.shutdown_listener.fork(tested_node.address.to_string());
shutdown_listener.disarm();
@@ -93,16 +93,18 @@ impl PacketSender {
.await
{
Err(_timeout) => {
return Err(RttError::UnreachableNode(
tested_node.identity.to_base58_string(),
io::ErrorKind::TimedOut.into(),
))
return Err(VerlocError::UnreachableNode {
identity: tested_node.identity.to_string(),
err: io::ErrorKind::TimedOut.into(),
address: tested_node.address,
})
}
Ok(Err(err)) => {
return Err(RttError::UnreachableNode(
tested_node.identity.to_base58_string(),
return Err(VerlocError::UnreachableNode {
identity: tested_node.identity.to_string(),
err,
))
address: tested_node.address,
})
}
Ok(Ok(conn)) => conn,
};
@@ -121,33 +123,34 @@ impl PacketSender {
write = tokio::time::timeout(self.packet_timeout, conn.write_all(packet_bytes.as_ref())) => {
match write {
Err(_timeout) => {
let identity_string = tested_node.identity.to_base58_string();
let identity = tested_node.identity;
debug!(
"failed to write echo packet to {} within {:?}. Stopping the test.",
identity_string, self.packet_timeout
"failed to write echo packet to {identity} within {:?}. Stopping the test.",
self.packet_timeout
);
return Err(RttError::UnexpectedConnectionFailureWrite(
identity_string,
io::ErrorKind::TimedOut.into(),
));
return Err(VerlocError::UnexpectedConnectionFailureWrite{
identity: identity.to_string(),
err:io::ErrorKind::TimedOut.into(),
address: tested_node.address
});
}
Ok(Err(err)) => {
let identity_string = tested_node.identity.to_base58_string();
let identity = tested_node.identity;
debug!(
"failed to write echo packet to {} - {}. Stopping the test.",
identity_string, err
"failed to write echo packet to {identity}: {err}. Stopping the test.",
);
return Err(RttError::UnexpectedConnectionFailureWrite(
identity_string,
return Err(VerlocError::UnexpectedConnectionFailureWrite{
identity: identity.to_string(),
err,
));
address: tested_node.address
});
}
Ok(Ok(_)) => {}
}
},
_ = shutdown_listener.recv() => {
log::trace!("PacketSender: Received shutdown while sending");
return Err(RttError::ShutdownReceived);
trace!("PacketSender: Received shutdown while sending");
return Err(VerlocError::ShutdownReceived);
},
}
@@ -156,15 +159,15 @@ impl PacketSender {
let reply_packet_future = async {
let mut buf = [0u8; ReplyPacket::SIZE];
if let Err(err) = conn.read_exact(&mut buf).await {
let identity = tested_node.identity;
debug!(
"failed to read reply packet from {} - {}. Stopping the test.",
tested_node.identity.to_base58_string(),
err
"failed to read reply packet from {identity}: {err}. Stopping the test.",
);
return Err(RttError::UnexpectedConnectionFailureRead(
tested_node.identity.to_base58_string(),
return Err(VerlocError::UnexpectedConnectionFailureRead {
identity: identity.to_string(),
err,
));
address: tested_node.address,
});
}
ReplyPacket::try_from_bytes(&buf, &tested_node.identity)
};
@@ -180,15 +183,16 @@ impl PacketSender {
"failed to receive reply to our echo packet within {:?}. Stopping the test",
self.packet_timeout
);
return Err(RttError::ConnectionReadTimeout(
tested_node.identity.to_base58_string(),
));
return Err(VerlocError::ConnectionReadTimeout{
identity: tested_node.identity.to_string(),
address: tested_node.address
});
}
}
},
_ = shutdown_listener.recv() => {
log::trace!("PacketSender: Received shutdown while waiting for reply");
return Err(RttError::ShutdownReceived);
trace!("PacketSender: Received shutdown while waiting for reply");
return Err(VerlocError::ShutdownReceived);
}
};
@@ -198,7 +202,7 @@ impl PacketSender {
// we have received the previous one
if reply_packet.base_sequence_number() != seq {
debug!("Received reply packet with invalid sequence number! Got {} expected {}. Stopping the test", reply_packet.base_sequence_number(), seq);
return Err(RttError::UnexpectedReplySequence);
return Err(VerlocError::UnexpectedReplySequence);
}
let time_taken = tokio::time::Instant::now().duration_since(start);
+225
View File
@@ -0,0 +1,225 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_crypto::asymmetric::ed25519::{self};
use std::cmp::Ordering;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::time::Duration;
use time::OffsetDateTime;
#[derive(Debug, Clone)]
pub struct VerlocResultData {
pub nodes_tested: usize,
pub run_started: OffsetDateTime,
pub run_finished: Option<OffsetDateTime>,
pub results: Vec<VerlocNodeResult>,
}
impl Default for VerlocResultData {
fn default() -> Self {
VerlocResultData {
nodes_tested: 0,
run_started: OffsetDateTime::now_utc(),
run_finished: None,
results: vec![],
}
}
}
impl VerlocResultData {
pub fn run_finished(&self) -> bool {
self.run_finished.is_some()
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct VerlocNodeResult {
pub node_identity: ed25519::PublicKey,
pub latest_measurement: Option<VerlocMeasurement>,
}
impl VerlocNodeResult {
pub fn new(
node_identity: ed25519::PublicKey,
latest_measurement: Option<VerlocMeasurement>,
) -> Self {
VerlocNodeResult {
node_identity,
latest_measurement,
}
}
}
impl PartialOrd for VerlocNodeResult {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VerlocNodeResult {
fn cmp(&self, other: &Self) -> Ordering {
// if both have measurement, compare measurements
// then if only one have measurement, prefer that one
// completely ignore identity as it makes no sense to order by it
if let Some(self_measurement) = &self.latest_measurement {
if let Some(other_measurement) = &other.latest_measurement {
self_measurement.cmp(other_measurement)
} else {
Ordering::Less
}
} else if other.latest_measurement.is_some() {
Ordering::Greater
} else {
Ordering::Equal
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct VerlocMeasurement {
/// Minimum RTT duration it took to receive an echo packet.
pub minimum: Duration,
/// Average RTT duration it took to receive the echo packets.
pub mean: Duration,
/// Maximum RTT duration it took to receive an echo packet.
pub maximum: Duration,
/// The standard deviation of the RTT duration it took to receive the echo packets.
pub standard_deviation: Duration,
}
impl VerlocMeasurement {
pub fn new(raw_results: &[Duration]) -> Self {
let minimum = raw_results.iter().min().copied().unwrap_or_default();
let maximum = raw_results.iter().max().copied().unwrap_or_default();
let mean = Self::duration_mean(raw_results);
let standard_deviation = Self::duration_standard_deviation(raw_results, mean);
VerlocMeasurement {
minimum,
mean,
maximum,
standard_deviation,
}
}
fn duration_mean(data: &[Duration]) -> Duration {
if data.is_empty() {
return Default::default();
}
let sum = data.iter().sum::<Duration>();
let count = data.len() as u32;
sum / count
}
fn duration_standard_deviation(data: &[Duration], mean: Duration) -> Duration {
if data.is_empty() {
return Default::default();
}
let variance_micros = data
.iter()
.map(|&value| {
// make sure we don't underflow
let diff = if mean > value {
mean - value
} else {
value - mean
};
// we don't need nanos precision
let diff_micros = diff.as_micros();
diff_micros * diff_micros
})
.sum::<u128>()
/ data.len() as u128;
// we shouldn't really overflow as our differences shouldn't be larger than couple seconds at the worst possible case scenario
let std_deviation_micros = (variance_micros as f64).sqrt() as u64;
Duration::from_micros(std_deviation_micros)
}
}
impl Display for VerlocMeasurement {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"rtt min/avg/max/mdev = {} / {} / {} / {}",
humantime::format_duration(self.minimum),
humantime::format_duration(self.mean),
humantime::format_duration(self.maximum),
humantime::format_duration(self.standard_deviation)
)
}
}
impl PartialOrd for VerlocMeasurement {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VerlocMeasurement {
fn cmp(&self, other: &Self) -> Ordering {
// minimum value is most important, then look at standard deviation, then mean and finally maximum
let min_cmp = self.minimum.cmp(&other.minimum);
if min_cmp != Ordering::Equal {
return min_cmp;
}
let std_dev_cmp = self.standard_deviation.cmp(&other.standard_deviation);
if std_dev_cmp != Ordering::Equal {
return std_dev_cmp;
}
let std_dev_cmp = self.mean.cmp(&other.mean);
if std_dev_cmp != Ordering::Equal {
return std_dev_cmp;
}
self.maximum.cmp(&other.maximum)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sorting_vec_of_verlocs() {
let some_identity =
ed25519::PublicKey::from_base58_string("Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt")
.unwrap();
let no_measurement = VerlocNodeResult::new(some_identity, None);
let low_min = VerlocNodeResult::new(
some_identity,
Some(VerlocMeasurement {
minimum: Duration::from_millis(42),
mean: Duration::from_millis(43),
maximum: Duration::from_millis(44),
standard_deviation: Duration::from_millis(45),
}),
);
let higher_min = VerlocNodeResult::new(
some_identity,
Some(VerlocMeasurement {
minimum: Duration::from_millis(420),
mean: Duration::from_millis(430),
maximum: Duration::from_millis(440),
standard_deviation: Duration::from_millis(450),
}),
);
let mut vec_verloc = vec![no_measurement, low_min, no_measurement, higher_min];
vec_verloc.sort();
let expected_sorted = vec![low_min, higher_min, no_measurement, no_measurement];
assert_eq!(expected_sorted, vec_verloc);
}
}
+1
View File
@@ -27,6 +27,7 @@ thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
tokio-stream = { workspace = true }
time = { workspace = true }
tracing = { workspace = true }
nym-authenticator-requests = { path = "../authenticator-requests" }
nym-credential-verification = { path = "../credential-verification" }
+1 -1
View File
@@ -16,7 +16,7 @@ pub enum Error {
MissingClientBandwidthEntry,
#[error("{0}")]
GatewayStorage(#[from] nym_gateway_storage::error::StorageError),
GatewayStorage(#[from] nym_gateway_storage::error::GatewayStorageError),
#[error("{0}")]
SystemTime(#[from] std::time::SystemTimeError),
+15 -6
View File
@@ -7,16 +7,18 @@
// #![warn(clippy::unwrap_used)]
use defguard_wireguard_rs::WGApi;
#[cfg(target_os = "linux")]
use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask};
use nym_crypto::asymmetric::encryption::KeyPair;
#[cfg(target_os = "linux")]
use nym_network_defaults::constants::WG_TUN_BASE_NAME;
use nym_wireguard_types::Config;
use peer_controller::PeerControlRequest;
use std::sync::Arc;
use tokio::sync::mpsc::{self, Receiver, Sender};
#[cfg(target_os = "linux")]
use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask};
#[cfg(target_os = "linux")]
use nym_network_defaults::constants::WG_TUN_BASE_NAME;
pub(crate) mod error;
pub mod peer_controller;
pub mod peer_handle;
@@ -81,8 +83,8 @@ pub struct WireguardData {
/// Start wireguard device
#[cfg(target_os = "linux")]
pub async fn start_wireguard<St: nym_gateway_storage::Storage + Clone + 'static>(
storage: St,
pub async fn start_wireguard(
storage: nym_gateway_storage::GatewayStorage,
all_peers: Vec<nym_gateway_storage::models::WireguardPeer>,
task_client: nym_task::TaskClient,
wireguard_data: WireguardData,
@@ -93,6 +95,7 @@ pub async fn start_wireguard<St: nym_gateway_storage::Storage + Clone + 'static>
use peer_controller::PeerController;
use std::collections::HashMap;
use tokio::sync::RwLock;
use tracing::info;
let ifname = String::from(WG_TUN_BASE_NAME);
let wg_api = defguard_wireguard_rs::WGApi::new(ifname.clone(), false)?;
@@ -121,6 +124,7 @@ pub async fn start_wireguard<St: nym_gateway_storage::Storage + Clone + 'static>
.await?;
peer_bandwidth_managers.insert(peer.public_key.clone(), (bandwidth_manager, peer.clone()));
}
wg_api.create_interface()?;
let interface_config = InterfaceConfiguration {
name: ifname.clone(),
@@ -130,6 +134,11 @@ pub async fn start_wireguard<St: nym_gateway_storage::Storage + Clone + 'static>
peers,
mtu: None,
};
info!(
"attempting to configure wireguard interface '{ifname}': address={}, port={}",
interface_config.address, interface_config.port
);
wg_api.configure_interface(&interface_config)?;
std::process::Command::new("ip")
.args([
+11 -9
View File
@@ -7,6 +7,7 @@ use defguard_wireguard_rs::{
WireguardInterfaceApi,
};
use futures::channel::oneshot;
use log::info;
use nym_authenticator_requests::latest::registration::{
RemainingBandwidthData, BANDWIDTH_CAP_PER_DAY,
};
@@ -14,7 +15,7 @@ use nym_credential_verification::{
bandwidth_storage_manager::BandwidthStorageManager, BandwidthFlushingBehaviourConfig,
ClientBandwidth,
};
use nym_gateway_storage::Storage;
use nym_gateway_storage::GatewayStorage;
use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK;
use std::{collections::HashMap, sync::Arc};
use tokio::sync::{mpsc, RwLock};
@@ -62,24 +63,24 @@ pub struct QueryBandwidthControlResponse {
pub bandwidth_data: Option<RemainingBandwidthData>,
}
pub struct PeerController<St: Storage + Clone + 'static> {
storage: St,
pub struct PeerController {
storage: GatewayStorage,
// used to receive commands from individual handles too
request_tx: mpsc::Sender<PeerControlRequest>,
request_rx: mpsc::Receiver<PeerControlRequest>,
wg_api: Arc<WgApiWrapper>,
host_information: Arc<RwLock<Host>>,
bw_storage_managers: HashMap<Key, Option<SharedBandwidthStorageManager<St>>>,
bw_storage_managers: HashMap<Key, Option<SharedBandwidthStorageManager>>,
timeout_check_interval: IntervalStream,
task_client: nym_task::TaskClient,
}
impl<St: Storage + Clone + 'static> PeerController<St> {
impl PeerController {
pub fn new(
storage: St,
storage: GatewayStorage,
wg_api: Arc<WgApiWrapper>,
initial_host_information: Host,
bw_storage_managers: HashMap<Key, (Option<SharedBandwidthStorageManager<St>>, Peer)>,
bw_storage_managers: HashMap<Key, (Option<SharedBandwidthStorageManager>, Peer)>,
request_tx: mpsc::Sender<PeerControlRequest>,
request_rx: mpsc::Receiver<PeerControlRequest>,
task_client: nym_task::TaskClient,
@@ -158,9 +159,9 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
}
pub async fn generate_bandwidth_manager(
storage: St,
storage: GatewayStorage,
public_key: &Key,
) -> Result<Option<BandwidthStorageManager<St>>, Error> {
) -> Result<Option<BandwidthStorageManager>, Error> {
if let Some(client_id) = storage
.get_wireguard_peer(&public_key.to_string())
.await?
@@ -257,6 +258,7 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
}
pub async fn run(&mut self) {
info!("started wireguard peer controller");
loop {
tokio::select! {
_ = self.timeout_check_interval.next() => {
+7 -8
View File
@@ -10,7 +10,6 @@ use futures::channel::oneshot;
use nym_authenticator_requests::latest::registration::BANDWIDTH_CAP_PER_DAY;
use nym_credential_verification::bandwidth_storage_manager::BandwidthStorageManager;
use nym_gateway_storage::models::WireguardPeer;
use nym_gateway_storage::Storage;
use nym_task::TaskClient;
use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK;
use std::sync::Arc;
@@ -18,26 +17,26 @@ use std::time::{Duration, SystemTime};
use tokio::sync::{mpsc, RwLock};
use tokio_stream::{wrappers::IntervalStream, StreamExt};
pub(crate) type SharedBandwidthStorageManager<St> = Arc<RwLock<BandwidthStorageManager<St>>>;
pub(crate) type SharedBandwidthStorageManager = Arc<RwLock<BandwidthStorageManager>>;
const AUTO_REMOVE_AFTER: Duration = Duration::from_secs(60 * 60 * 24 * 30); // 30 days
pub struct PeerHandle<St> {
pub struct PeerHandle {
public_key: Key,
host_information: Arc<RwLock<Host>>,
peer_storage_manager: PeerStorageManager<St>,
bandwidth_storage_manager: Option<SharedBandwidthStorageManager<St>>,
peer_storage_manager: PeerStorageManager,
bandwidth_storage_manager: Option<SharedBandwidthStorageManager>,
request_tx: mpsc::Sender<PeerControlRequest>,
timeout_check_interval: IntervalStream,
task_client: TaskClient,
startup_timestamp: SystemTime,
}
impl<St: Storage + Clone + 'static> PeerHandle<St> {
impl PeerHandle {
pub fn new(
public_key: Key,
host_information: Arc<RwLock<Host>>,
peer_storage_manager: PeerStorageManager<St>,
bandwidth_storage_manager: Option<SharedBandwidthStorageManager<St>>,
peer_storage_manager: PeerStorageManager,
bandwidth_storage_manager: Option<SharedBandwidthStorageManager>,
request_tx: mpsc::Sender<PeerControlRequest>,
task_client: &TaskClient,
) -> Self {
+5 -5
View File
@@ -4,7 +4,7 @@
use crate::error::Error;
use defguard_wireguard_rs::host::Peer;
use nym_gateway_storage::models::WireguardPeer;
use nym_gateway_storage::Storage;
use nym_gateway_storage::GatewayStorage;
use std::time::Duration;
use time::OffsetDateTime;
@@ -29,15 +29,15 @@ impl Default for PeerFlushingBehaviourConfig {
}
}
pub struct PeerStorageManager<S> {
pub(crate) storage: S,
pub struct PeerStorageManager {
pub(crate) storage: GatewayStorage,
pub(crate) peer_information: Option<PeerInformation>,
pub(crate) cfg: PeerFlushingBehaviourConfig,
pub(crate) with_client_id: bool,
}
impl<S: Storage + Clone + 'static> PeerStorageManager<S> {
pub(crate) fn new(storage: S, peer: Peer, with_client_id: bool) -> Self {
impl PeerStorageManager {
pub(crate) fn new(storage: GatewayStorage, peer: Peer, with_client_id: bool) -> Self {
let peer_information = Some(PeerInformation::new(peer));
Self {
storage,
+1 -2
View File
@@ -58,7 +58,6 @@ nym-mixnet-client = { path = "../common/client-libs/mixnet-client" }
nym-mixnode-common = { path = "../common/mixnode-common" }
nym-network-defaults = { path = "../common/network-defaults" }
nym-network-requester = { path = "../service-providers/network-requester" }
nym-node-http-api = { path = "../nym-node/nym-node-http-api" }
nym-sdk = { path = "../sdk/rust/nym-sdk" }
nym-sphinx = { path = "../common/nymsphinx" }
nym-statistics-common = { path = "../common/statistics" }
@@ -67,13 +66,13 @@ nym-topology = { path = "../common/topology" }
nym-types = { path = "../common/types" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
nym-ip-packet-router = { path = "../service-providers/ip-packet-router" }
nym-node-metrics = { path = "../nym-node/nym-node-metrics" }
nym-wireguard = { path = "../common/wireguard" }
nym-wireguard-types = { path = "../common/wireguard-types", default-features = false }
defguard_wireguard_rs = { workspace = true }
[build-dependencies]
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
sqlx = { workspace = true, features = [
+13 -147
View File
@@ -1,36 +1,19 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_network_defaults::{DEFAULT_NYM_NODE_HTTP_PORT, TICKETBOOK_VALIDITY_DAYS};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::path::PathBuf;
use nym_network_defaults::TICKETBOOK_VALIDITY_DAYS;
use std::net::SocketAddr;
use std::time::Duration;
use url::Url;
use zeroize::{Zeroize, ZeroizeOnDrop};
// 'DEBUG'
// where applicable, the below are defined in milliseconds
const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000);
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_millis(5);
const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 512 * 1024; // 512kB
// TODO: can we move those away?
pub const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_millis(5);
pub const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 512 * 1024; // 512kB
#[derive(Debug)]
pub struct Config {
pub host: Host,
pub http: Http,
pub gateway: Gateway,
// pub storage_paths: GatewayPaths,
pub network_requester: NetworkRequester,
pub ip_packet_router: IpPacketRouter,
@@ -39,18 +22,13 @@ pub struct Config {
}
impl Config {
#[allow(clippy::too_many_arguments)]
pub fn externally_loaded(
host: impl Into<Host>,
http: impl Into<Http>,
pub fn new(
gateway: impl Into<Gateway>,
network_requester: impl Into<NetworkRequester>,
ip_packet_router: impl Into<IpPacketRouter>,
debug: impl Into<Debug>,
) -> Self {
Config {
host: host.into(),
http: http.into(),
gateway: gateway.into(),
network_requester: network_requester.into(),
ip_packet_router: ip_packet_router.into(),
@@ -65,96 +43,23 @@ impl Config {
pub fn get_nyxd_urls(&self) -> Vec<Url> {
self.gateway.nyxd_urls.clone()
}
pub fn get_cosmos_mnemonic(&self) -> bip39::Mnemonic {
self.gateway.cosmos_mnemonic.clone()
}
}
// TODO: this is very much a WIP. we need proper ssl certificate support here
#[derive(Debug, PartialEq)]
pub struct Host {
/// Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections.
pub public_ips: Vec<IpAddr>,
/// Optional hostname of this node, for example nymtech.net.
// TODO: this is temporary. to be replaced by pulling the data directly from the certs.
pub hostname: Option<String>,
}
impl Host {
pub fn validate(&self) -> bool {
if self.public_ips.is_empty() {
return false;
}
true
}
}
#[derive(Debug, PartialEq)]
pub struct Http {
/// Socket address this node will use for binding its http API.
/// default: `0.0.0.0:8000`
pub bind_address: SocketAddr,
/// Path to assets directory of custom landing page of this node.
pub landing_page_assets_path: Option<PathBuf>,
}
impl Default for Http {
fn default() -> Self {
Http {
bind_address: SocketAddr::new(
IpAddr::V4(Ipv4Addr::UNSPECIFIED),
DEFAULT_NYM_NODE_HTTP_PORT,
),
landing_page_assets_path: None,
}
}
}
// we only really care about the mnemonic being zeroized
#[derive(Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
#[derive(Debug, PartialEq, Eq)]
pub struct Gateway {
/// Version of the gateway for which this configuration was created.
pub version: String,
/// Indicates whether this gateway is accepting only zk-nym credentials for accessing the mixnet
/// or if it also accepts non-paying clients
pub enforce_zk_nyms: bool,
/// ID specifies the human readable ID of this particular gateway.
pub id: String,
/// Indicates whether this gateway is accepting only coconut credentials for accessing the
/// the mixnet, or if it also accepts non-paying clients
pub only_coconut_credentials: bool,
/// Address to which this mixnode will bind to and will be listening for packets.
#[zeroize(skip)]
pub listening_address: IpAddr,
/// Port used for listening for all mixnet traffic.
/// (default: 1789)
pub mix_port: u16,
/// Port used for listening for all client-related traffic.
/// (default: 9000)
pub clients_port: u16,
/// If applicable, announced port for listening for secure websocket client traffic.
/// (default: None)
pub clients_wss_port: Option<u16>,
/// Socket address this node will use for binding its client websocket API.
/// default: `0.0.0.0:9000`
pub websocket_bind_address: SocketAddr,
/// Addresses to APIs from which the node gets the view of the network.
#[zeroize(skip)]
pub nym_api_urls: Vec<Url>,
/// Addresses to validators which the node uses to check for double spending of ERC20 tokens.
#[zeroize(skip)]
pub nyxd_urls: Vec<Url>,
/// Mnemonic of a cosmos wallet used in checking for double spending.
// #[deprecated(note = "move to storage")]
// TODO: I don't think this should be stored directly in the config...
pub cosmos_mnemonic: bip39::Mnemonic,
}
#[derive(Debug, PartialEq)]
@@ -185,60 +90,21 @@ impl Default for IpPacketRouter {
#[derive(Debug)]
pub struct Debug {
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
pub packet_forwarding_initial_backoff: Duration,
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
pub packet_forwarding_maximum_backoff: Duration,
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
pub initial_connection_timeout: Duration,
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
pub maximum_connection_buffer_size: usize,
/// Delay between each subsequent presence data being sent.
// DEAD FIELD
pub presence_sending_delay: Duration,
/// Length of filenames for new client messages.
// DEAD FIELD
pub stored_messages_filename_length: u16,
/// Number of messages from offline client that can be pulled at once from the storage.
pub message_retrieval_limit: i64,
/// Defines maximum delay between client bandwidth information being flushed to the persistent storage.
pub client_bandwidth_max_flushing_rate: Duration,
/// Defines a maximum change in client bandwidth before it gets flushed to the persistent storage.
pub client_bandwidth_max_delta_flushing_amount: i64,
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
// it's set to true by default. The reason for that decision is to preserve compatibility with the
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
// It shall be disabled in the subsequent releases.
pub use_legacy_framed_packet_version: bool,
pub zk_nym_tickets: ZkNymTicketHandlerDebug,
}
impl Default for Debug {
fn default() -> Self {
Debug {
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY,
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
client_bandwidth_max_flushing_rate: DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE,
client_bandwidth_max_delta_flushing_amount:
DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT,
use_legacy_framed_packet_version: false,
zk_nym_tickets: Default::default(),
}
}
+4 -10
View File
@@ -3,7 +3,7 @@
use nym_authenticator::error::AuthenticatorError;
use nym_gateway_stats_storage::error::StatsStorageError;
use nym_gateway_storage::error::StorageError;
use nym_gateway_storage::error::GatewayStorageError;
use nym_ip_packet_router::error::IpPacketRouterError;
use nym_network_requester::error::{ClientCoreError, NetworkRequesterError};
use nym_validator_client::nyxd::error::NyxdError;
@@ -38,7 +38,7 @@ pub enum GatewayError {
#[error("storage failure: {source}")]
StorageError {
#[from]
source: StorageError,
source: GatewayStorageError,
},
#[error("stats storage failure: {source}")]
@@ -74,14 +74,8 @@ pub enum GatewayError {
source: AuthenticatorError,
},
#[error("failed to startup local network requester")]
NetworkRequesterStartupFailure,
#[error("failed to startup local ip packet router")]
IpPacketRouterStartupFailure,
#[error("failed to startup local authenticator")]
AuthenticatorStartupFailure,
#[error("failed to startup local {typ}")]
ServiceProviderStartupFailure { typ: &'static str },
#[error("there are no nym API endpoints available")]
NoNymApisAvailable,
+1 -1
View File
@@ -9,4 +9,4 @@ pub mod error;
pub mod node;
pub use error::GatewayError;
pub use node::Gateway;
pub use node::GatewayTasksBuilder;
@@ -5,8 +5,6 @@ use super::websocket::message_receiver::{IsActiveRequestSender, MixMessageSender
use crate::node::client_handling::embedded_clients::LocalEmbeddedClientHandle;
use dashmap::DashMap;
use nym_sphinx::DestinationAddressBytes;
use nym_statistics_common::gateways;
use nym_statistics_common::gateways::GatewayStatsReporter;
use std::sync::Arc;
use tracing::warn;
@@ -34,10 +32,9 @@ impl ActiveClient {
}
}
#[derive(Clone)]
pub(crate) struct ActiveClientsStore {
#[derive(Clone, Default)]
pub struct ActiveClientsStore {
inner: Arc<DashMap<DestinationAddressBytes, ActiveClient>>,
stats_event_reporter: GatewayStatsReporter,
}
#[derive(Clone)]
@@ -51,11 +48,8 @@ pub(crate) struct ClientIncomingChannels {
impl ActiveClientsStore {
/// Creates new instance of `ActiveClientsStore` to store in-memory handles to all currently connected clients.
pub(crate) fn new(stats_event_reporter: GatewayStatsReporter) -> Self {
ActiveClientsStore {
inner: Arc::new(DashMap::new()),
stats_event_reporter,
}
pub fn new() -> Self {
Self::default()
}
/// Tries to obtain sending channel to specified client. Note that if stale entry existed, it is
@@ -64,7 +58,7 @@ impl ActiveClientsStore {
/// # Arguments
///
/// * `client`: address of the client for which to obtain the handle.
pub(crate) fn get_sender(&self, client: DestinationAddressBytes) -> Option<MixMessageSender> {
pub fn get_sender(&self, client: DestinationAddressBytes) -> Option<MixMessageSender> {
let entry = self.inner.get(&client)?;
let handle = entry.value().get_sender();
@@ -130,8 +124,6 @@ impl ActiveClientsStore {
/// * `client`: address of the client for which to remove the handle.
pub(crate) fn disconnect(&self, client: DestinationAddressBytes) {
self.inner.remove(&client);
self.stats_event_reporter
.report(gateways::GatewayStatsEvent::new_session_stop(client));
}
/// Insert new client handle into the store.
@@ -153,12 +145,10 @@ impl ActiveClientsStore {
if self.inner.insert(client, entry).is_some() {
panic!("inserted a duplicate remote client")
}
self.stats_event_reporter
.report(gateways::GatewayStatsEvent::new_session_start(client));
}
/// Inserts a handle to the embedded client
pub(crate) fn insert_embedded(&self, local_client_handle: LocalEmbeddedClientHandle) {
pub fn insert_embedded(&self, local_client_handle: LocalEmbeddedClientHandle) {
let key = local_client_handle.client_destination();
let entry = ActiveClient::Embedded(local_client_handle);
if self.inner.insert(key, entry).is_some() {
@@ -9,10 +9,11 @@ use nym_network_requester::{GatewayPacketRouter, PacketRouter};
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::DestinationAddressBytes;
use nym_task::TaskClient;
use tokio::task::JoinHandle;
use tracing::{debug, error, trace};
#[derive(Debug)]
pub(crate) struct LocalEmbeddedClientHandle {
pub struct LocalEmbeddedClientHandle {
/// Nym address of the embedded client.
pub(crate) address: Recipient,
@@ -52,8 +53,8 @@ impl MessageRouter {
}
}
pub(crate) fn start_with_shutdown(self, shutdown: TaskClient) {
tokio::spawn(self.run_with_shutdown(shutdown));
pub(crate) fn start_with_shutdown(self, shutdown: TaskClient) -> JoinHandle<()> {
tokio::spawn(self.run_with_shutdown(shutdown))
}
fn handle_received_messages(&self, messages: Vec<Vec<u8>>) {
@@ -1,18 +1,23 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::ActiveClientsStore;
use nym_credential_verification::{ecash::EcashManager, BandwidthFlushingBehaviourConfig};
use nym_crypto::asymmetric::identity;
use nym_statistics_common::gateways::GatewayStatsReporter;
use nym_gateway_storage::GatewayStorage;
use nym_mixnet_client::forwarder::MixForwardingSender;
use nym_node_metrics::events::MetricEventsSender;
use std::sync::Arc;
// I can see this being possible expanded with say storage or client store
#[derive(Clone)]
pub(crate) struct CommonHandlerState<S> {
pub(crate) ecash_verifier: Arc<EcashManager<S>>,
pub(crate) storage: S,
pub(crate) struct CommonHandlerState {
pub(crate) ecash_verifier: Arc<EcashManager>,
pub(crate) storage: GatewayStorage,
pub(crate) local_identity: Arc<identity::KeyPair>,
pub(crate) only_coconut_credentials: bool,
pub(crate) bandwidth_cfg: BandwidthFlushingBehaviourConfig,
pub(crate) stats_event_reporter: GatewayStatsReporter,
pub(crate) metrics_sender: MetricEventsSender,
pub(crate) outbound_mix_sender: MixForwardingSender,
pub(crate) active_clients_store: ActiveClientsStore,
}
@@ -24,9 +24,10 @@ use nym_gateway_requests::{
ClientControlRequest, ClientRequest, GatewayRequestsError, SensitiveServerResponse,
SimpleGatewayRequestsError,
};
use nym_gateway_storage::{error::StorageError, Storage};
use nym_gateway_storage::error::GatewayStorageError;
use nym_node_metrics::events::MetricsEvent;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_statistics_common::gateways;
use nym_statistics_common::gateways::GatewaySessionEvent;
use nym_task::TaskClient;
use nym_validator_client::coconut::EcashApiError;
use rand::{random, CryptoRng, Rng};
@@ -39,7 +40,7 @@ use tracing::*;
#[derive(Debug, Error)]
pub enum RequestHandlingError {
#[error("Internal gateway storage error")]
StorageError(#[from] StorageError),
StorageError(#[from] GatewayStorageError),
#[error(
"the database entry for bandwidth of the registered client {client_address} is missing!"
@@ -136,9 +137,9 @@ impl IntoWSMessage for ServerResponse {
}
}
pub(crate) struct AuthenticatedHandler<R, S, St> {
inner: FreshHandler<R, S, St>,
bandwidth_storage_manager: BandwidthStorageManager<St>,
pub(crate) struct AuthenticatedHandler<R, S> {
inner: FreshHandler<R, S>,
bandwidth_storage_manager: BandwidthStorageManager,
client: ClientDetails,
mix_receiver: MixMessageReceiver,
// Occasionally the handler is requested to ping the connected client for confirm that it's
@@ -149,20 +150,13 @@ pub(crate) struct AuthenticatedHandler<R, S, St> {
}
// explicitly remove handle from the global store upon being dropped
impl<R, S, St> Drop for AuthenticatedHandler<R, S, St> {
impl<R, S> Drop for AuthenticatedHandler<R, S> {
fn drop(&mut self) {
self.inner
.active_clients_store
.disconnect(self.client.address)
self.disconnect_client()
}
}
impl<R, S, St> AuthenticatedHandler<R, S, St>
where
// TODO: those trait bounds here don't really make sense....
R: Rng + CryptoRng,
St: Storage + Clone + 'static,
{
impl<R, S> AuthenticatedHandler<R, S> {
/// Upgrades `FreshHandler` into the Authenticated variant implying the client is now authenticated
/// and thus allowed to perform more actions with the gateway, such as redeeming bandwidth or
/// sending sphinx packets.
@@ -173,7 +167,7 @@ where
/// * `client`: details (i.e. address and shared keys) of the registered client
/// * `mix_receiver`: channel used for receiving messages from the mixnet destined for this client.
pub(crate) async fn upgrade(
fresh: FreshHandler<R, S, St>,
fresh: FreshHandler<R, S>,
client: ClientDetails,
mix_receiver: MixMessageReceiver,
is_active_request_receiver: IsActiveRequestReceiver,
@@ -191,7 +185,7 @@ where
client_address: client.address.as_base58_string(),
})?;
Ok(AuthenticatedHandler {
let handler = AuthenticatedHandler {
bandwidth_storage_manager: BandwidthStorageManager::new(
fresh.shared_state.storage.clone(),
ClientBandwidth::new(bandwidth.into()),
@@ -204,14 +198,24 @@ where
mix_receiver,
is_active_request_receiver,
is_active_ping_pending_reply: None,
})
};
handler.send_metrics(GatewaySessionEvent::new_session_start(
handler.client.address,
));
Ok(handler)
}
/// Explicitly removes handle from the global store.
fn disconnect(self) {
fn disconnect_client(&mut self) {
self.inner
.shared_state
.active_clients_store
.disconnect(self.client.address)
.disconnect(self.client.address);
self.send_metrics(GatewaySessionEvent::new_session_stop(self.client.address));
}
fn send_metrics(&self, event: impl Into<MetricsEvent>) {
self.inner.send_metrics(event)
}
/// Forwards the received mix packet from the client into the mix network.
@@ -220,7 +224,12 @@ where
///
/// * `mix_packet`: packet received from the client that should get forwarded into the network.
fn forward_packet(&self, mix_packet: MixPacket) {
if let Err(err) = self.inner.outbound_mix_sender.unbounded_send(mix_packet) {
if let Err(err) = self
.inner
.shared_state
.outbound_mix_sender
.forward_packet(mix_packet)
{
error!("We failed to forward requested mix packet - {err}. Presumably our mix forwarder has crashed. We cannot continue.");
process::exit(1);
}
@@ -259,8 +268,8 @@ where
trace!("available total bandwidth: {available_total}");
if let Ok(ticket_type) = maybe_ticket_type {
self.inner.shared_state.stats_event_reporter.report(
gateways::GatewayStatsEvent::new_ecash_ticket(self.client.address, ticket_type),
self.inner.shared_state.metrics_sender.report_unchecked(
GatewaySessionEvent::new_ecash_ticket(self.client.address, ticket_type),
);
} else {
error!("Somehow verified a ticket with an unknown ticket type");
@@ -372,7 +381,10 @@ where
/// # Arguments
///
/// * `raw_request`: raw message to handle.
async fn handle_text(&mut self, raw_request: String) -> Message {
async fn handle_text(&mut self, raw_request: String) -> Message
where
R: Rng + CryptoRng,
{
trace!("text request");
let request = match ClientControlRequest::try_from(raw_request) {
@@ -470,7 +482,10 @@ where
client = %self.client.address.as_base58_string()
)
)]
async fn handle_request(&mut self, raw_request: Message) -> Option<Message> {
async fn handle_request(&mut self, raw_request: Message) -> Option<Message>
where
R: Rng + CryptoRng,
{
trace!("new request");
// apparently tungstenite auto-handles ping/pong/close messages so for now let's ignore
@@ -542,8 +557,8 @@ where
/// and for sphinx packets received from the mix network that should be sent back to the client.
pub(crate) async fn listen_for_requests(mut self, mut shutdown: TaskClient)
where
R: Rng + CryptoRng,
S: AsyncRead + AsyncWrite + Unpin,
St: Storage,
{
trace!("Started listening for ALL incoming requests...");
@@ -612,7 +627,6 @@ where
}
}
self.disconnect();
trace!("The stream was closed!");
}
}
@@ -3,14 +3,9 @@
use crate::node::client_handling::websocket::common_state::CommonHandlerState;
use crate::node::client_handling::websocket::connection_handler::INITIAL_MESSAGE_TIMEOUT;
use crate::node::client_handling::{
active_clients::ActiveClientsStore,
websocket::{
connection_handler::{
AuthenticatedHandler, ClientDetails, InitialAuthResult, SocketStream,
},
message_receiver::{IsActive, IsActiveRequestSender},
},
use crate::node::client_handling::websocket::{
connection_handler::{AuthenticatedHandler, ClientDetails, InitialAuthResult, SocketStream},
message_receiver::{IsActive, IsActiveRequestSender},
};
use futures::{
channel::{mpsc, oneshot},
@@ -27,11 +22,11 @@ use nym_gateway_requests::{
types::{ClientControlRequest, ServerResponse},
BinaryResponse, SharedGatewayKey, CURRENT_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION,
};
use nym_gateway_storage::{error::StorageError, Storage};
use nym_mixnet_client::forwarder::MixForwardingSender;
use nym_gateway_storage::error::GatewayStorageError;
use nym_node_metrics::events::MetricsEvent;
use nym_sphinx::DestinationAddressBytes;
use nym_task::TaskClient;
use rand::{CryptoRng, Rng};
use rand::CryptoRng;
use std::net::SocketAddr;
use std::time::Duration;
use thiserror::Error;
@@ -43,7 +38,7 @@ use tracing::*;
#[derive(Debug, Error)]
pub(crate) enum InitialAuthenticationError {
#[error("Internal gateway storage error")]
StorageError(#[from] StorageError),
StorageError(#[from] GatewayStorageError),
#[error(
"our datastore is corrupted. the stored key for client {client_id} is malformed: {source}"
@@ -51,7 +46,7 @@ pub(crate) enum InitialAuthenticationError {
MalformedStoredSharedKey {
client_id: String,
#[source]
source: StorageError,
source: GatewayStorageError,
},
#[error("Failed to perform registration handshake: {0}")]
@@ -107,11 +102,9 @@ pub(crate) enum InitialAuthenticationError {
EmptyClientDetails,
}
pub(crate) struct FreshHandler<R, S, St> {
pub(crate) struct FreshHandler<R, S> {
rng: R,
pub(crate) shared_state: CommonHandlerState<St>,
pub(crate) active_clients_store: ActiveClientsStore,
pub(crate) outbound_mix_sender: MixForwardingSender,
pub(crate) shared_state: CommonHandlerState,
pub(crate) socket_connection: SocketStream<S>,
pub(crate) peer_address: SocketAddr,
pub(crate) shutdown: TaskClient,
@@ -120,11 +113,7 @@ pub(crate) struct FreshHandler<R, S, St> {
pub(crate) negotiated_protocol: Option<u8>,
}
impl<R, S, St> FreshHandler<R, S, St>
where
R: Rng + CryptoRng,
St: Storage + Clone + 'static,
{
impl<R, S> FreshHandler<R, S> {
// for time being we assume handle is always constructed from raw socket.
// if we decide we want to change it, that's not too difficult
// also at this point I'm not entirely sure how to deal with this warning without
@@ -133,16 +122,12 @@ where
pub(crate) fn new(
rng: R,
conn: S,
outbound_mix_sender: MixForwardingSender,
active_clients_store: ActiveClientsStore,
shared_state: CommonHandlerState<St>,
shared_state: CommonHandlerState,
peer_address: SocketAddr,
shutdown: TaskClient,
) -> Self {
FreshHandler {
rng,
active_clients_store,
outbound_mix_sender,
socket_connection: SocketStream::RawTcp(conn),
peer_address,
negotiated_protocol: None,
@@ -151,6 +136,10 @@ where
}
}
pub(crate) fn send_metrics(&self, event: impl Into<MetricsEvent>) {
self.shared_state.metrics_sender.report_unchecked(event)
}
/// Attempts to perform websocket handshake with the remote and upgrades the raw TCP socket
/// to the framed WebSocket.
pub(crate) async fn perform_websocket_handshake(&mut self) -> Result<(), WsError>
@@ -494,7 +483,7 @@ where
// The other handler reported that the client is not active, so we can
// disconnect the other client and continue with this connection.
debug!("Other handler reports it is not active");
self.active_clients_store.disconnect(address);
self.shared_state.active_clients_store.disconnect(address);
}
IsActive::Active => {
// The other handled reported a positive reply, so we have to assume it's
@@ -513,14 +502,14 @@ where
Ok(Err(_)) => {
// Other channel failed to reply (the channel sender probably dropped)
info!("Other connection failed to reply, disconnecting it in favour of this new connection");
self.active_clients_store.disconnect(address);
self.shared_state.active_clients_store.disconnect(address);
}
Err(_) => {
// Timeout waiting for reply
warn!(
"Other connection timed out, disconnecting it in favour of this new connection"
);
self.active_clients_store.disconnect(address);
self.shared_state.active_clients_store.disconnect(address);
}
}
Ok(())
@@ -562,7 +551,11 @@ where
.map_err(InitialAuthenticationError::MalformedIV)?;
// Check for duplicate clients
if let Some(client_tx) = self.active_clients_store.get_remote_client(address) {
if let Some(client_tx) = self
.shared_state
.active_clients_store
.get_remote_client(address)
{
warn!("Detected duplicate connection for client: {address}");
self.handle_duplicate_client(address, client_tx.is_active_request_sender)
.await?;
@@ -678,7 +671,11 @@ where
debug!(remote_client = %remote_identity);
if self.active_clients_store.is_active(remote_address) {
if self
.shared_state
.active_clients_store
.is_active(remote_address)
{
return Err(InitialAuthenticationError::DuplicateConnection);
}
@@ -786,7 +783,7 @@ where
pub(crate) async fn handle_until_authenticated_or_failure(
mut self,
shutdown: &mut TaskClient,
) -> Option<AuthenticatedHandler<R, S, St>>
) -> Option<AuthenticatedHandler<R, S>>
where
S: AsyncRead + AsyncWrite + Unpin + Send,
R: CryptoRng + RngCore + Send,
@@ -822,7 +819,7 @@ where
let (mix_sender, mix_receiver) = mpsc::unbounded();
// Channel for handlers to ask other handlers if they are still active.
let (is_active_request_sender, is_active_request_receiver) = mpsc::unbounded();
self.active_clients_store.insert_remote(
self.shared_state.active_clients_store.insert_remote(
registration_details.address,
mix_sender,
is_active_request_sender,
@@ -5,7 +5,6 @@ use crate::config::Config;
use nym_credential_verification::BandwidthFlushingBehaviourConfig;
use nym_gateway_requests::shared_key::SharedGatewayKey;
use nym_gateway_requests::ServerResponse;
use nym_gateway_storage::Storage;
use nym_sphinx::DestinationAddressBytes;
use rand::{CryptoRng, Rng};
use std::time::Duration;
@@ -90,11 +89,10 @@ impl InitialAuthResult {
// imo there's no point in including the peer address in anything higher than debug
#[instrument(level = "debug", skip_all, fields(peer = %handle.peer_address))]
pub(crate) async fn handle_connection<R, S, St>(mut handle: FreshHandler<R, S, St>)
pub(crate) async fn handle_connection<R, S>(mut handle: FreshHandler<R, S>)
where
R: Rng + CryptoRng + Send,
S: AsyncRead + AsyncWrite + Unpin + Send,
St: Storage + Clone + 'static,
{
// don't accept any new requests if we have already received shutdown
if handle.shutdown.is_shutdown() {
@@ -1,41 +1,37 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::client_handling::active_clients::ActiveClientsStore;
use crate::node::client_handling::websocket::common_state::CommonHandlerState;
use crate::node::client_handling::websocket::connection_handler::FreshHandler;
use nym_gateway_storage::Storage;
use nym_mixnet_client::forwarder::MixForwardingSender;
use nym_task::TaskClient;
use rand::rngs::OsRng;
use std::net::SocketAddr;
use std::process;
use tokio::task::JoinHandle;
use tracing::*;
pub(crate) struct Listener<S> {
pub struct Listener {
address: SocketAddr,
shared_state: CommonHandlerState<S>,
shared_state: CommonHandlerState,
shutdown: TaskClient,
}
impl<S> Listener<S>
where
S: Storage + Send + Sync + Clone + 'static,
{
pub(crate) fn new(address: SocketAddr, shared_state: CommonHandlerState<S>) -> Self {
impl Listener {
pub(crate) fn new(
address: SocketAddr,
shared_state: CommonHandlerState,
shutdown: TaskClient,
) -> Self {
Listener {
address,
shared_state,
shutdown,
}
}
// TODO: change the signature to pub(crate) async fn run(&self, handler: Handler)
pub(crate) async fn run(
&mut self,
outbound_mix_sender: MixForwardingSender,
active_clients_store: ActiveClientsStore,
mut shutdown: nym_task::TaskClient,
) {
pub(crate) async fn run(&mut self) {
info!("Starting websocket listener at {}", self.address);
let tcp_listener = match tokio::net::TcpListener::bind(self.address).await {
Ok(listener) => listener,
@@ -45,24 +41,22 @@ where
}
};
while !shutdown.is_shutdown() {
while !self.shutdown.is_shutdown() {
tokio::select! {
biased;
_ = shutdown.recv() => {
_ = self.shutdown.recv() => {
trace!("client_handling::Listener: received shutdown");
}
connection = tcp_listener.accept() => {
match connection {
Ok((socket, remote_addr)) => {
let shutdown = shutdown.clone().named(format!("ClientConnectionHandler_{remote_addr}"));
let shutdown = self.shutdown.fork(format!("websocket-handler-{remote_addr}"));
trace!("received a socket connection from {remote_addr}");
// TODO: I think we *REALLY* need a mechanism for having a maximum number of connected
// clients or spawned tokio tasks -> perhaps a worker system?
let handle = FreshHandler::new(
OsRng,
socket,
outbound_mix_sender.clone(),
active_clients_store.clone(),
self.shared_state.clone(),
remote_addr,
shutdown,
@@ -77,15 +71,7 @@ where
}
}
pub(crate) fn start(
mut self,
outbound_mix_sender: MixForwardingSender,
active_clients_store: ActiveClientsStore,
shutdown: nym_task::TaskClient,
) -> JoinHandle<()> {
tokio::spawn(async move {
self.run(outbound_mix_sender, active_clients_store, shutdown)
.await
})
pub fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move { self.run().await })
}
}
@@ -0,0 +1,226 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::client_handling::embedded_clients::{LocalEmbeddedClientHandle, MessageRouter};
use crate::node::client_handling::websocket::message_receiver::{
MixMessageReceiver, MixMessageSender,
};
use crate::GatewayError;
use async_trait::async_trait;
use futures::channel::{mpsc, oneshot};
use nym_authenticator::Authenticator;
use nym_crypto::asymmetric::ed25519;
use nym_ip_packet_router::error::IpPacketRouterError;
use nym_ip_packet_router::IpPacketRouter;
use nym_mixnet_client::forwarder::MixForwardingSender;
use nym_network_requester::error::NetworkRequesterError;
use nym_network_requester::NRServiceProviderBuilder;
use nym_sdk::mixnet::Recipient;
use nym_sdk::{GatewayTransceiver, LocalGateway, PacketRouter};
use nym_task::TaskClient;
use std::fmt::Display;
use tokio::task::JoinHandle;
use tracing::error;
pub trait LocalRecipient {
fn address(&self) -> Recipient;
}
impl LocalRecipient for nym_network_requester::core::OnStartData {
fn address(&self) -> Recipient {
self.address
}
}
impl LocalRecipient for nym_ip_packet_router::OnStartData {
fn address(&self) -> Recipient {
self.address
}
}
impl LocalRecipient for nym_authenticator::OnStartData {
fn address(&self) -> Recipient {
self.address
}
}
#[async_trait]
pub trait RunnableServiceProvider {
const NAME: &'static str;
type OnStartData: LocalRecipient;
type Error;
async fn run_service_provider(self) -> Result<(), Self::Error>;
}
#[async_trait]
impl RunnableServiceProvider for NRServiceProviderBuilder {
const NAME: &'static str = "network requester";
type OnStartData = nym_network_requester::core::OnStartData;
type Error = NetworkRequesterError;
async fn run_service_provider(self) -> Result<(), Self::Error> {
self.run_service_provider().await
}
}
#[async_trait]
impl RunnableServiceProvider for IpPacketRouter {
const NAME: &'static str = "ip router";
type OnStartData = nym_ip_packet_router::OnStartData;
type Error = IpPacketRouterError;
async fn run_service_provider(self) -> Result<(), Self::Error> {
self.run_service_provider().await
}
}
#[async_trait]
impl RunnableServiceProvider for Authenticator {
const NAME: &'static str = "authenticator";
type OnStartData = nym_authenticator::OnStartData;
type Error = nym_authenticator::error::AuthenticatorError;
async fn run_service_provider(self) -> Result<(), Self::Error> {
self.run_service_provider().await
}
}
pub struct ServiceProviderBeingBuilt<T: RunnableServiceProvider> {
on_start_rx: oneshot::Receiver<T::OnStartData>,
sp_builder: T,
sp_message_router_builder: SpMessageRouterBuilder,
}
pub struct StartedServiceProvider<T: RunnableServiceProvider> {
pub sp_join_handle: JoinHandle<()>,
pub message_router_join_handle: JoinHandle<()>,
pub on_start_data: T::OnStartData,
pub handle: LocalEmbeddedClientHandle,
}
impl<T> ServiceProviderBeingBuilt<T>
where
T: RunnableServiceProvider + Send + Sync + 'static,
T::Error: Display + Send + Sync + 'static,
{
pub(crate) fn new(
on_start_rx: oneshot::Receiver<T::OnStartData>,
sp_builder: T,
sp_message_router_builder: SpMessageRouterBuilder,
) -> Self {
ServiceProviderBeingBuilt {
on_start_rx,
sp_builder,
sp_message_router_builder,
}
}
pub async fn start_service_provider(
mut self,
) -> Result<StartedServiceProvider<T>, GatewayError> {
let sp_join_handle = tokio::task::spawn(async move {
if let Err(err) = self.sp_builder.run_service_provider().await {
error!(
"the {} service provider encountered an error: {err}",
T::NAME
)
}
});
let on_start_data = self
.on_start_rx
.await
.map_err(|_| GatewayError::ServiceProviderStartupFailure { typ: T::NAME })?;
// this should be instantaneous since the data is sent on this channel before the on start is called;
// the failure should be impossible
let Ok(Some(packet_router)) = self.sp_message_router_builder.router_receiver.try_recv()
else {
return Err(GatewayError::ServiceProviderStartupFailure { typ: T::NAME });
};
let mix_sender = self.sp_message_router_builder.mix_sender();
let message_router_join_handle = self
.sp_message_router_builder
.start_message_router(packet_router);
Ok(StartedServiceProvider {
sp_join_handle,
message_router_join_handle,
handle: LocalEmbeddedClientHandle::new(on_start_data.address(), mix_sender),
on_start_data,
})
}
}
pub struct ExitServiceProviders {
pub(crate) network_requester: ServiceProviderBeingBuilt<NRServiceProviderBuilder>,
pub(crate) ip_router: ServiceProviderBeingBuilt<IpPacketRouter>,
}
impl ExitServiceProviders {
pub async fn start_service_providers(
self,
) -> Result<
(
StartedServiceProvider<NRServiceProviderBuilder>,
StartedServiceProvider<IpPacketRouter>,
),
GatewayError,
> {
let started_nr = self.network_requester.start_service_provider().await?;
let started_ipr = self.ip_router.start_service_provider().await?;
Ok((started_nr, started_ipr))
}
}
pub struct SpMessageRouterBuilder {
mix_sender: Option<MixMessageSender>,
mix_receiver: MixMessageReceiver,
router_receiver: oneshot::Receiver<PacketRouter>,
gateway_transceiver: Option<LocalGateway>,
shutdown: TaskClient,
}
impl SpMessageRouterBuilder {
pub(crate) fn new(
node_identity: ed25519::PublicKey,
forwarding_channel: MixForwardingSender,
shutdown: TaskClient,
) -> Self {
let (mix_sender, mix_receiver) = mpsc::unbounded();
let (router_tx, router_rx) = oneshot::channel();
let transceiver = LocalGateway::new(node_identity, forwarding_channel, router_tx);
SpMessageRouterBuilder {
mix_sender: Some(mix_sender),
mix_receiver,
router_receiver: router_rx,
gateway_transceiver: Some(transceiver),
shutdown,
}
}
#[allow(clippy::expect_used)]
pub(crate) fn gateway_transceiver(&mut self) -> Box<dyn GatewayTransceiver + Send + Sync> {
Box::new(
self.gateway_transceiver
.take()
.expect("attempting to use the same gateway transceiver twice"),
)
}
#[allow(clippy::expect_used)]
fn mix_sender(&mut self) -> MixMessageSender {
self.mix_sender
.take()
.expect("attempting to use the same mix sender twice")
}
fn start_message_router(self, packet_router: PacketRouter) -> JoinHandle<()> {
MessageRouter::new(self.mix_receiver, packet_router).start_with_shutdown(self.shutdown)
}
}
@@ -6,7 +6,7 @@ use crate::node::client_handling::websocket::message_receiver::MixMessageSender;
use crate::node::mixnet_handling::receiver::packet_processing::PacketProcessor;
use futures::channel::mpsc::SendError;
use futures::StreamExt;
use nym_gateway_storage::{error::StorageError, Storage};
use nym_gateway_storage::{error::GatewayStorageError, GatewayStorage};
use nym_mixnet_client::forwarder::MixForwardingSender;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_sphinx::framing::codec::NymCodec;
@@ -30,7 +30,7 @@ enum CriticalPacketProcessingError {
AckForwardingFailure { source: SendError },
}
pub(crate) struct ConnectionHandler<St: Storage> {
pub(crate) struct ConnectionHandler {
packet_processor: PacketProcessor,
// TODO: investigate performance trade-offs for whether this cache even makes sense
@@ -39,11 +39,11 @@ pub(crate) struct ConnectionHandler<St: Storage> {
// and each `get` internally copies the channel, however, is it really that expensive?
clients_store_cache: HashMap<DestinationAddressBytes, MixMessageSender>,
active_clients_store: ActiveClientsStore,
storage: St,
storage: GatewayStorage,
ack_sender: MixForwardingSender,
}
impl<St: Storage + Clone> Clone for ConnectionHandler<St> {
impl Clone for ConnectionHandler {
fn clone(&self) -> Self {
// remove stale entries from the cache while cloning
let mut clients_store_cache = HashMap::with_capacity(self.clients_store_cache.capacity());
@@ -63,10 +63,10 @@ impl<St: Storage + Clone> Clone for ConnectionHandler<St> {
}
}
impl<St: Storage> ConnectionHandler<St> {
impl ConnectionHandler {
pub(crate) fn new(
packet_processor: PacketProcessor,
storage: St,
storage: GatewayStorage,
ack_sender: MixForwardingSender,
active_clients_store: ActiveClientsStore,
) -> Self {
@@ -123,7 +123,7 @@ impl<St: Storage> ConnectionHandler<St> {
&self,
client_address: DestinationAddressBytes,
message: Vec<u8>,
) -> Result<(), StorageError> {
) -> Result<(), GatewayStorageError> {
debug!("Storing received message for {client_address} on the disk...",);
self.storage.store_message(client_address, message).await
@@ -137,14 +137,9 @@ impl<St: Storage> ConnectionHandler<St> {
if let Some(forward_ack) = forward_ack {
let next_hop = forward_ack.next_hop();
trace!("Sending ack from packet for {client_address} to {next_hop}",);
self.ack_sender
.unbounded_send(forward_ack)
.map_err(
|source| CriticalPacketProcessingError::AckForwardingFailure {
source: source.into_send_error(),
},
)?;
.forward_packet(forward_ack)
.map_err(|source| CriticalPacketProcessingError::AckForwardingFailure { source })?;
}
Ok(())
}
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler;
use nym_gateway_storage::Storage;
use nym_task::TaskClient;
use std::net::SocketAddr;
use std::process;
@@ -20,10 +19,7 @@ impl Listener {
Listener { address, shutdown }
}
pub(crate) async fn run<St>(&mut self, connection_handler: ConnectionHandler<St>)
where
St: Storage + Clone + 'static,
{
pub(crate) async fn run(&mut self, connection_handler: ConnectionHandler) {
info!("Starting mixnet listener at {}", self.address);
let tcp_listener = match tokio::net::TcpListener::bind(self.address).await {
Ok(listener) => listener,
@@ -52,10 +48,7 @@ impl Listener {
}
}
pub(crate) fn start<St>(mut self, connection_handler: ConnectionHandler<St>) -> JoinHandle<()>
where
St: Storage + Clone + 'static,
{
pub(crate) fn start(mut self, connection_handler: ConnectionHandler) -> JoinHandle<()> {
info!("Running mix listener on {:?}", self.address.to_string());
tokio::spawn(async move { self.run(connection_handler).await })
+349 -585
View File
File diff suppressed because it is too large Load Diff
-86
View File
@@ -1,86 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_gateway_stats_storage::PersistentStatsStorage;
use nym_node_http_api::state::metrics::SharedSessionStats;
use nym_statistics_common::gateways::{
GatewayStatsEvent, GatewayStatsReceiver, GatewayStatsReporter,
};
use nym_task::TaskClient;
use sessions::SessionStatsHandler;
use std::time::Duration;
use time::OffsetDateTime;
use tracing::{error, trace, warn};
pub mod sessions;
const STATISTICS_UPDATE_TIMER_INTERVAL: Duration = Duration::from_secs(3600); //update timer, no need to check everytime
pub(crate) struct GatewayStatisticsCollector {
stats_event_rx: GatewayStatsReceiver,
session_stats: SessionStatsHandler,
//here goes additionnal stats handler
}
impl GatewayStatisticsCollector {
pub fn new(
shared_session_stats: SharedSessionStats,
stats_storage: PersistentStatsStorage,
) -> (GatewayStatisticsCollector, GatewayStatsReporter) {
let (stats_event_tx, stats_event_rx) = tokio::sync::mpsc::unbounded_channel();
let session_stats = SessionStatsHandler::new(shared_session_stats, stats_storage);
let collector = GatewayStatisticsCollector {
stats_event_rx,
session_stats,
};
let reporter = GatewayStatsReporter::new(stats_event_tx);
(collector, reporter)
}
async fn update_shared_state(&mut self, update_time: OffsetDateTime) {
if let Err(e) = self
.session_stats
.maybe_update_shared_state(update_time)
.await
{
error!("Failed to update session stats - {e}");
}
//here goes additionnal stats handler update
}
async fn on_start(&mut self) {
if let Err(e) = self.session_stats.on_start().await {
error!("Failed to cleanup session stats handler - {e}");
}
//here goes additionnal stats handler start cleanup
}
pub async fn run(&mut self, mut shutdown: TaskClient) {
self.on_start().await;
let mut update_interval = tokio::time::interval(STATISTICS_UPDATE_TIMER_INTERVAL);
while !shutdown.is_shutdown() {
tokio::select! {
biased;
_ = shutdown.recv() => {
trace!("StatisticsCollector: Received shutdown");
},
_ = update_interval.tick() => {
let now = OffsetDateTime::now_utc();
self.update_shared_state(now).await;
},
Some(stat_event) = self.stats_event_rx.recv() => {
//dispatching event to proper handler
match stat_event {
GatewayStatsEvent::SessionStatsEvent(event) => {
if let Err(e) = self.session_stats.handle_event(event).await{
warn!("Session event handling error - {e}");
}},
}
},
}
}
}
}
-54
View File
@@ -1,54 +0,0 @@
# Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
# SPDX-License-Identifier: GPL-3.0-only
[package]
name = "nym-mixnode"
license = "GPL-3.0"
version = "1.1.37"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
"Drazen Urch <durch@users.noreply.github.com>",
]
description = "Implementation of a Loopix-based Mixnode"
edition = "2021"
rust-version = "1.70"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
colored = { workspace = true }
futures = { workspace = true }
time.workspace = true
tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] }
tokio-util = { workspace = true, features = ["codec"] }
url = { workspace = true, features = ["serde"] }
tracing = { workspace = true }
thiserror = { workspace = true }
# internal
nym-crypto = { path = "../common/crypto" }
nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" }
nym-http-api-common = { path = "../common/http-api-common" }
nym-mixnet-client = { path = "../common/client-libs/mixnet-client" }
nym-mixnode-common = { path = "../common/mixnode-common" }
nym-metrics = { path = "../common/nym-metrics" }
nym-nonexhaustive-delayqueue = { path = "../common/nonexhaustive-delayqueue" }
nym-node-http-api = { path = "../nym-node/nym-node-http-api" }
nym-sphinx = { path = "../common/nymsphinx" }
nym-sphinx-params = { path = "../common/nymsphinx/params" }
nym-task = { path = "../common/task" }
nym-types = { path = "../common/types" }
nym-topology = { path = "../common/topology" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
[dev-dependencies]
tokio = { workspace = true, features = [
"rt-multi-thread",
"net",
"signal",
"test-util",
] }
nym-sphinx-types = { path = "../common/nymsphinx/types" }
nym-sphinx-params = { path = "../common/nymsphinx/params" }
-75
View File
@@ -1,75 +0,0 @@
<!--
Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
SPDX-License-Identifier: GPL-3.0-only
-->
# Nym Mixnode
A Rust mixnode implementation.
## License
Copyright (C) 2020 Nym Technologies SA <contact@nymtech.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
## Usage
* `nym-mixnode` prints a help message showing usage options
* `nym-mixnode run --help` prints a help message showing usage options for the run command
* `nym-mixnode run --layer 1 --host x.x.x.x` will start the mixnode in layer 1 and bind to the specified host IP address. Coordinate with other people in your network to find out which layer needs coverage.
By default, the Nym Mixnode will start on port 1789. If desired, you can change the port using the `--port` option.
## Install debian
```bash
sudo curl -s --compressed "http://apt.nymtech.net.s3-website.eu-central-1.amazonaws.com/nymtech.gpg" | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/nymtech.gpg > /dev/null
sudo echo "deb [signed-by=/etc/apt/trusted.gpg.d/nymtech.gpg] http://apt.nymtech.net.s3-website.eu-central-1.amazonaws.com/ squeeze main" > /etc/apt/sources.list.d/nymtech.list
sudo apt-get update
sudo apt-get install nym-mixnode
# See below for starting and managing the node
```
## Systemd support
```bash
sudo systemctl enable nym-mixnode
# Run
sudo systemctl start nym-mixnode
# Check status
sudo systemctl status nym-mixnode
# Logs
journalctl -f -u nym-mixnode
```
## Build debian package
```bash
# cargo install cargo-deb
# Build package
cargo deb -p nym-mixnode
# Install
# This will init the mixnode to `/etc/nym` as `nym` user, and create a systemd service
sudo dpkg -i target/debian/<PACKAGE>
```
-237
View File
@@ -1,237 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use std::net::{IpAddr, SocketAddr};
use std::path::PathBuf;
use std::time::Duration;
use url::Url;
// 'RTT MEASUREMENT'
const DEFAULT_PACKETS_PER_NODE: usize = 100;
const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000);
const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500);
const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50);
const DEFAULT_BATCH_SIZE: usize = 50;
const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12);
const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30);
// 'DEBUG'
const DEFAULT_NODE_STATS_LOGGING_DELAY: Duration = Duration::from_millis(60_000);
const DEFAULT_NODE_STATS_UPDATING_DELAY: Duration = Duration::from_millis(30_000);
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
#[derive(Debug, PartialEq)]
pub struct Config {
pub host: Host,
pub http: Http,
pub mixnode: MixNode,
pub verloc: Verloc,
pub debug: Debug,
}
impl Config {
pub fn externally_loaded(
host: impl Into<Host>,
http: impl Into<Http>,
mixnode: impl Into<MixNode>,
verloc: impl Into<Verloc>,
debug: impl Into<Debug>,
) -> Self {
Config {
host: host.into(),
http: http.into(),
mixnode: mixnode.into(),
verloc: verloc.into(),
debug: debug.into(),
}
}
// builder methods
pub fn with_custom_nym_apis(mut self, nym_api_urls: Vec<Url>) -> Self {
self.mixnode.nym_api_urls = nym_api_urls;
self
}
pub fn with_listening_address(mut self, listening_address: IpAddr) -> Self {
self.mixnode.listening_address = listening_address;
let http_port = self.http.bind_address.port();
self.http.bind_address = SocketAddr::new(listening_address, http_port);
self
}
pub fn with_mix_port(mut self, port: u16) -> Self {
self.mixnode.mix_port = port;
self
}
pub fn with_verloc_port(mut self, port: u16) -> Self {
self.mixnode.verloc_port = port;
self
}
pub fn with_http_api_port(mut self, port: u16) -> Self {
let http_ip = self.http.bind_address.ip();
self.http.bind_address = SocketAddr::new(http_ip, port);
self
}
pub fn get_nym_api_endpoints(&self) -> Vec<Url> {
self.mixnode.nym_api_urls.clone()
}
pub fn with_metrics_key(mut self, metrics_key: String) -> Self {
self.http.metrics_key = Some(metrics_key);
self
}
pub fn metrics_key(&self) -> Option<&String> {
self.http.metrics_key.as_ref()
}
}
// TODO: this is very much a WIP. we need proper ssl certificate support here
#[derive(Debug, PartialEq)]
pub struct Host {
/// Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections.
pub public_ips: Vec<IpAddr>,
/// Optional hostname of this node, for example nymtech.net.
// TODO: this is temporary. to be replaced by pulling the data directly from the certs.
pub hostname: Option<String>,
}
impl Host {
pub fn validate(&self) -> bool {
if self.public_ips.is_empty() {
return false;
}
true
}
}
#[derive(Debug, PartialEq)]
pub struct Http {
/// Socket address this node will use for binding its http API.
/// default: `0.0.0.0:8000`
pub bind_address: SocketAddr,
/// Path to assets directory of custom landing page of this node.
pub landing_page_assets_path: Option<PathBuf>,
pub metrics_key: Option<String>,
}
#[derive(Debug, PartialEq)]
pub struct MixNode {
/// Version of the mixnode for which this configuration was created.
pub version: String,
/// ID specifies the human readable ID of this particular mixnode.
pub id: String,
/// Address to which this mixnode will bind to and will be listening for packets.
pub listening_address: IpAddr,
/// Port used for listening for all mixnet traffic.
/// (default: 1789)
pub mix_port: u16,
/// Port used for listening for verloc traffic.
/// (default: 1790)
pub verloc_port: u16,
/// Addresses to nym APIs from which the node gets the view of the network.
pub nym_api_urls: Vec<Url>,
}
#[derive(Debug, PartialEq)]
pub struct Verloc {
/// Specifies number of echo packets sent to each node during a measurement run.
pub packets_per_node: usize,
/// Specifies maximum amount of time to wait for the connection to get established.
pub connection_timeout: Duration,
/// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test.
pub packet_timeout: Duration,
/// Specifies delay between subsequent test packets being sent (after receiving a reply).
pub delay_between_packets: Duration,
/// Specifies number of nodes being tested at once.
pub tested_nodes_batch_size: usize,
/// Specifies delay between subsequent test runs.
pub testing_interval: Duration,
/// Specifies delay between attempting to run the measurement again if the previous run failed
/// due to being unable to get the list of nodes.
pub retry_timeout: Duration,
}
impl Default for Verloc {
fn default() -> Self {
Verloc {
packets_per_node: DEFAULT_PACKETS_PER_NODE,
connection_timeout: DEFAULT_CONNECTION_TIMEOUT,
packet_timeout: DEFAULT_PACKET_TIMEOUT,
delay_between_packets: DEFAULT_DELAY_BETWEEN_PACKETS,
tested_nodes_batch_size: DEFAULT_BATCH_SIZE,
testing_interval: DEFAULT_TESTING_INTERVAL,
retry_timeout: DEFAULT_RETRY_TIMEOUT,
}
}
}
#[derive(Debug, PartialEq)]
pub struct Debug {
/// Delay between each subsequent node statistics being logged to the console
pub node_stats_logging_delay: Duration,
/// Delay between each subsequent node statistics being updated
pub node_stats_updating_delay: Duration,
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
pub packet_forwarding_initial_backoff: Duration,
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
pub packet_forwarding_maximum_backoff: Duration,
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
pub initial_connection_timeout: Duration,
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
pub maximum_connection_buffer_size: usize,
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
// it's set to true by default. The reason for that decision is to preserve compatibility with the
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
// It shall be disabled in the subsequent releases.
pub use_legacy_framed_packet_version: bool,
}
impl Default for Debug {
fn default() -> Self {
Debug {
node_stats_logging_delay: DEFAULT_NODE_STATS_LOGGING_DELAY,
node_stats_updating_delay: DEFAULT_NODE_STATS_UPDATING_DELAY,
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
use_legacy_framed_packet_version: false,
}
}
}
-7
View File
@@ -1,7 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
pub mod config;
pub mod node;
pub use node::MixNode;
@@ -1,129 +0,0 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::listener::connection_handler::packet_processing::PacketProcessor;
use crate::node::packet_delayforwarder::PacketDelayForwardSender;
use crate::node::TaskClient;
use futures::StreamExt;
use nym_metrics::nanos;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_sphinx::framing::codec::NymCodec;
use nym_sphinx::framing::packet::FramedNymPacket;
use nym_sphinx::framing::processing::MixProcessingResult;
use nym_sphinx::Delay as SphinxDelay;
use packet_processing::process_received_packet;
use std::net::SocketAddr;
use tokio::net::TcpStream;
use tokio::time::Instant;
use tokio_util::codec::Framed;
use tracing::{debug, error, info, trace, warn};
pub(crate) mod packet_processing;
#[derive(Clone)]
pub(crate) struct ConnectionHandler {
packet_processor: PacketProcessor,
delay_forwarding_channel: PacketDelayForwardSender,
}
impl ConnectionHandler {
pub(crate) fn new(
packet_processor: PacketProcessor,
delay_forwarding_channel: PacketDelayForwardSender,
) -> Self {
ConnectionHandler {
packet_processor,
delay_forwarding_channel,
}
}
pub fn packet_processor(&self) -> &PacketProcessor {
&self.packet_processor
}
fn delay_and_forward_packet(&self, mix_packet: MixPacket, delay: Option<SphinxDelay>) {
// determine instant at which packet should get forwarded. this way we minimise effect of
// being stuck in the queue [of the channel] to get inserted into the delay queue
let forward_instant = delay.map(|delay| Instant::now() + delay.to_duration());
// if unbounded_send() failed it means that the receiver channel was disconnected
// and hence something weird must have happened without a way of recovering
self.delay_forwarding_channel
.unbounded_send((mix_packet, forward_instant))
.expect("the delay-forwarder has died!");
}
fn handle_received_packet(&self, framed_sphinx_packet: FramedNymPacket) {
//
// TODO: here be replay attack detection - it will require similar key cache to the one in
// packet processor for vpn packets,
// question: can it also be per connection vs global?
//
// all processing such, key caching, etc. was done.
// however, if it was a forward hop, we still need to delay it
nanos!("handle_received_packet", {
self.packet_processor
.node_stats_update_sender()
.report_received();
match process_received_packet(framed_sphinx_packet, self.packet_processor().inner()) {
Err(err) => debug!("We failed to process received sphinx packet - {err}"),
Ok(res) => match res {
MixProcessingResult::ForwardHop(forward_packet, delay) => {
self.delay_and_forward_packet(forward_packet, delay)
}
MixProcessingResult::FinalHop(..) => {
warn!("Somehow processed a loop cover message that we haven't implemented yet!")
}
},
}
})
}
pub(crate) async fn handle_connection(
self,
conn: TcpStream,
remote: SocketAddr,
mut shutdown: TaskClient,
) {
debug!("Starting connection handler for {:?}", remote);
shutdown.disarm();
let mut framed_conn = Framed::new(conn, NymCodec);
while !shutdown.is_shutdown() {
tokio::select! {
biased;
_ = shutdown.recv() => {
trace!("ConnectionHandler: received shutdown");
}
framed_sphinx_packet = framed_conn.next() => {
match framed_sphinx_packet {
Some(Ok(framed_sphinx_packet)) => {
// TODO: benchmark spawning tokio task with full processing vs just processing it
// synchronously (without delaying inside of course,
// delay is moved to a global DelayQueue)
// under higher load in single and multi-threaded situation.
// in theory we could process multiple sphinx packet from the same connection in parallel,
// but we already handle multiple concurrent connections so if anything, making
// that change would only slow things down
self.handle_received_packet(framed_sphinx_packet);
}
Some(Err(err)) => {
error!(
"{remote:?} - The socket connection got corrupted with error: {err}. Closing the socket",
);
return;
}
None => break, // stream got closed by remote
}
},
}
}
info!(
"Closing connection from {:?}",
framed_conn.into_inner().peer_addr()
);
trace!("ConnectionHandler: Exiting");
}
}
@@ -1,46 +0,0 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::node_statistics;
use nym_crypto::asymmetric::encryption;
use nym_mixnode_common::packet_processor::error::MixProcessingError;
use nym_mixnode_common::packet_processor::processor::SphinxPacketProcessor;
use nym_sphinx::framing::packet::FramedNymPacket;
use nym_sphinx::framing::processing::{process_framed_packet, MixProcessingResult};
// PacketProcessor contains all data required to correctly unwrap and forward sphinx packets
#[derive(Clone)]
pub(crate) struct PacketProcessor {
/// Responsible for performing unwrapping
inner_processor: SphinxPacketProcessor,
/// Responsible for updating metrics data
node_stats_update_sender: node_statistics::UpdateSender,
}
impl PacketProcessor {
pub(crate) fn new(
encryption_key: &encryption::PrivateKey,
node_stats_update_sender: node_statistics::UpdateSender,
) -> Self {
PacketProcessor {
inner_processor: SphinxPacketProcessor::new(encryption_key.into()),
node_stats_update_sender,
}
}
pub fn inner(&self) -> &SphinxPacketProcessor {
&self.inner_processor
}
pub fn node_stats_update_sender(&self) -> &node_statistics::UpdateSender {
&self.node_stats_update_sender
}
}
pub fn process_received_packet(
packet: FramedNymPacket,
inner_processor: &SphinxPacketProcessor,
) -> Result<MixProcessingResult, MixProcessingError> {
Ok(process_framed_packet(packet, inner_processor.sphinx_key())?)
}
-60
View File
@@ -1,60 +0,0 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::listener::connection_handler::ConnectionHandler;
use std::net::SocketAddr;
use std::process;
use tokio::net::TcpListener;
use tokio::task::JoinHandle;
use tracing::{error, info, trace, warn};
use super::TaskClient;
pub(crate) mod connection_handler;
pub(crate) struct Listener {
address: SocketAddr,
shutdown: TaskClient,
}
impl Listener {
pub(crate) fn new(address: SocketAddr, shutdown: TaskClient) -> Self {
Listener { address, shutdown }
}
async fn run(&mut self, connection_handler: ConnectionHandler) {
trace!("Starting Listener");
let listener = match TcpListener::bind(self.address).await {
Ok(listener) => listener,
Err(err) => {
error!("Failed to bind to {} - {err}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?", self.address);
process::exit(1);
}
};
while !self.shutdown.is_shutdown() {
tokio::select! {
biased;
_ = self.shutdown.recv() => {
trace!("Listener: Received shutdown");
}
connection = listener.accept() => {
match connection {
Ok((socket, remote_addr)) => {
let handler = connection_handler.clone();
tokio::spawn(handler.handle_connection(socket, remote_addr, self.shutdown.clone()));
}
Err(err) => warn!("Failed to accept incoming connection - {err}"),
}
},
};
}
trace!("Listener: Exiting");
}
pub(crate) fn start(mut self, connection_handler: ConnectionHandler) -> JoinHandle<()> {
info!("Running mix listener on {:?}", self.address.to_string());
tokio::spawn(async move { self.run(connection_handler).await })
}
}
-217
View File
@@ -1,217 +0,0 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::Config;
use crate::node::listener::connection_handler::packet_processing::PacketProcessor;
use crate::node::listener::connection_handler::ConnectionHandler;
use crate::node::listener::Listener;
use crate::node::packet_delayforwarder::{DelayForwarder, PacketDelayForwardSender};
use nym_crypto::asymmetric::{encryption, identity};
use nym_mixnode_common::verloc;
use nym_mixnode_common::verloc::VerlocMeasurer;
use nym_node_http_api::state::metrics::{SharedMixingStats, SharedVerlocStats};
use nym_task::{TaskClient, TaskHandle};
use std::net::SocketAddr;
use std::process;
use std::sync::Arc;
use tracing::{error, info, warn};
mod listener;
mod node_statistics;
mod packet_delayforwarder;
// the MixNode will live for whole duration of this program
pub struct MixNode {
config: Config,
identity_keypair: Arc<identity::KeyPair>,
sphinx_keypair: Arc<encryption::KeyPair>,
task_client: Option<TaskClient>,
mixing_stats: Option<SharedMixingStats>,
verloc_stats: Option<SharedVerlocStats>,
}
impl MixNode {
pub fn new_loaded(
config: Config,
identity_keypair: Arc<identity::KeyPair>,
sphinx_keypair: Arc<encryption::KeyPair>,
) -> Self {
MixNode {
task_client: None,
config,
identity_keypair,
sphinx_keypair,
mixing_stats: None,
verloc_stats: None,
}
}
pub fn set_task_client(&mut self, task_client: TaskClient) {
self.task_client = Some(task_client)
}
pub fn set_mixing_stats(&mut self, mixing_stats: SharedMixingStats) {
self.mixing_stats = Some(mixing_stats);
}
pub fn set_verloc_stats(&mut self, verloc_stats: SharedVerlocStats) {
self.verloc_stats = Some(verloc_stats)
}
fn start_node_stats_controller(
&mut self,
shutdown: TaskClient,
) -> (SharedMixingStats, node_statistics::UpdateSender) {
info!("Starting node stats controller...");
let mixing_stats = self.mixing_stats.take().unwrap_or_default();
let controller = node_statistics::Controller::new(
self.config.debug.node_stats_logging_delay,
self.config.debug.node_stats_updating_delay,
mixing_stats.clone(),
shutdown,
);
let update_sender = controller.start();
(mixing_stats, update_sender)
}
fn start_socket_listener(
&self,
node_stats_update_sender: node_statistics::UpdateSender,
delay_forwarding_channel: PacketDelayForwardSender,
shutdown: TaskClient,
) {
info!("Starting socket listener...");
let packet_processor =
PacketProcessor::new(self.sphinx_keypair.private_key(), node_stats_update_sender);
let connection_handler = ConnectionHandler::new(packet_processor, delay_forwarding_channel);
let listening_address = SocketAddr::new(
self.config.mixnode.listening_address,
self.config.mixnode.mix_port,
);
Listener::new(listening_address, shutdown).start(connection_handler);
}
fn start_packet_delay_forwarder(
&mut self,
node_stats_update_sender: node_statistics::UpdateSender,
shutdown: TaskClient,
) -> PacketDelayForwardSender {
info!("Starting packet delay-forwarder...");
let client_config = nym_mixnet_client::Config::new(
self.config.debug.packet_forwarding_initial_backoff,
self.config.debug.packet_forwarding_maximum_backoff,
self.config.debug.initial_connection_timeout,
self.config.debug.maximum_connection_buffer_size,
self.config.debug.use_legacy_framed_packet_version,
);
let mut packet_forwarder = DelayForwarder::new(
nym_mixnet_client::Client::new(client_config),
node_stats_update_sender,
shutdown,
);
let packet_sender = packet_forwarder.sender();
tokio::spawn(async move { packet_forwarder.run().await });
packet_sender
}
fn start_verloc_measurements(&mut self, shutdown: TaskClient) -> SharedVerlocStats {
info!("Starting the round-trip-time measurer...");
// use the same binding address with the HARDCODED port for time being (I don't like that approach personally)
let listening_address = SocketAddr::new(
self.config.mixnode.listening_address,
self.config.mixnode.verloc_port,
);
let config = verloc::ConfigBuilder::new()
.listening_address(listening_address)
.packets_per_node(self.config.verloc.packets_per_node)
.connection_timeout(self.config.verloc.connection_timeout)
.packet_timeout(self.config.verloc.packet_timeout)
.delay_between_packets(self.config.verloc.delay_between_packets)
.tested_nodes_batch_size(self.config.verloc.tested_nodes_batch_size)
.testing_interval(self.config.verloc.testing_interval)
.retry_timeout(self.config.verloc.retry_timeout)
.nym_api_urls(self.config.get_nym_api_endpoints())
.build();
let verloc_state = self.verloc_stats.take().unwrap_or_default();
let mut verloc_measurer =
VerlocMeasurer::new(config, Arc::clone(&self.identity_keypair), shutdown);
verloc_measurer.set_shared_state(verloc_state.clone());
tokio::spawn(async move { verloc_measurer.run().await });
verloc_state
}
async fn check_if_bonded(&self) -> bool {
// TODO: if anything, this should be getting data directly from the contract
// as opposed to the validator API
for api_url in self.config.get_nym_api_endpoints() {
let client = nym_validator_client::NymApiClient::new(api_url.clone());
match client.get_all_basic_nodes(None).await {
Ok(nodes) => {
return nodes.iter().any(|node| {
&node.ed25519_identity_pubkey == self.identity_keypair.public_key()
})
}
Err(err) => {
error!("failed to grab initial network mixnodes from {api_url}: {err}",);
}
}
}
error!(
"failed to grab initial network mixnodes from any of the available apis. Please try to startup again in few minutes",
);
process::exit(1);
}
async fn wait_for_interrupt(&self, shutdown: TaskHandle) {
let _res = shutdown.wait_for_shutdown().await;
info!("Stopping nym mixnode");
}
pub async fn run(&mut self) {
info!("Starting nym mixnode");
if self.check_if_bonded().await {
warn!("You seem to have bonded your mixnode before starting it - that's highly unrecommended as in the future it might result in slashing");
}
// Shutdown notifier for signalling tasks to stop
let shutdown = self
.task_client
.take()
.map(Into::<TaskHandle>::into)
.unwrap_or_default()
.name_if_unnamed("mixnode");
let (_, node_stats_update_sender) =
self.start_node_stats_controller(shutdown.fork("node_statistics::Controller"));
let delay_forwarding_channel = self.start_packet_delay_forwarder(
node_stats_update_sender.clone(),
shutdown.fork("DelayForwarder"),
);
self.start_socket_listener(
node_stats_update_sender,
delay_forwarding_channel,
shutdown.fork("Listener"),
);
self.start_verloc_measurements(shutdown.fork("VerlocMeasurer"));
info!("Finished nym mixnode startup procedure - it should now be able to receive mix traffic!");
self.wait_for_interrupt(shutdown).await;
}
}
-435
View File
@@ -1,435 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_metrics::inc_by;
use super::TaskClient;
use futures::channel::mpsc;
use futures::lock::Mutex;
use futures::StreamExt;
use nym_node_http_api::state::metrics::SharedMixingStats;
use std::collections::HashMap;
use std::ops::DerefMut;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;
use time::OffsetDateTime;
use tracing::{debug, info, trace};
// convenience aliases
type PacketsMap = HashMap<String, u64>;
type PacketDataReceiver = mpsc::UnboundedReceiver<PacketEvent>;
type PacketDataSender = mpsc::UnboundedSender<PacketEvent>;
trait MixingStatsUpdateExt {
async fn update(&self, new_received: u64, new_sent: PacketsMap, new_dropped: PacketsMap);
}
impl MixingStatsUpdateExt for SharedMixingStats {
async fn update(&self, new_received: u64, new_sent: PacketsMap, new_dropped: PacketsMap) {
let mut guard = self.write().await;
let snapshot_time = OffsetDateTime::now_utc();
guard.previous_update_time = guard.update_time;
guard.update_time = snapshot_time;
guard.packets_received_since_startup += new_received;
for count in new_sent.values() {
guard.packets_sent_since_startup_all += count;
}
for count in new_dropped.values() {
guard.packets_dropped_since_startup_all += count;
}
inc_by!("packets_received_since_startup", new_received);
inc_by!(
"packets_sent_since_startup_all",
new_sent.values().sum::<u64>()
);
inc_by!(
"packets_dropped_since_startup_all",
new_dropped.values().sum::<u64>()
);
guard.packets_received_since_last_update = new_received;
guard.packets_sent_since_last_update = new_sent;
guard.packets_explicitly_dropped_since_last_update = new_dropped;
}
}
pub(crate) enum PacketEvent {
Sent(String),
Received,
Dropped(String),
}
#[derive(Debug, Clone)]
struct CurrentPacketData {
inner: Arc<PacketDataInner>,
}
#[derive(Debug)]
struct PacketDataInner {
received: AtomicU64,
sent: Mutex<PacketsMap>,
dropped: Mutex<PacketsMap>,
}
impl CurrentPacketData {
pub(crate) fn new() -> Self {
CurrentPacketData {
inner: Arc::new(PacketDataInner {
received: AtomicU64::new(0),
sent: Mutex::new(HashMap::new()),
dropped: Mutex::new(HashMap::new()),
}),
}
}
fn increment_received(&self) {
self.inner.received.fetch_add(1, Ordering::SeqCst);
}
async fn increment_sent(&self, destination: String) {
let mut unlocked = self.inner.sent.lock().await;
let receiver_count = unlocked.entry(destination).or_insert(0);
*receiver_count += 1;
}
async fn increment_dropped(&self, destination: String) {
let mut unlocked = self.inner.dropped.lock().await;
let dropped_count = unlocked.entry(destination).or_insert(0);
*dropped_count += 1;
}
async fn acquire_and_reset(&self) -> (u64, PacketsMap, PacketsMap) {
let mut unlocked_sent = self.inner.sent.lock().await;
let mut unlocked_dropped = self.inner.dropped.lock().await;
let received = self.inner.received.swap(0, Ordering::SeqCst);
let sent = std::mem::take(unlocked_sent.deref_mut());
let dropped = std::mem::take(unlocked_dropped.deref_mut());
(received, sent, dropped)
}
}
// Worker that listens to a channel and updates the shared current packet data
struct UpdateHandler {
current_data: CurrentPacketData,
update_receiver: PacketDataReceiver,
shutdown: TaskClient,
}
impl UpdateHandler {
fn new(
current_data: CurrentPacketData,
update_receiver: PacketDataReceiver,
shutdown: TaskClient,
) -> Self {
UpdateHandler {
current_data,
update_receiver,
shutdown,
}
}
async fn run(&mut self) {
trace!("Starting UpdateHandler");
while !self.shutdown.is_shutdown() {
tokio::select! {
Some(packet_data) = self.update_receiver.next() => {
match packet_data {
PacketEvent::Received => self.current_data.increment_received(),
PacketEvent::Sent(destination) => {
self.current_data.increment_sent(destination).await
}
PacketEvent::Dropped(destination) => {
self.current_data.increment_dropped(destination).await
}
}
}
_ = self.shutdown.recv() => {
trace!("UpdateHandler: Received shutdown");
break;
}
}
}
trace!("UpdateHandler: Exiting");
}
}
// Channel to report statistics
#[derive(Clone)]
pub struct UpdateSender(PacketDataSender);
impl UpdateSender {
pub(crate) fn new(update_sender: PacketDataSender) -> Self {
UpdateSender(update_sender)
}
pub(crate) fn report_sent(&self, destination: String) {
// in unbounded_send() failed it means that the receiver channel was disconnected
// and hence something weird must have happened without a way of recovering
self.0
.unbounded_send(PacketEvent::Sent(destination))
.unwrap()
}
// TODO: in the future this could be slightly optimised to get rid of the channel
// in favour of incrementing value directly
pub(crate) fn report_received(&self) {
// in unbounded_send() failed it means that the receiver channel was disconnected
// and hence something weird must have happened without a way of recovering
self.0.unbounded_send(PacketEvent::Received).unwrap()
}
pub(crate) fn report_dropped(&self, destination: String) {
// in unbounded_send() failed it means that the receiver channel was disconnected
// and hence something weird must have happened without a way of recovering
self.0
.unbounded_send(PacketEvent::Dropped(destination))
.unwrap()
}
}
// Worker that periodically updates the shared node stats from the current packet data buffer that
// the `UpdateHandler` updates.
struct StatsUpdater {
updating_delay: Duration,
current_packet_data: CurrentPacketData,
current_stats: SharedMixingStats,
shutdown: TaskClient,
}
impl StatsUpdater {
fn new(
updating_delay: Duration,
current_packet_data: CurrentPacketData,
current_stats: SharedMixingStats,
shutdown: TaskClient,
) -> Self {
StatsUpdater {
updating_delay,
current_packet_data,
current_stats,
shutdown,
}
}
async fn update_stats(&self) {
// grab new data since last update
let (received, sent, dropped) = self.current_packet_data.acquire_and_reset().await;
self.current_stats.update(received, sent, dropped).await;
}
async fn run(&mut self) {
while !self.shutdown.is_shutdown() {
tokio::select! {
_ = tokio::time::sleep(self.updating_delay) => self.update_stats().await,
_ = self.shutdown.recv() => {
trace!("StatsUpdater: Received shutdown");
}
}
}
trace!("StatsUpdater: Exiting");
}
}
// TODO: question: should this data still be logged to the console or should we perhaps remove it
// since we have the http endpoint now?
struct PacketStatsConsoleLogger {
logging_delay: Duration,
stats: SharedMixingStats,
shutdown: TaskClient,
}
impl PacketStatsConsoleLogger {
fn new(logging_delay: Duration, stats: SharedMixingStats, shutdown: TaskClient) -> Self {
PacketStatsConsoleLogger {
logging_delay,
stats,
shutdown,
}
}
async fn log_running_stats(&mut self) {
let stats = self.stats.read().await;
// it's super unlikely this will ever fail, but anything involving time is super weird
// so let's just guard against it
let time_difference = stats.update_time - stats.previous_update_time;
if time_difference.is_positive() {
// we honestly don't care if it was 30.000828427s or 30.002461449s, 30s is enough
let difference_secs = time_difference.whole_seconds();
info!(
"Since startup mixed {} packets! ({} in last {} seconds)",
stats.packets_sent_since_startup_all,
stats.packets_sent_since_last_update.values().sum::<u64>(),
difference_secs,
);
if stats.packets_dropped_since_startup_all > 0 {
info!(
"Since startup dropped {} packets! ({} in last {} seconds)",
stats.packets_dropped_since_startup_all,
stats
.packets_explicitly_dropped_since_last_update
.values()
.sum::<u64>(),
difference_secs,
);
}
debug!(
"Since startup received {} packets ({} in last {} seconds)",
stats.packets_received_since_startup,
stats.packets_received_since_last_update,
difference_secs,
);
trace!(
"Since startup sent packets to the following: \n{:#?} \n And in last {} seconds: {:#?})",
stats.packets_sent_since_startup_all,
difference_secs,
stats.packets_sent_since_last_update
);
} else {
info!(
"Since startup mixed {} packets!",
stats.packets_sent_since_startup_all,
);
if stats.packets_dropped_since_startup_all > 0 {
info!(
"Since startup dropped {} packets!",
stats.packets_dropped_since_startup_all,
);
}
debug!(
"Since startup received {} packets",
stats.packets_received_since_startup
);
trace!(
"Since startup sent packets {}",
stats.packets_sent_since_startup_all
);
}
}
async fn run(&mut self) {
trace!("Starting PacketStatsConsoleLogger");
while !self.shutdown.is_shutdown() {
tokio::select! {
_ = tokio::time::sleep(self.logging_delay) => self.log_running_stats().await,
_ = self.shutdown.recv() => {
trace!("PacketStatsConsoleLogger: Received shutdown");
}
};
}
trace!("PacketStatsConsoleLogger: Exiting");
}
}
// basically an easy single entry point to start all of the required tasks
pub struct Controller {
/// Responsible for handling data coming from UpdateSender
update_handler: UpdateHandler,
/// Wrapper around channel sending information about new packet being received or sent
update_sender: UpdateSender,
/// Responsible for logging stats to the console at given interval
console_logger: PacketStatsConsoleLogger,
/// Responsible for updating stats at given interval
stats_updater: StatsUpdater,
}
impl Controller {
pub(crate) fn new(
logging_delay: Duration,
stats_updating_delay: Duration,
mixing_stats: SharedMixingStats,
shutdown: TaskClient,
) -> Self {
let (sender, receiver) = mpsc::unbounded();
let shared_packet_data = CurrentPacketData::new();
Controller {
update_handler: UpdateHandler::new(
shared_packet_data.clone(),
receiver,
shutdown.clone(),
),
update_sender: UpdateSender::new(sender),
console_logger: PacketStatsConsoleLogger::new(
logging_delay,
mixing_stats.clone(),
shutdown.clone(),
),
stats_updater: StatsUpdater::new(
stats_updating_delay,
shared_packet_data,
mixing_stats.clone(),
shutdown,
),
}
}
// reporter is how node is going to be accessing the metrics data
pub(crate) fn start(self) -> UpdateSender {
// move out of self
let mut update_handler = self.update_handler;
let mut stats_updater = self.stats_updater;
let mut console_logger = self.console_logger;
tokio::spawn(async move { update_handler.run().await });
tokio::spawn(async move { stats_updater.run().await });
tokio::spawn(async move { console_logger.run().await });
self.update_sender
}
}
#[cfg(test)]
mod tests {
use super::*;
use nym_metrics::metrics;
use nym_task::TaskManager;
#[tokio::test]
async fn node_stats_reported_are_received() {
let logging_delay = Duration::from_millis(20);
let stats_updating_delay = Duration::from_millis(10);
let shutdown = TaskManager::default();
let stats = SharedMixingStats::new();
let node_stats_controller = Controller::new(
logging_delay,
stats_updating_delay,
stats.clone(),
shutdown.subscribe(),
);
let update_sender = node_stats_controller.start();
tokio::time::pause();
// Pass input
update_sender.report_sent("foo".to_string());
update_sender.report_sent("foo".to_string());
tokio::task::yield_now().await;
tokio::time::advance(Duration::from_secs(1)).await;
tokio::task::yield_now().await;
// Get output (stats)
let stats = stats.read().await;
assert_eq!(&stats.packets_sent_since_startup_all, &2);
assert_eq!(&stats.packets_sent_since_last_update.get("foo"), &Some(&2));
assert_eq!(&stats.packets_sent_since_last_update.len(), &1);
assert_eq!(&stats.packets_received_since_startup, &0);
assert_eq!(&stats.packets_dropped_since_startup_all, &0);
assert_eq!(metrics!(), "# HELP nym_mixnode_packets_dropped_since_startup_all nym_mixnode_packets_dropped_since_startup_all\n# TYPE nym_mixnode_packets_dropped_since_startup_all counter\nnym_mixnode_packets_dropped_since_startup_all 0\n# HELP nym_mixnode_packets_received_since_startup nym_mixnode_packets_received_since_startup\n# TYPE nym_mixnode_packets_received_since_startup counter\nnym_mixnode_packets_received_since_startup 0\n# HELP nym_mixnode_packets_sent_since_startup_all nym_mixnode_packets_sent_since_startup_all\n# TYPE nym_mixnode_packets_sent_since_startup_all counter\nnym_mixnode_packets_sent_since_startup_all 2\n")
}
}
-319
View File
@@ -1,319 +0,0 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use super::TaskClient;
use crate::node::node_statistics::UpdateSender;
use futures::channel::mpsc;
use futures::StreamExt;
use nym_nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue};
use nym_sphinx::forwarding::packet::MixPacket;
use std::io;
use tokio::time::Instant;
use tracing::trace;
// Delay + MixPacket vs Instant + MixPacket
// rather than using Duration directly, we use an Instant, this way we minimise skew due to
// time packet spent waiting in the queue to get delayed
pub(crate) type PacketDelayForwardSender = mpsc::UnboundedSender<(MixPacket, Option<Instant>)>;
type PacketDelayForwardReceiver = mpsc::UnboundedReceiver<(MixPacket, Option<Instant>)>;
/// Entity responsible for delaying received sphinx packet and forwarding it to next node.
pub(crate) struct DelayForwarder<C>
where
C: nym_mixnet_client::SendWithoutResponse,
{
delay_queue: NonExhaustiveDelayQueue<MixPacket>,
mixnet_client: C,
packet_sender: PacketDelayForwardSender,
packet_receiver: PacketDelayForwardReceiver,
node_stats_update_sender: UpdateSender,
shutdown: TaskClient,
}
impl<C> DelayForwarder<C>
where
C: nym_mixnet_client::SendWithoutResponse,
{
pub(crate) fn new(
client: C,
node_stats_update_sender: UpdateSender,
shutdown: TaskClient,
) -> DelayForwarder<C> {
let (packet_sender, packet_receiver) = mpsc::unbounded();
DelayForwarder::<C> {
delay_queue: NonExhaustiveDelayQueue::new(),
mixnet_client: client,
packet_sender,
packet_receiver,
node_stats_update_sender,
shutdown,
}
}
pub(crate) fn sender(&self) -> PacketDelayForwardSender {
self.packet_sender.clone()
}
fn forward_packet(&mut self, packet: MixPacket) {
let next_hop = packet.next_hop();
let packet_type = packet.packet_type();
let packet = packet.into_packet();
if let Err(err) = self
.mixnet_client
.send_without_response(next_hop, packet, packet_type)
{
if err.kind() == io::ErrorKind::WouldBlock {
// we only know for sure if we dropped a packet if our sending queue was full
// in any other case the connection might still be re-established (or created for the first time)
// and the packet might get sent, but we won't know about it
self.node_stats_update_sender
.report_dropped(next_hop.to_string())
} else if err.kind() == io::ErrorKind::NotConnected {
// let's give the benefit of the doubt and assume we manage to establish connection
self.node_stats_update_sender
.report_sent(next_hop.to_string());
}
} else {
self.node_stats_update_sender
.report_sent(next_hop.to_string());
}
}
/// Upon packet being finished getting delayed, forward it to the mixnet.
fn handle_done_delaying(&mut self, packet: Expired<MixPacket>) {
let delayed_packet = packet.into_inner();
self.forward_packet(delayed_packet)
}
fn handle_new_packet(&mut self, new_packet: (MixPacket, Option<Instant>)) {
// in case of a zero delay packet, don't bother putting it in the delay queue,
// just forward it immediately
if let Some(instant) = new_packet.1 {
// check if the delay has already expired, if so, don't bother putting it through
// the delay queue only to retrieve it immediately. Just forward it.
if instant.checked_duration_since(Instant::now()).is_none() {
self.forward_packet(new_packet.0)
} else {
self.delay_queue.insert_at(new_packet.0, instant);
}
} else {
self.forward_packet(new_packet.0)
}
}
pub(crate) async fn run(&mut self) {
trace!("Starting DelayForwarder");
loop {
tokio::select! {
delayed = self.delay_queue.next() => {
self.handle_done_delaying(delayed.unwrap());
}
new_packet = self.packet_receiver.next() => {
// this one is impossible to ever panic - the object itself contains a sender
// and hence it can't happen that ALL senders are dropped
self.handle_new_packet(new_packet.unwrap())
}
_ = self.shutdown.recv() => {
trace!("DelayForwarder: Received shutdown");
break;
}
}
}
trace!("DelayForwarder: Exiting");
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use nym_sphinx::NymPacket;
use nym_task::TaskManager;
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::PacketType;
use nym_sphinx_types::{
crypto, Delay as SphinxDelay, Destination, DestinationAddressBytes, Node, NodeAddressBytes,
DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, NODE_ADDRESS_LENGTH,
};
#[derive(Default)]
struct TestClient {
pub packets_sent: Arc<Mutex<Vec<(NymNodeRoutingAddress, NymPacket, PacketType)>>>,
}
impl nym_mixnet_client::SendWithoutResponse for TestClient {
fn send_without_response(
&mut self,
address: NymNodeRoutingAddress,
packet: NymPacket,
packet_type: PacketType,
) -> io::Result<()> {
self.packets_sent
.lock()
.unwrap()
.push((address, packet, packet_type));
Ok(())
}
}
fn make_valid_sphinx_packet(size: PacketSize) -> NymPacket {
let (_, node1_pk) = crypto::keygen();
let node1 = Node::new(
NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]),
node1_pk,
);
let (_, node2_pk) = crypto::keygen();
let node2 = Node::new(
NodeAddressBytes::from_bytes([4u8; NODE_ADDRESS_LENGTH]),
node2_pk,
);
let (_, node3_pk) = crypto::keygen();
let node3 = Node::new(
NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
node3_pk,
);
let route = [node1, node2, node3];
let destination = Destination::new(
DestinationAddressBytes::from_bytes([3u8; DESTINATION_ADDRESS_LENGTH]),
[4u8; IDENTIFIER_LENGTH],
);
let delays = vec![
SphinxDelay::new_from_nanos(42),
SphinxDelay::new_from_nanos(42),
SphinxDelay::new_from_nanos(42),
];
NymPacket::sphinx_build(size.payload_size(), b"foomp", &route, &destination, &delays)
.unwrap()
}
fn make_valid_outfox_packet(size: PacketSize) -> NymPacket {
let (_, node1_pk) = crypto::keygen();
let node1 = Node::new(
NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]),
node1_pk,
);
let (_, node2_pk) = crypto::keygen();
let node2 = Node::new(
NodeAddressBytes::from_bytes([4u8; NODE_ADDRESS_LENGTH]),
node2_pk,
);
let (_, node3_pk) = crypto::keygen();
let node3 = Node::new(
NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
node3_pk,
);
let (_, node4_pk) = crypto::keygen();
let node4 = Node::new(
NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
node4_pk,
);
let destination = Destination::new(
DestinationAddressBytes::from_bytes([3u8; DESTINATION_ADDRESS_LENGTH]),
[4u8; IDENTIFIER_LENGTH],
);
let route = &[node1, node2, node3, node4];
let payload = vec![1; 48];
NymPacket::outfox_build(payload, route, &destination, Some(size.plaintext_size())).unwrap()
}
#[tokio::test]
async fn packets_received_are_forwarded() {
// Wire up the DelayForwarder
let (stats_sender, _stats_receiver) = mpsc::unbounded();
let node_stats_update_sender = UpdateSender::new(stats_sender);
let client = TestClient::default();
let client_packets_sent = client.packets_sent.clone();
let shutdown = TaskManager::default();
let mut delay_forwarder =
DelayForwarder::new(client, node_stats_update_sender, shutdown.subscribe());
let packet_sender = delay_forwarder.sender();
// Spawn the worker, listening on packet_sender channel
tokio::spawn(async move { delay_forwarder.run().await });
// Send a `MixPacket` down the channel without any delay attached.
let next_hop =
NymNodeRoutingAddress::from(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), 42));
let mix_packet = MixPacket::new(
next_hop,
make_valid_sphinx_packet(PacketSize::default()),
PacketType::default(),
);
let forward_instant = None;
packet_sender
.unbounded_send((mix_packet, forward_instant))
.unwrap();
// Give the the worker a chance to act
tokio::time::sleep(Duration::from_millis(10)).await;
// The client should have forwarded the packet straight away
assert_eq!(
client_packets_sent
.lock()
.unwrap()
.iter()
.map(|(a, _, _)| *a)
.collect::<Vec<_>>(),
vec![next_hop]
);
}
#[tokio::test]
async fn outfox_packets_received_are_forwarded() {
// Wire up the DelayForwarder
let (stats_sender, _stats_receiver) = mpsc::unbounded();
let node_stats_update_sender = UpdateSender::new(stats_sender);
let client = TestClient::default();
let client_packets_sent = client.packets_sent.clone();
let shutdown = TaskManager::default();
let mut delay_forwarder =
DelayForwarder::new(client, node_stats_update_sender, shutdown.subscribe());
let packet_sender = delay_forwarder.sender();
// Spawn the worker, listening on packet_sender channel
tokio::spawn(async move { delay_forwarder.run().await });
// Send a `MixPacket` down the channel without any delay attached.
let next_hop =
NymNodeRoutingAddress::from(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), 42));
let mix_packet = MixPacket::new(
next_hop,
make_valid_outfox_packet(PacketSize::default()),
PacketType::default(),
);
let forward_instant = None;
packet_sender
.unbounded_send((mix_packet, forward_instant))
.unwrap();
// Give the the worker a chance to act
tokio::time::sleep(Duration::from_millis(10)).await;
// The client should have forwarded the packet straight away
assert_eq!(
client_packets_sent
.lock()
.unwrap()
.iter()
.map(|(a, _, _)| *a)
.collect::<Vec<_>>(),
vec![next_hop]
);
}
}
+3
View File
@@ -68,6 +68,9 @@ pub(crate) struct Args {
/// default: `127.0.0.1:8080` in `debug` builds and `0.0.0.0:8080` in `release`
#[clap(long)]
pub(crate) bind_address: Option<SocketAddr>,
#[clap(hide = true, long, default_value_t = false)]
pub(crate) allow_illegal_ips: bool,
// #[clap(short, long, default_value_t = OutputFormat::default())]
// output: OutputFormat,
}
+3
View File
@@ -100,6 +100,9 @@ pub(crate) struct Args {
/// default: `127.0.0.1:8080` in `debug` builds and `0.0.0.0:8080` in `release`
#[clap(long)]
pub(crate) bind_address: Option<SocketAddr>,
#[clap(hide = true, long, default_value_t = false)]
pub(crate) allow_illegal_ips: bool,
}
async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHandles> {
+3
View File
@@ -175,6 +175,9 @@ impl Config {
if let Some(http_bind_address) = args.bind_address {
self.base.bind_address = http_bind_address
}
if args.allow_illegal_ips {
self.topology_cacher.debug.node_describe_allow_illegal_ips = true
}
self
}
+4
View File
@@ -31,6 +31,8 @@ pub(crate) struct OverrideConfig {
/// Socket address this api will use for binding its http API.
/// default: `127.0.0.1:8080` in `debug` builds and `0.0.0.0:8080` in `release`
pub(crate) bind_address: Option<SocketAddr>,
pub(crate) allow_illegal_ips: bool,
}
impl From<init::Args> for OverrideConfig {
@@ -44,6 +46,7 @@ impl From<init::Args> for OverrideConfig {
announce_address: args.announce_address,
monitor_credentials_mode: Some(args.monitor_credentials_mode),
bind_address: args.bind_address,
allow_illegal_ips: args.allow_illegal_ips,
}
}
}
@@ -59,6 +62,7 @@ impl From<run::Args> for OverrideConfig {
announce_address: args.announce_address,
monitor_credentials_mode: args.monitor_credentials_mode,
bind_address: args.bind_address,
allow_illegal_ips: args.allow_illegal_ips,
}
}
}
@@ -21,13 +21,13 @@ cosmwasm-std = { workspace = true }
envy = { workspace = true }
futures-util = { workspace = true }
moka = { workspace = true, features = ["future"] }
nym-bin-common = { path = "../../common/bin-common", features = ["models"]}
nym-bin-common = { path = "../../common/bin-common", features = ["models"] }
nym-node-status-client = { path = "../nym-node-status-client" }
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] }
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
nym-network-defaults = { path = "../../common/network-defaults" }
nym-serde-helpers = { path = "../../common/serde-helpers"}
nym-statistics-common = { path = "../../common/statistics"}
nym-statistics-common = { path = "../../common/statistics" }
nym-validator-client = { path = "../../common/client-libs/validator-client" }
nym-task = { path = "../../common/task" }
nym-node-requests = { path = "../../nym-node/nym-node-requests", features = ["openapi"] }
@@ -55,12 +55,15 @@ utoipa-swagger-ui = { workspace = true, features = ["axum"] }
# utoipauto = { git = "https://github.com/ProbablyClem/utoipauto", rev = "eb04cba" }
utoipauto = { workspace = true }
nym-node-metrics = { path = "../../nym-node/nym-node-metrics" }
[build-dependencies]
anyhow = { workspace = true }
tokio = { workspace = true, features = ["macros"] }
sqlx = { workspace = true, features = [
"runtime-tokio-rustls",
"sqlite",
"macros",
"migrate",
"runtime-tokio-rustls",
"sqlite",
"macros",
"migrate",
] }
@@ -1,9 +1,7 @@
use crate::db::{models::GatewaySessionsRecord, queries, DbPool};
use error::NodeScraperError;
use nym_network_defaults::{NymNetworkDetails, DEFAULT_NYM_NODE_HTTP_PORT};
use nym_node_requests::api::{client::NymNodeApiClientExt, v1::metrics::models::SessionStats};
use nym_statistics_common::gateways::SessionType;
use nym_validator_client::{
client::{NodeId, NymNodeDetails},
models::{DescribedNodeType, NymNodeDescription},
@@ -11,6 +9,7 @@ use nym_validator_client::{
};
use time::OffsetDateTime;
use nym_node_metrics::entry::SessionType;
use std::collections::HashMap;
use tokio::time::Duration;
use tracing::instrument;
+33 -3
View File
@@ -14,21 +14,27 @@ license = "GPL-3.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-trait = { workspace = true }
anyhow.workspace = true
bip39 = { workspace = true, features = ["zeroize"] }
bs58.workspace = true
celes = { workspace = true } # country codes
colored = { workspace = true }
clap = { workspace = true, features = ["cargo", "env"] }
dashmap = { workspace = true }
futures = { workspace = true }
humantime-serde = { workspace = true }
human-repr = { workspace = true }
ipnetwork = { workspace = true }
rand = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
si-scale = { workspace = true }
thiserror.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
tokio = { workspace = true, features = ["macros", "sync"] }
tokio = { workspace = true, features = ["macros", "sync", "rt-multi-thread"] }
tokio-util = { workspace = true, features = ["codec"] }
toml = { workspace = true }
url = { workspace = true, features = ["serde"] }
zeroize = { workspace = true, features = ["zeroize_derive"] }
@@ -47,23 +53,47 @@ nym-bin-common = { path = "../common/bin-common", features = [
nym-client-core-config-types = { path = "../common/client-core/config-types" }
nym-config = { path = "../common/config" }
nym-crypto = { path = "../common/crypto", features = ["asymmetric", "rand"] }
nym-node-http-api = { path = "nym-node-http-api" }
nym-nonexhaustive-delayqueue = { path = "../common/nonexhaustive-delayqueue" }
nym-mixnet-client = { path = "../common/client-libs/mixnet-client" }
nym-pemstore = { path = "../common/pemstore" }
nym-sphinx-acknowledgements = { path = "../common/nymsphinx/acknowledgements" }
nym-sphinx-addressing = { path = "../common/nymsphinx/addressing" }
nym-sphinx-framing = { path = "../common/nymsphinx/framing" }
nym-sphinx-types = { path = "../common/nymsphinx/types" }
nym-sphinx-forwarding = { path = "../common/nymsphinx/forwarding" }
nym-task = { path = "../common/task" }
nym-types = { path = "../common/types" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
nym-wireguard = { path = "../common/wireguard" }
nym-wireguard-types = { path = "../common/wireguard-types", default-features = false }
nym-verloc = { path = "../common/verloc" }
nym-metrics = { path = "../common/nym-metrics" }
nym-gateway-stats-storage = { path = "../common/gateway-stats-storage" }
nym-topology = { path = "../common/topology" }
# http server
# useful for `#[axum_macros::debug_handler]`
#axum-macros = "0.3.8"
axum.workspace = true
axum-extra = { workspace = true, features = ["typed-header"] }
headers.workspace = true
time = { workspace = true, features = ["serde"] }
tower-http = { workspace = true, features = ["fs"] }
utoipa = { workspace = true, features = ["axum_extras", "time"] }
utoipa-swagger-ui = { workspace = true, features = ["axum"] }
nym-http-api-common = { path = "../common/http-api-common", features = ["utoipa"] }
nym-node-requests = { path = "nym-node-requests", default-features = false, features = ["openapi"] }
nym-node-metrics = { path = "nym-node-metrics" }
# nodes:
nym-mixnode = { path = "../mixnode" }
nym-gateway = { path = "../gateway" }
nym-authenticator = { path = "../service-providers/authenticator" }
nym-network-requester = { path = "../service-providers/network-requester" }
nym-ip-packet-router = { path = "../service-providers/ip-packet-router" }
[build-dependencies]
# temporary bonding information v1 (to grab and parse nym-mixnode and nym-gateway package versions)
cargo_metadata = { workspace = true }
-47
View File
@@ -1,47 +0,0 @@
[package]
name = "nym-node-http-api"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
axum.workspace = true
axum-extra = { workspace = true, features = ["typed-header"] }
headers.workspace = true
# useful for `#[axum_macros::debug_handler]`
#axum-macros = "0.3.8"
thiserror.workspace = true
time = { workspace = true, features = ["serde"] }
tokio = { workspace = true, features = ["macros"] }
tower-http = { workspace = true, features = ["fs"] }
tracing.workspace = true
utoipa = { workspace = true, features = ["axum_extras", "time"] }
utoipa-swagger-ui = { workspace = true, features = ["axum"] }
colored = { workspace = true }
ipnetwork = { workspace = true }
rand = { workspace = true }
# Wireguard:
fastrand = { workspace = true }
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
nym-http-api-common = { path = "../../common/http-api-common", features = ["utoipa"] }
nym-node-requests = { path = "../nym-node-requests", default-features = false, features = ["openapi"] }
nym-task = { path = "../../common/task" }
nym-metrics = { path = "../../common/nym-metrics" }
nym-wireguard = { path = "../../common/wireguard" }
[dev-dependencies]
base64 = { workspace = true }
hyper.workspace = true
dashmap.workspace = true
serde_json.workspace = true
hmac = { workspace = true }
tower = { workspace = true }
x25519-dalek = { workspace = true }
-67
View File
@@ -1,67 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use axum::extract::connect_info::IntoMakeServiceWithConnectInfo;
use axum::extract::ConnectInfo;
use axum::middleware::AddExtension;
use axum::serve::Serve;
use axum::Router;
use nym_task::TaskClient;
use std::net::SocketAddr;
use tracing::{debug, error};
pub mod error;
pub mod middleware;
pub mod router;
pub mod state;
pub use error::NymNodeHttpError;
pub use router::{api, landing_page, Config, NymNodeRouter};
// I guess this wasn't really meant to be extracted into separate type haha
type InnerService = IntoMakeServiceWithConnectInfo<Router, SocketAddr>;
type ConnectInfoExt = AddExtension<Router, ConnectInfo<SocketAddr>>;
pub type ServeService = Serve<InnerService, ConnectInfoExt>;
pub struct NymNodeHTTPServer {
task_client: Option<TaskClient>,
inner: ServeService,
}
impl NymNodeHTTPServer {
pub(crate) fn new(inner: ServeService) -> Self {
NymNodeHTTPServer {
task_client: None,
inner,
}
}
#[must_use]
pub fn with_task_client(mut self, task_client: TaskClient) -> Self {
self.task_client = Some(task_client);
self
}
async fn run_server_forever(server: ServeService) {
if let Err(err) = server.await {
error!("the HTTP server has terminated with the error: {err}");
} else {
error!("the HTTP server has terminated with producing any errors");
}
}
pub async fn run(self) {
if let Some(mut task_client) = self.task_client {
tokio::select! {
_ = task_client.recv_with_delay() => {
debug!("NymNodeHTTPServer: Received shutdown");
}
_ = Self::run_server_forever(self.inner) => { }
}
} else {
Self::run_server_forever(self.inner).await
}
debug!("NymNodeHTTPServer: Exiting");
}
}
@@ -1,51 +0,0 @@
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use axum::{
extract::{ConnectInfo, Request},
http::{
header::{HOST, USER_AGENT},
HeaderValue,
},
middleware::Next,
response::IntoResponse,
};
use colored::*;
use std::net::SocketAddr;
use tracing::info;
/// Simple logger for requests
pub async fn logger(
ConnectInfo(addr): ConnectInfo<SocketAddr>,
req: Request,
next: Next,
) -> impl IntoResponse {
let method = req.method().to_string().green();
let uri = req.uri().to_string().blue();
let agent = header_map(
req.headers().get(USER_AGENT),
"Unknown User Agent".to_string(),
);
let host = header_map(req.headers().get(HOST), "Unknown Host".to_string());
let res = next.run(req).await;
let status = res.status();
let print_status = if status.is_client_error() || status.is_server_error() {
status.to_string().red()
} else if status.is_success() {
status.to_string().green()
} else {
status.to_string().yellow()
};
info!(target: "incoming request", "[{addr} -> {host}] {method} '{uri}': {print_status} / agent: {agent}");
res
}
fn header_map(header: Option<&HeaderValue>, msg: String) -> String {
header
.map(|x| x.to_str().unwrap_or(&msg).to_string())
.unwrap_or(msg)
}
-64
View File
@@ -1,64 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use axum::extract::connect_info::IntoMakeServiceWithConnectInfo;
use axum::Router;
use hyper::server::conn::AddrIncoming;
use hyper::Server;
use nym_task::TaskClient;
use std::net::SocketAddr;
use tracing::{debug, error, info};
pub mod middleware;
pub mod router;
pub mod state;
pub use router::{api, landing_page, Config, NymNodeRouter};
pub struct NymNodeHTTPServer {
task_client: Option<TaskClient>,
inner: Server<AddrIncoming, IntoMakeServiceWithConnectInfo<Router, SocketAddr>>,
}
impl NymNodeHTTPServer {
pub(crate) fn new(
inner: Server<AddrIncoming, IntoMakeServiceWithConnectInfo<Router, SocketAddr>>,
) -> Self {
NymNodeHTTPServer {
task_client: None,
inner,
}
}
#[must_use]
pub fn with_task_client(mut self, task_client: TaskClient) -> Self {
self.task_client = Some(task_client);
self
}
async fn run_server_forever(
server: Server<AddrIncoming, IntoMakeServiceWithConnectInfo<Router, SocketAddr>>,
) {
if let Err(err) = server.await {
error!("the HTTP server has terminated with the error: {err}");
} else {
error!("the HTTP server has terminated with producing any errors");
}
}
pub async fn run(self) {
info!("Started NymNodeHTTPServer on {}", self.inner.local_addr());
if let Some(mut task_client) = self.task_client {
tokio::select! {
_ = task_client.recv_with_delay() => {
debug!("NymNodeHTTPServer: Received shutdown");
}
_ = Self::run_server_forever(self.inner) => { }
}
} else {
Self::run_server_forever(self.inner).await
}
debug!("NymNodeHTTPServer: Exiting");
}
}
@@ -1,33 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::state::metrics::MetricsAppState;
use axum::extract::{Query, State};
use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_node_requests::api::v1::metrics::models::MixingStats;
/// If applicable, returns mixing statistics information of this node.
/// This information is **PURELY** self-reported and in no way validated.
#[utoipa::path(
get,
path = "/mixing",
context_path = "/api/v1/metrics",
tag = "Metrics",
responses(
(status = 200, content(
("application/json" = MixingStats),
("application/yaml" = MixingStats)
))
),
params(OutputParams),
)]
pub(crate) async fn mixing_stats(
Query(output): Query<OutputParams>,
State(metrics_state): State<MetricsAppState>,
) -> MixingStatsResponse {
let output = output.output.unwrap_or_default();
let response = metrics_state.mixing_stats.read().await.as_response();
output.to_response(response)
}
pub type MixingStatsResponse = FormattedResponse<MixingStats>;
@@ -1,33 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::state::metrics::MetricsAppState;
use axum::extract::{Query, State};
use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_node_requests::api::v1::metrics::models::SessionStats;
/// If applicable, returns sessions statistics information of this node.
/// This information is **PURELY** self-reported and in no way validated.
#[utoipa::path(
get,
path = "/sessions",
context_path = "/api/v1/metrics",
tag = "Metrics",
responses(
(status = 200, content(
("application/json" = SessionStats),
("application/yaml" = SessionStats)
))
),
params(OutputParams),
)]
pub(crate) async fn sessions_stats(
Query(output): Query<OutputParams>,
State(metrics_state): State<MetricsAppState>,
) -> SessionStatsResponse {
let output = output.output.unwrap_or_default();
let response = metrics_state.session_stats.read().await.as_response();
output.to_response(response)
}
pub type SessionStatsResponse = FormattedResponse<SessionStats>;
@@ -1,32 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::state::metrics::MetricsAppState;
use axum::extract::{Query, State};
use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_node_requests::api::v1::metrics::models::VerlocStats;
/// If applicable, returns verloc statistics information of this node.
#[utoipa::path(
get,
path = "/verloc",
context_path = "/api/v1/metrics",
tag = "Metrics",
responses(
(status = 200, content(
("application/json" = VerlocStats),
("application/yaml" = VerlocStats)
))
),
params(OutputParams),
)]
pub(crate) async fn verloc_stats(
Query(output): Query<OutputParams>,
State(metrics_state): State<MetricsAppState>,
) -> VerlocStatsResponse {
let output = output.output.unwrap_or_default();
let response = metrics_state.verloc.read().await.as_response();
output.to_response(response)
}
pub type VerlocStatsResponse = FormattedResponse<VerlocStats>;
@@ -1,214 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::state::AppState;
use axum::extract::FromRef;
use nym_node_requests::api::v1::metrics::models::{
MixingStats, Session, SessionStats, VerlocResult, VerlocResultData, VerlocStats,
};
use std::collections::HashMap;
use std::sync::Arc;
use time::macros::time;
use time::{Date, OffsetDateTime};
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
pub use nym_node_requests::api::v1::metrics::models::{VerlocMeasurement, VerlocNodeResult};
type PacketsMap = HashMap<String, u64>;
#[derive(Clone, Debug, Default)]
pub struct SharedMixingStats {
inner: Arc<RwLock<MixingStatsState>>,
}
impl SharedMixingStats {
pub fn new() -> SharedMixingStats {
let now = OffsetDateTime::now_utc();
SharedMixingStats {
inner: Arc::new(RwLock::new(MixingStatsState {
update_time: now,
previous_update_time: now,
..Default::default()
})),
}
}
pub async fn read(&self) -> RwLockReadGuard<'_, MixingStatsState> {
self.inner.read().await
}
pub async fn write(&self) -> RwLockWriteGuard<'_, MixingStatsState> {
self.inner.write().await
}
}
#[derive(Debug)]
pub struct MixingStatsState {
pub update_time: OffsetDateTime,
pub previous_update_time: OffsetDateTime,
pub packets_received_since_startup: u64,
pub packets_sent_since_startup_all: u64,
pub packets_dropped_since_startup_all: u64,
pub packets_received_since_last_update: u64,
// note: sent does not imply forwarded. We don't know if it was delivered successfully
pub packets_sent_since_last_update: PacketsMap,
// we know for sure we dropped packets to those destinations
pub packets_explicitly_dropped_since_last_update: PacketsMap,
}
impl MixingStatsState {
pub fn as_response(&self) -> MixingStats {
MixingStats {
update_time: self.update_time,
previous_update_time: self.previous_update_time,
received_since_startup: self.packets_received_since_startup,
sent_since_startup: self.packets_sent_since_startup_all,
dropped_since_startup: self.packets_dropped_since_startup_all,
received_since_last_update: self.packets_received_since_last_update,
sent_since_last_update: self.packets_sent_since_last_update.values().sum(),
dropped_since_last_update: self
.packets_explicitly_dropped_since_last_update
.values()
.sum(),
}
}
}
impl Default for MixingStatsState {
fn default() -> Self {
MixingStatsState {
update_time: OffsetDateTime::UNIX_EPOCH,
previous_update_time: OffsetDateTime::UNIX_EPOCH,
packets_received_since_startup: 0,
packets_sent_since_startup_all: 0,
packets_dropped_since_startup_all: 0,
packets_received_since_last_update: 0,
packets_sent_since_last_update: Default::default(),
packets_explicitly_dropped_since_last_update: Default::default(),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct SharedVerlocStats {
inner: Arc<RwLock<VerlocStatsState>>,
}
#[derive(Clone, Debug, Default)]
pub struct VerlocStatsState {
pub current_run_data: VerlocResultData,
pub previous_run_data: VerlocResultData,
}
impl SharedVerlocStats {
pub async fn read(&self) -> RwLockReadGuard<'_, VerlocStatsState> {
self.inner.read().await
}
pub async fn write(&self) -> RwLockWriteGuard<'_, VerlocStatsState> {
self.inner.write().await
}
}
impl VerlocStatsState {
pub fn as_response(&self) -> VerlocStats {
let previous = if !self.previous_run_data.run_finished() {
VerlocResult::Unavailable
} else {
VerlocResult::Data(self.previous_run_data.clone())
};
let current = if !self.current_run_data.run_finished() {
VerlocResult::MeasurementInProgress
} else {
VerlocResult::Data(self.current_run_data.clone())
};
VerlocStats { previous, current }
}
}
#[derive(Clone, Debug, Default)]
pub struct SharedSessionStats {
inner: Arc<RwLock<SessionStatsState>>,
}
impl SharedSessionStats {
pub fn new() -> SharedSessionStats {
SharedSessionStats {
inner: Arc::new(RwLock::new(Default::default())),
}
}
pub async fn read(&self) -> RwLockReadGuard<'_, SessionStatsState> {
self.inner.read().await
}
pub async fn write(&self) -> RwLockWriteGuard<'_, SessionStatsState> {
self.inner.write().await
}
}
type FinishedSessions = Vec<(u64, String)>;
#[derive(Debug, Clone)]
pub struct SessionStatsState {
pub update_time: Date,
pub unique_active_users_count: u32,
pub unique_active_users_hashes: Vec<String>,
pub session_started: u32,
pub sessions: FinishedSessions,
}
impl SessionStatsState {
pub fn as_response(&self) -> SessionStats {
let sessions = self
.sessions
.clone()
.into_iter()
.map(|(duration_ms, typ)| Session { duration_ms, typ })
.collect();
SessionStats {
update_time: self.update_time.with_time(time!(0:00)).assume_utc(),
unique_active_users: self.unique_active_users_count,
unique_active_users_hashes: self.unique_active_users_hashes.clone(),
sessions,
sessions_started: self.session_started,
sessions_finished: self.sessions.len() as u32,
}
}
}
impl Default for SessionStatsState {
fn default() -> Self {
SessionStatsState {
update_time: OffsetDateTime::UNIX_EPOCH.date(),
unique_active_users_count: 0,
unique_active_users_hashes: Default::default(),
session_started: 0,
sessions: Default::default(),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct MetricsAppState {
pub(crate) prometheus_access_token: Option<String>,
pub(crate) mixing_stats: SharedMixingStats,
pub(crate) session_stats: SharedSessionStats,
pub(crate) verloc: SharedVerlocStats,
}
impl FromRef<AppState> for MetricsAppState {
fn from_ref(app_state: &AppState) -> Self {
app_state.metrics.clone()
}
}
@@ -1,54 +0,0 @@
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::state::metrics::{
MetricsAppState, SharedMixingStats, SharedSessionStats, SharedVerlocStats,
};
use tokio::time::Instant;
pub mod metrics;
#[derive(Debug, Clone)]
pub struct AppState {
pub(crate) startup_time: Instant,
pub(crate) metrics: MetricsAppState,
}
impl AppState {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
AppState {
// is it 100% accurate?
// no.
// does it have to be?
// also no.
startup_time: Instant::now(),
metrics: Default::default(),
}
}
#[must_use]
pub fn with_mixing_stats(mut self, mixing_stats: SharedMixingStats) -> Self {
self.metrics.mixing_stats = mixing_stats;
self
}
#[must_use]
pub fn with_sessions_stats(mut self, session_stats: SharedSessionStats) -> Self {
self.metrics.session_stats = session_stats;
self
}
#[must_use]
pub fn with_verloc_stats(mut self, verloc_stats: SharedVerlocStats) -> Self {
self.metrics.verloc = verloc_stats;
self
}
#[must_use]
pub fn with_metrics_key(mut self, bearer_token: impl Into<Option<String>>) -> Self {
self.metrics.prometheus_access_token = bearer_token.into();
self
}
}
+20
View File
@@ -0,0 +1,20 @@
[package]
name = "nym-node-metrics"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
dashmap = { workspace = true }
futures = { workspace = true }
tokio = { workspace = true, features = ["sync"] }
time = { workspace = true }
strum = { workspace = true }
tracing = { workspace = true }
nym-metrics = { path = "../../common/nym-metrics" }
nym-statistics-common = { path = "../../common/statistics" }
+112
View File
@@ -0,0 +1,112 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_statistics_common::hash_identifier;
use std::time::Duration;
use time::{Date, OffsetDateTime};
use tokio::sync::{RwLock, RwLockReadGuard};
#[derive(Default)]
pub struct EntryStats {
sessions: RwLock<ClientSessions>,
}
impl EntryStats {
pub async fn update_client_sessions(&self, new: ClientSessions) {
*self.sessions.write().await = new
}
pub async fn client_sessions(&self) -> RwLockReadGuard<ClientSessions> {
self.sessions.read().await
}
}
pub struct ClientSessions {
pub update_time: Date,
pub unique_users: Vec<String>,
pub sessions_started: u32,
pub finished_sessions: Vec<FinishedSession>,
}
impl Default for ClientSessions {
fn default() -> Self {
ClientSessions {
update_time: OffsetDateTime::UNIX_EPOCH.date(),
unique_users: vec![],
sessions_started: 0,
finished_sessions: vec![],
}
}
}
impl ClientSessions {
pub fn new(
update_time: Date,
unique_users: Vec<String>,
sessions_started: u32,
sessions: Vec<FinishedSession>,
) -> Self {
ClientSessions {
update_time,
unique_users: unique_users.into_iter().map(hash_identifier).collect(),
sessions_started,
finished_sessions: sessions,
}
}
}
pub struct FinishedSession {
pub duration: Duration,
pub typ: SessionType,
}
impl FinishedSession {
pub fn new(duration: Duration, typ: SessionType) -> Self {
FinishedSession { duration, typ }
}
}
#[derive(PartialEq, Copy, Clone, strum::Display, strum::EnumString)]
pub enum SessionType {
Vpn,
Mixnet,
Unknown,
}
impl SessionType {
pub fn from_string<S: AsRef<str>>(s: S) -> Self {
s.as_ref().parse().unwrap_or(Self::Unknown)
}
}
pub struct ActiveSession {
pub start: OffsetDateTime,
pub typ: SessionType,
}
impl ActiveSession {
pub fn new(start_time: OffsetDateTime) -> Self {
ActiveSession {
start: start_time,
typ: SessionType::Unknown,
}
}
pub fn set_type(&mut self, typ: SessionType) {
self.typ = typ;
}
pub fn end_at(self, stop_time: OffsetDateTime) -> Option<FinishedSession> {
let session_duration = stop_time - self.start;
//ensure duration is positive to fit in a u64
//u64::max milliseconds is 500k millenia so no overflow issue
if session_duration > Duration::ZERO {
Some(FinishedSession {
duration: session_duration.unsigned_abs(),
typ: self.typ,
})
} else {
None
}
}
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use futures::channel::mpsc;
use futures::channel::mpsc::SendError;
pub use nym_statistics_common::gateways::GatewaySessionEvent;
use tracing::error;
pub fn events_channels() -> (MetricEventsSender, MetricEventsReceiver) {
let (tx, rx) = mpsc::unbounded();
(tx.into(), rx)
}
#[derive(Clone)]
pub struct MetricEventsSender(mpsc::UnboundedSender<MetricsEvent>);
impl From<mpsc::UnboundedSender<MetricsEvent>> for MetricEventsSender {
fn from(tx: mpsc::UnboundedSender<MetricsEvent>) -> Self {
MetricEventsSender(tx)
}
}
impl MetricEventsSender {
pub fn report(&self, metric: impl Into<MetricsEvent>) -> Result<(), SendError> {
self.0
.unbounded_send(metric.into())
.map_err(|err| err.into_send_error())
}
pub fn report_unchecked(&self, metric: impl Into<MetricsEvent>) {
if let Err(err) = self.report(metric) {
error!("failed to send metric information: {err}")
}
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.0.len()
}
}
pub type MetricEventsReceiver = mpsc::UnboundedReceiver<MetricsEvent>;
// please create a new variant per "category" of metrics
pub enum MetricsEvent {
GatewayClientSession(GatewaySessionEvent),
}
impl From<GatewaySessionEvent> for MetricsEvent {
fn from(gateway_stats: GatewaySessionEvent) -> Self {
MetricsEvent::GatewayClientSession(gateway_stats)
}
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::entry::EntryStats;
use crate::mixnet::MixingStats;
use crate::network::NetworkStats;
use std::ops::Deref;
use std::sync::Arc;
pub mod entry;
pub mod events;
pub mod mixnet;
pub mod network;
#[derive(Clone, Default)]
pub struct NymNodeMetrics {
inner: Arc<NymNodeMetricsInner>,
}
impl NymNodeMetrics {
pub fn new() -> Self {
NymNodeMetrics::default()
}
}
impl Deref for NymNodeMetrics {
type Target = NymNodeMetricsInner;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[derive(Default)]
pub struct NymNodeMetricsInner {
pub mixnet: MixingStats,
pub entry: EntryStats,
pub network: NetworkStats,
}
+292
View File
@@ -0,0 +1,292 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use dashmap::DashMap;
use std::net::{IpAddr, SocketAddr};
use std::sync::atomic::{AtomicI64, AtomicUsize, Ordering};
use time::OffsetDateTime;
#[derive(Default)]
pub struct MixingStats {
// updated on each packet
pub ingress: IngressMixingStats,
// updated on each packet
pub egress: EgressMixingStats,
// updated on a timer
pub legacy: LegacyMixingStats,
}
impl MixingStats {
pub fn update_legacy_stats(
&self,
received_since_last_update: usize,
sent_since_last_update: usize,
dropped_since_last_update: usize,
update_timestamp: i64,
) {
self.legacy
.received_since_last_update
.store(received_since_last_update, Ordering::Relaxed);
self.legacy
.sent_since_last_update
.store(sent_since_last_update, Ordering::Relaxed);
self.legacy
.dropped_since_last_update
.store(dropped_since_last_update, Ordering::Relaxed);
let old_last = self.legacy.last_update_ts.load(Ordering::Acquire);
self.legacy
.previous_update_ts
.store(old_last, Ordering::Release);
self.legacy
.last_update_ts
.store(update_timestamp, Ordering::Release);
}
pub fn ingress_malformed_packet(&self, source: IpAddr) {
self.ingress
.malformed_packets_received
.fetch_add(1, Ordering::Relaxed);
self.ingress.senders.entry(source).or_default().malformed += 1;
}
pub fn ingress_received_forward_packet(&self, source: IpAddr) {
self.ingress
.forward_hop_packets_received
.fetch_add(1, Ordering::Relaxed);
self.ingress
.senders
.entry(source)
.or_default()
.forward_packets
.received += 1;
}
pub fn ingress_received_final_hop_packet(&self, source: IpAddr) {
self.ingress
.final_hop_packets_received
.fetch_add(1, Ordering::Relaxed);
self.ingress
.senders
.entry(source)
.or_default()
.final_hop_packets
.received += 1;
}
pub fn ingress_excessive_delay_packet(&self) {
self.ingress
.excessive_delay_packets
.fetch_add(1, Ordering::Relaxed);
}
pub fn ingress_dropped_forward_packet(&self, source: IpAddr) {
self.ingress
.forward_hop_packets_dropped
.fetch_add(1, Ordering::Relaxed);
self.ingress
.senders
.entry(source)
.or_default()
.forward_packets
.dropped += 1;
}
pub fn ingress_dropped_final_hop_packet(&self, source: IpAddr) {
self.ingress
.final_hop_packets_dropped
.fetch_add(1, Ordering::Relaxed);
self.ingress
.senders
.entry(source)
.or_default()
.final_hop_packets
.dropped += 1;
}
pub fn egress_sent_forward_packet(&self, target: SocketAddr) {
self.egress
.forward_hop_packets_sent
.fetch_add(1, Ordering::Relaxed);
self.egress
.forward_recipients
.entry(target)
.or_default()
.sent += 1;
}
pub fn egress_sent_ack(&self) {
self.egress.ack_packets_sent.fetch_add(1, Ordering::Relaxed);
}
pub fn egress_dropped_forward_packet(&self, target: SocketAddr) {
self.egress
.forward_hop_packets_dropped
.fetch_add(1, Ordering::Relaxed);
self.egress
.forward_recipients
.entry(target)
.or_default()
.dropped += 1;
}
pub fn egress_dropped_final_hop_packet(&self) {
todo!()
// self.egress
// .final_hop_packets_dropped
// .fetch_add(1, Ordering::Relaxed);
}
}
#[derive(Clone, Copy, Default, PartialEq)]
pub struct EgressRecipientStats {
pub dropped: usize,
pub sent: usize,
}
#[derive(Default)]
pub struct EgressMixingStats {
// this includes ACKS!
forward_hop_packets_sent: AtomicUsize,
ack_packets_sent: AtomicUsize,
forward_hop_packets_dropped: AtomicUsize,
forward_recipients: DashMap<SocketAddr, EgressRecipientStats>,
}
impl EgressMixingStats {
pub fn forward_hop_packets_sent(&self) -> usize {
self.forward_hop_packets_sent.load(Ordering::Relaxed)
}
pub fn ack_packets_sent(&self) -> usize {
self.ack_packets_sent.load(Ordering::Relaxed)
}
pub fn forward_hop_packets_dropped(&self) -> usize {
self.forward_hop_packets_dropped.load(Ordering::Relaxed)
}
pub fn forward_recipients(&self) -> &DashMap<SocketAddr, EgressRecipientStats> {
&self.forward_recipients
}
pub fn remove_stale_forward_recipient(&self, recipient: SocketAddr) {
self.forward_recipients.remove(&recipient);
}
}
#[derive(Clone, Copy, Default, PartialEq)]
pub struct IngressPacketsStats {
pub dropped: usize,
pub received: usize,
}
#[derive(Clone, Copy, Default, PartialEq)]
pub struct IngressRecipientStats {
pub forward_packets: IngressPacketsStats,
pub final_hop_packets: IngressPacketsStats,
pub malformed: usize,
}
#[derive(Default)]
pub struct IngressMixingStats {
// forward hop packets (i.e. to mixnode)
forward_hop_packets_received: AtomicUsize,
// final hop packets (i.e. to gateway)
final_hop_packets_received: AtomicUsize,
// packets that failed to get unwrapped
malformed_packets_received: AtomicUsize,
// (forward) packets that had invalid, i.e. too large, delays
excessive_delay_packets: AtomicUsize,
// forward hop packets (i.e. to mixnode)
forward_hop_packets_dropped: AtomicUsize,
// final hop packets (i.e. to gateway)
final_hop_packets_dropped: AtomicUsize,
senders: DashMap<IpAddr, IngressRecipientStats>,
}
impl IngressMixingStats {
pub fn forward_hop_packets_received(&self) -> usize {
self.forward_hop_packets_received.load(Ordering::Relaxed)
}
pub fn final_hop_packets_received(&self) -> usize {
self.final_hop_packets_received.load(Ordering::Relaxed)
}
pub fn malformed_packets_received(&self) -> usize {
self.malformed_packets_received.load(Ordering::Relaxed)
}
pub fn excessive_delay_packets(&self) -> usize {
self.excessive_delay_packets.load(Ordering::Relaxed)
}
pub fn forward_hop_packets_dropped(&self) -> usize {
self.forward_hop_packets_dropped.load(Ordering::Relaxed)
}
pub fn final_hop_packets_dropped(&self) -> usize {
self.final_hop_packets_dropped.load(Ordering::Relaxed)
}
pub fn senders(&self) -> &DashMap<IpAddr, IngressRecipientStats> {
&self.senders
}
pub fn remove_stale_sender(&self, sender: IpAddr) {
self.senders.remove(&sender);
}
}
#[derive(Debug, Default)]
pub struct LegacyMixingStats {
last_update_ts: AtomicI64,
previous_update_ts: AtomicI64,
received_since_last_update: AtomicUsize,
// note: sent does not imply forwarded. We don't know if it was delivered successfully
sent_since_last_update: AtomicUsize,
// we know for sure we dropped those packets
dropped_since_last_update: AtomicUsize,
}
impl LegacyMixingStats {
pub fn last_update(&self) -> OffsetDateTime {
// SAFETY: all values put here are guaranteed to be valid timestamps
#[allow(clippy::unwrap_used)]
OffsetDateTime::from_unix_timestamp(self.last_update_ts.load(Ordering::Relaxed)).unwrap()
}
pub fn previous_update(&self) -> OffsetDateTime {
// SAFETY: all values put here are guaranteed to be valid timestamps
#[allow(clippy::unwrap_used)]
OffsetDateTime::from_unix_timestamp(self.previous_update_ts.load(Ordering::Relaxed))
.unwrap()
}
pub fn received_since_last_update(&self) -> usize {
self.received_since_last_update.load(Ordering::Relaxed)
}
pub fn sent_since_last_update(&self) -> usize {
self.sent_since_last_update.load(Ordering::Relaxed)
}
pub fn dropped_since_last_update(&self) -> usize {
self.dropped_since_last_update.load(Ordering::Relaxed)
}
}
+27
View File
@@ -0,0 +1,27 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Default)]
pub struct NetworkStats {
// for now just experiment with basic data, we could always extend it
active_ingress_mixnet_connections: AtomicUsize,
}
impl NetworkStats {
pub fn new_active_ingress_mixnet_client(&self) {
self.active_ingress_mixnet_connections
.fetch_add(1, Ordering::Relaxed);
}
pub fn disconnected_ingress_mixnet_client(&self) {
self.active_ingress_mixnet_connections
.fetch_sub(1, Ordering::Relaxed);
}
pub fn active_ingress_mixnet_connections_count(&self) -> usize {
self.active_ingress_mixnet_connections
.load(Ordering::Relaxed)
}
}
@@ -1,304 +1,177 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_crypto::asymmetric::identity::{self, serde_helpers::bs58_ed25519_pubkey};
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::time::Duration;
use time::OffsetDateTime;
pub use mixing::*;
pub use session::*;
pub use verloc::*;
#[derive(Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct MixingStats {
#[serde(with = "time::serde::rfc3339")]
pub update_time: OffsetDateTime,
pub mod packets {
use serde::{Deserialize, Serialize};
#[serde(with = "time::serde::rfc3339")]
pub previous_update_time: OffsetDateTime,
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct PacketsStats {
pub ingress_mixing: IngressMixingStats,
pub egress_mixing: EgressMixingStats,
}
pub received_since_startup: u64,
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct IngressMixingStats {
// forward hop packets (i.e. to mixnode)
pub forward_hop_packets_received: usize,
// note: sent does not imply forwarded. We don't know if it was delivered successfully
pub sent_since_startup: u64,
// final hop packets (i.e. to gateway)
pub final_hop_packets_received: usize,
// we know for sure we dropped those packets
pub dropped_since_startup: u64,
// packets that failed to get unwrapped
pub malformed_packets_received: usize,
pub received_since_last_update: u64,
// (forward) packets that had invalid, i.e. too large, delays
pub excessive_delay_packets: usize,
// note: sent does not imply forwarded. We don't know if it was delivered successfully
pub sent_since_last_update: u64,
// forward hop packets (i.e. to mixnode)
pub forward_hop_packets_dropped: usize,
// we know for sure we dropped those packets
pub dropped_since_last_update: u64,
}
// final hop packets (i.e. to gateway)
pub final_hop_packets_dropped: usize,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct Session {
pub duration_ms: u64,
pub typ: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct EgressMixingStats {
pub forward_hop_packets_sent: usize,
#[derive(Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct SessionStats {
#[serde(with = "time::serde::rfc3339")]
pub update_time: OffsetDateTime,
pub forward_hop_packets_dropped: usize,
pub unique_active_users: u32,
#[serde(default = "Vec::new")] // field was added later
pub unique_active_users_hashes: Vec<String>,
pub sessions: Vec<Session>,
pub sessions_started: u32,
pub sessions_finished: u32,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct VerlocStats {
pub previous: VerlocResult,
pub current: VerlocResult,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub enum VerlocResult {
Data(VerlocResultData),
MeasurementInProgress,
#[default]
Unavailable,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct VerlocResultData {
pub nodes_tested: usize,
#[serde(with = "time::serde::rfc3339")]
pub run_started: OffsetDateTime,
#[serde(with = "time::serde::rfc3339::option")]
pub run_finished: Option<OffsetDateTime>,
pub results: Vec<VerlocNodeResult>,
}
impl Default for VerlocResultData {
fn default() -> Self {
VerlocResultData {
nodes_tested: 0,
run_started: OffsetDateTime::now_utc(),
run_finished: None,
results: vec![],
}
pub ack_packets_sent: usize,
}
}
impl VerlocResultData {
pub fn run_finished(&self) -> bool {
self.run_finished.is_some()
pub mod mixing {
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
#[derive(Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct LegacyMixingStats {
#[serde(with = "time::serde::rfc3339")]
pub update_time: OffsetDateTime,
#[serde(with = "time::serde::rfc3339")]
pub previous_update_time: OffsetDateTime,
pub received_since_startup: u64,
// note: sent does not imply forwarded. We don't know if it was delivered successfully
pub sent_since_startup: u64,
// we know for sure we dropped those packets
pub dropped_since_startup: u64,
pub received_since_last_update: u64,
// note: sent does not imply forwarded. We don't know if it was delivered successfully
pub sent_since_last_update: u64,
// we know for sure we dropped those packets
pub dropped_since_last_update: u64,
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct VerlocNodeResult {
#[serde(with = "bs58_ed25519_pubkey")]
pub node_identity: identity::PublicKey,
pub mod verloc {
use nym_crypto::asymmetric::ed25519::{self, serde_helpers::bs58_ed25519_pubkey};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use time::OffsetDateTime;
pub latest_measurement: Option<VerlocMeasurement>,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct VerlocStats {
pub previous: VerlocResult,
pub current: VerlocResult,
}
impl VerlocNodeResult {
pub fn new(
node_identity: identity::PublicKey,
latest_measurement: Option<VerlocMeasurement>,
) -> Self {
VerlocNodeResult {
node_identity,
latest_measurement,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub enum VerlocResult {
Data(VerlocResultData),
MeasurementInProgress,
#[default]
Unavailable,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct VerlocResultData {
pub nodes_tested: usize,
#[serde(with = "time::serde::rfc3339")]
pub run_started: OffsetDateTime,
#[serde(with = "time::serde::rfc3339::option")]
pub run_finished: Option<OffsetDateTime>,
pub results: Vec<VerlocNodeResult>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct VerlocNodeResult {
#[serde(with = "bs58_ed25519_pubkey")]
pub node_identity: ed25519::PublicKey,
pub latest_measurement: Option<VerlocMeasurement>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct VerlocMeasurement {
/// Minimum RTT duration it took to receive an echo packet.
#[serde(serialize_with = "humantime_serde::serialize")]
pub minimum: Duration,
/// Average RTT duration it took to receive the echo packets.
#[serde(serialize_with = "humantime_serde::serialize")]
pub mean: Duration,
/// Maximum RTT duration it took to receive an echo packet.
#[serde(serialize_with = "humantime_serde::serialize")]
pub maximum: Duration,
/// The standard deviation of the RTT duration it took to receive the echo packets.
#[serde(serialize_with = "humantime_serde::serialize")]
pub standard_deviation: Duration,
}
}
impl PartialOrd for VerlocNodeResult {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VerlocNodeResult {
fn cmp(&self, other: &Self) -> Ordering {
// if both have measurement, compare measurements
// then if only one have measurement, prefer that one
// completely ignore identity as it makes no sense to order by it
if let Some(self_measurement) = &self.latest_measurement {
if let Some(other_measurement) = &other.latest_measurement {
self_measurement.cmp(other_measurement)
} else {
Ordering::Less
}
} else if other.latest_measurement.is_some() {
Ordering::Greater
} else {
Ordering::Equal
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct VerlocMeasurement {
/// Minimum RTT duration it took to receive an echo packet.
#[serde(serialize_with = "humantime_serde::serialize")]
pub minimum: Duration,
/// Average RTT duration it took to receive the echo packets.
#[serde(serialize_with = "humantime_serde::serialize")]
pub mean: Duration,
/// Maximum RTT duration it took to receive an echo packet.
#[serde(serialize_with = "humantime_serde::serialize")]
pub maximum: Duration,
/// The standard deviation of the RTT duration it took to receive the echo packets.
#[serde(serialize_with = "humantime_serde::serialize")]
pub standard_deviation: Duration,
}
impl VerlocMeasurement {
pub fn new(raw_results: &[Duration]) -> Self {
let minimum = raw_results.iter().min().copied().unwrap_or_default();
let maximum = raw_results.iter().max().copied().unwrap_or_default();
let mean = Self::duration_mean(raw_results);
let standard_deviation = Self::duration_standard_deviation(raw_results, mean);
VerlocMeasurement {
minimum,
mean,
maximum,
standard_deviation,
}
}
fn duration_mean(data: &[Duration]) -> Duration {
if data.is_empty() {
return Default::default();
}
let sum = data.iter().sum::<Duration>();
let count = data.len() as u32;
sum / count
}
fn duration_standard_deviation(data: &[Duration], mean: Duration) -> Duration {
if data.is_empty() {
return Default::default();
}
let variance_micros = data
.iter()
.map(|&value| {
// make sure we don't underflow
let diff = if mean > value {
mean - value
} else {
value - mean
};
// we don't need nanos precision
let diff_micros = diff.as_micros();
diff_micros * diff_micros
})
.sum::<u128>()
/ data.len() as u128;
// we shouldn't really overflow as our differences shouldn't be larger than couple seconds at the worst possible case scenario
let std_deviation_micros = (variance_micros as f64).sqrt() as u64;
Duration::from_micros(std_deviation_micros)
}
}
impl Display for VerlocMeasurement {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"rtt min/avg/max/mdev = {} / {} / {} / {}",
humantime::format_duration(self.minimum),
humantime::format_duration(self.mean),
humantime::format_duration(self.maximum),
humantime::format_duration(self.standard_deviation)
)
}
}
impl PartialOrd for VerlocMeasurement {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VerlocMeasurement {
fn cmp(&self, other: &Self) -> Ordering {
// minimum value is most important, then look at standard deviation, then mean and finally maximum
let min_cmp = self.minimum.cmp(&other.minimum);
if min_cmp != Ordering::Equal {
return min_cmp;
}
let std_dev_cmp = self.standard_deviation.cmp(&other.standard_deviation);
if std_dev_cmp != Ordering::Equal {
return std_dev_cmp;
}
let std_dev_cmp = self.mean.cmp(&other.mean);
if std_dev_cmp != Ordering::Equal {
return std_dev_cmp;
}
self.maximum.cmp(&other.maximum)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sorting_vec_of_verlocs() {
let some_identity =
identity::PublicKey::from_base58_string("Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt")
.unwrap();
let no_measurement = VerlocNodeResult::new(some_identity, None);
let low_min = VerlocNodeResult::new(
some_identity,
Some(VerlocMeasurement {
minimum: Duration::from_millis(42),
mean: Duration::from_millis(43),
maximum: Duration::from_millis(44),
standard_deviation: Duration::from_millis(45),
}),
);
let higher_min = VerlocNodeResult::new(
some_identity,
Some(VerlocMeasurement {
minimum: Duration::from_millis(420),
mean: Duration::from_millis(430),
maximum: Duration::from_millis(440),
standard_deviation: Duration::from_millis(450),
}),
);
let mut vec_verloc = vec![no_measurement, low_min, no_measurement, higher_min];
vec_verloc.sort();
let expected_sorted = vec![low_min, higher_min, no_measurement, no_measurement];
assert_eq!(expected_sorted, vec_verloc);
pub mod session {
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
#[derive(Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct Session {
pub duration_ms: u64,
pub typ: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct SessionStats {
#[serde(with = "time::serde::rfc3339")]
pub update_time: OffsetDateTime,
pub unique_active_users: u32,
#[serde(default = "Vec::new")] // field was added later
pub unique_active_users_hashes: Vec<String>,
pub sessions: Vec<Session>,
pub sessions_started: u32,
pub sessions_finished: u32,
}
}
+4 -2
View File
@@ -64,12 +64,14 @@ pub mod routes {
pub mod metrics {
use super::*;
pub const MIXING: &str = "/mixing";
pub const LEGACY_MIXING: &str = "/mixing";
pub const PACKETS_STATS: &str = "/packets-stats";
pub const SESSIONS: &str = "/sessions";
pub const VERLOC: &str = "/verloc";
pub const PROMETHEUS: &str = "/prometheus";
absolute_route!(mixing_absolute, metrics_absolute(), MIXING);
absolute_route!(legacy_mixing_absolute, metrics_absolute(), LEGACY_MIXING);
absolute_route!(packets_stats_absolute, metrics_absolute(), PACKETS_STATS);
absolute_route!(sessions_absolute, metrics_absolute(), SESSIONS);
absolute_route!(verloc_absolute, metrics_absolute(), VERLOC);
absolute_route!(prometheus_absolute, metrics_absolute(), PROMETHEUS);

Some files were not shown because too many files have changed in this diff Show More