Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a520df064 | |||
| 6bb5f1bcae | |||
| e6b10c708c | |||
| 56fc135c2f | |||
| 24ff4272b6 | |||
| e6d5f463e7 | |||
| cad47732b7 | |||
| f4ff6717e0 | |||
| d8b8b38101 | |||
| 8e6ceddc66 | |||
| d029a58e13 | |||
| 21f8cb89d6 | |||
| 4ac9cdb1b1 | |||
| 296f243433 | |||
| 10b6ad050b | |||
| 5441960976 | |||
| e6e25dacea | |||
| 77ab256588 | |||
| fa2cbb5d21 | |||
| c3e4f944d5 | |||
| 933bbbb67d | |||
| 3f300cc2c1 | |||
| 30da87bf41 | |||
| e32783bced | |||
| 49687270b1 | |||
| a49957cb5c | |||
| 8b8c583ac5 | |||
| 9d808d30c2 | |||
| 880a33fb20 | |||
| 932bd0660f | |||
| 80f3ddca89 | |||
| c7b0eed15f | |||
| 9224f01d49 | |||
| 878fe85d66 | |||
| f060d63f2e | |||
| 8916b021a9 | |||
| dccdde108c | |||
| 9d661e7a7b | |||
| 76ce1bc0f9 | |||
| d3648f13c5 | |||
| 9a931b9251 | |||
| f4ba8ac2b3 | |||
| c274cc588d | |||
| 7dd1dd1a6c | |||
| 982786b678 | |||
| 561182ce6b | |||
| f4b59158df |
@@ -89,7 +89,7 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace --all-targets --exclude nym-gateway-probe -- -D warnings
|
||||
args: --workspace --all-targets --exclude nym-gateway-probe --exclude nym-node-status-api -- -D warnings
|
||||
|
||||
- name: Clippy (non-macos)
|
||||
if: contains(matrix.os, 'linux') || contains(matrix.os, 'windows')
|
||||
@@ -104,6 +104,14 @@ jobs:
|
||||
with:
|
||||
command: build
|
||||
|
||||
# only build on linux because of wg FFI bindings of its dependency (network probe)
|
||||
- name: Build nym-node-status-api (linux only)
|
||||
if: runner.os == 'Linux'
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: -p nym-node-status-api
|
||||
|
||||
- name: Build all examples
|
||||
if: contains(matrix.os, 'linux')
|
||||
uses: actions-rs/cargo@v1
|
||||
|
||||
@@ -3,7 +3,7 @@ name: ci-check-ns-api-version
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "nym-node-status-api/**"
|
||||
- "nym-node-status-api/nym-node-status-api/**"
|
||||
|
||||
env:
|
||||
WORKING_DIRECTORY: "nym-node-status-api/nym-node-status-api"
|
||||
|
||||
Generated
+543
-6
@@ -536,6 +536,19 @@ dependencies = [
|
||||
"tungstenite 0.21.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asynchronous-codec"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atoi"
|
||||
version = "2.0.0"
|
||||
@@ -703,12 +716,28 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base-x"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
|
||||
|
||||
[[package]]
|
||||
name = "base16ct"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
||||
|
||||
[[package]]
|
||||
name = "base256emoji"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c"
|
||||
dependencies = [
|
||||
"const-str 0.4.3",
|
||||
"match-lookup",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
@@ -1391,6 +1420,12 @@ version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "const-str"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3"
|
||||
|
||||
[[package]]
|
||||
name = "const-str"
|
||||
version = "0.5.7"
|
||||
@@ -1459,6 +1494,15 @@ dependencies = [
|
||||
"rand 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core2"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmos-sdk-proto"
|
||||
version = "0.27.0"
|
||||
@@ -2134,6 +2178,26 @@ version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding-macro"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d"
|
||||
dependencies = [
|
||||
"data-encoding",
|
||||
"data-encoding-macro-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding-macro-internal"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976"
|
||||
dependencies = [
|
||||
"data-encoding",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defguard_boringtun"
|
||||
version = "0.6.3"
|
||||
@@ -2905,6 +2969,16 @@ dependencies = [
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-bounded"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91f328e7fb845fc832912fb6a34f40cf6d1888c92f974d1893a54e97b5ff542e"
|
||||
dependencies = [
|
||||
"futures-timer",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
@@ -2930,6 +3004,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2987,6 +3062,27 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-ticker"
|
||||
version = "0.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9763058047f713632a52e916cc7f6a4b3fc6e9fc1ff8c5b1dc49e5a89041682e"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
|
||||
dependencies = [
|
||||
"gloo-timers 0.2.6",
|
||||
"send_wrapper 0.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
@@ -3096,6 +3192,18 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-timers"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-timers"
|
||||
version = "0.3.0"
|
||||
@@ -3383,6 +3491,12 @@ version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0"
|
||||
|
||||
[[package]]
|
||||
name = "hex_fmt"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f"
|
||||
|
||||
[[package]]
|
||||
name = "hickory-proto"
|
||||
version = "0.25.2"
|
||||
@@ -4108,6 +4222,7 @@ name = "integration-tests"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
"nym-bandwidth-controller",
|
||||
"nym-credential-verification",
|
||||
"nym-credentials-interface",
|
||||
@@ -4678,6 +4793,215 @@ version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||
|
||||
[[package]]
|
||||
name = "libp2p"
|
||||
version = "0.54.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbbe80f9c7e00526cd6b838075b9c171919404a4732cb2fa8ece0a093223bfc4"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"either",
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"getrandom 0.2.16",
|
||||
"libp2p-allow-block-list",
|
||||
"libp2p-connection-limits",
|
||||
"libp2p-core",
|
||||
"libp2p-gossipsub",
|
||||
"libp2p-identify",
|
||||
"libp2p-identity",
|
||||
"libp2p-metrics",
|
||||
"libp2p-ping",
|
||||
"libp2p-swarm",
|
||||
"multiaddr",
|
||||
"pin-project",
|
||||
"rw-stream-sink",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libp2p-allow-block-list"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1027ccf8d70320ed77e984f273bc8ce952f623762cb9bf2d126df73caef8041"
|
||||
dependencies = [
|
||||
"libp2p-core",
|
||||
"libp2p-identity",
|
||||
"libp2p-swarm",
|
||||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libp2p-connection-limits"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d003540ee8baef0d254f7b6bfd79bac3ddf774662ca0abf69186d517ef82ad8"
|
||||
dependencies = [
|
||||
"libp2p-core",
|
||||
"libp2p-identity",
|
||||
"libp2p-swarm",
|
||||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libp2p-core"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a61f26c83ed111104cd820fe9bc3aaabbac5f1652a1d213ed6e900b7918a1298"
|
||||
dependencies = [
|
||||
"either",
|
||||
"fnv",
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"libp2p-identity",
|
||||
"multiaddr",
|
||||
"multihash",
|
||||
"multistream-select",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"pin-project",
|
||||
"quick-protobuf",
|
||||
"rand 0.8.5",
|
||||
"rw-stream-sink",
|
||||
"smallvec",
|
||||
"thiserror 1.0.69",
|
||||
"tracing",
|
||||
"unsigned-varint 0.8.0",
|
||||
"void",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libp2p-gossipsub"
|
||||
version = "0.47.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4e830fdf24ac8c444c12415903174d506e1e077fbe3875c404a78c5935a8543"
|
||||
dependencies = [
|
||||
"asynchronous-codec",
|
||||
"base64 0.22.1",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"either",
|
||||
"fnv",
|
||||
"futures",
|
||||
"futures-ticker",
|
||||
"getrandom 0.2.16",
|
||||
"hex_fmt",
|
||||
"libp2p-core",
|
||||
"libp2p-identity",
|
||||
"libp2p-swarm",
|
||||
"prometheus-client",
|
||||
"quick-protobuf",
|
||||
"quick-protobuf-codec",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"sha2 0.10.9",
|
||||
"smallvec",
|
||||
"tracing",
|
||||
"void",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libp2p-identify"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1711b004a273be4f30202778856368683bd9a83c4c7dcc8f848847606831a4e3"
|
||||
dependencies = [
|
||||
"asynchronous-codec",
|
||||
"either",
|
||||
"futures",
|
||||
"futures-bounded",
|
||||
"futures-timer",
|
||||
"libp2p-core",
|
||||
"libp2p-identity",
|
||||
"libp2p-swarm",
|
||||
"lru",
|
||||
"quick-protobuf",
|
||||
"quick-protobuf-codec",
|
||||
"smallvec",
|
||||
"thiserror 1.0.69",
|
||||
"tracing",
|
||||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libp2p-identity"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0c7892c221730ba55f7196e98b0b8ba5e04b4155651736036628e9f73ed6fc3"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"ed25519-dalek",
|
||||
"hkdf",
|
||||
"multihash",
|
||||
"quick-protobuf",
|
||||
"rand 0.8.5",
|
||||
"sha2 0.10.9",
|
||||
"thiserror 2.0.17",
|
||||
"tracing",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libp2p-metrics"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ebafa94a717c8442d8db8d3ae5d1c6a15e30f2d347e0cd31d057ca72e42566"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"libp2p-core",
|
||||
"libp2p-identify",
|
||||
"libp2p-identity",
|
||||
"libp2p-ping",
|
||||
"libp2p-swarm",
|
||||
"pin-project",
|
||||
"prometheus-client",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libp2p-ping"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "005a34420359223b974ee344457095f027e51346e992d1e0dcd35173f4cdd422"
|
||||
dependencies = [
|
||||
"either",
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"libp2p-core",
|
||||
"libp2p-identity",
|
||||
"libp2p-swarm",
|
||||
"rand 0.8.5",
|
||||
"tracing",
|
||||
"void",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libp2p-swarm"
|
||||
version = "0.45.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7dd6741793d2c1fb2088f67f82cf07261f25272ebe3c0b0c311e0c6b50e851a"
|
||||
dependencies = [
|
||||
"either",
|
||||
"fnv",
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"getrandom 0.2.16",
|
||||
"libp2p-core",
|
||||
"libp2p-identity",
|
||||
"lru",
|
||||
"multistream-select",
|
||||
"once_cell",
|
||||
"rand 0.8.5",
|
||||
"smallvec",
|
||||
"tracing",
|
||||
"void",
|
||||
"wasm-bindgen-futures",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.12"
|
||||
@@ -4757,6 +5081,15 @@ version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru-slab"
|
||||
version = "0.1.2"
|
||||
@@ -4834,6 +5167,17 @@ dependencies = [
|
||||
"web_atoms",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "match-lookup"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "match_token"
|
||||
version = "0.35.0"
|
||||
@@ -5034,6 +5378,60 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multiaddr"
|
||||
version = "0.18.2"
|
||||
source = "git+https://github.com/mfahampshire/rust-multiaddr?branch=nym-protocol#b9966d6eedd09236ee76f157f985d04e02f8b880"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"byteorder",
|
||||
"data-encoding",
|
||||
"libp2p-identity",
|
||||
"multibase",
|
||||
"multihash",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"unsigned-varint 0.8.0",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multibase"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77"
|
||||
dependencies = [
|
||||
"base-x",
|
||||
"base256emoji",
|
||||
"data-encoding",
|
||||
"data-encoding-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multihash"
|
||||
version = "0.19.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d"
|
||||
dependencies = [
|
||||
"core2",
|
||||
"unsigned-varint 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multistream-select"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea0df8e5eec2298a62b326ee4f0d7fe1a6b90a09dfcf9df37b38f947a8c42f19"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
"log",
|
||||
"pin-project",
|
||||
"smallvec",
|
||||
"unsigned-varint 0.7.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-packet-core"
|
||||
version = "0.8.1"
|
||||
@@ -5521,7 +5919,7 @@ dependencies = [
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"clap_complete_fig",
|
||||
"const-str",
|
||||
"const-str 0.5.7",
|
||||
"log",
|
||||
"opentelemetry",
|
||||
"opentelemetry-jaeger",
|
||||
@@ -5686,12 +6084,13 @@ version = "1.20.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"bincode",
|
||||
"bs58",
|
||||
"cfg-if",
|
||||
"clap",
|
||||
"comfy-table",
|
||||
"futures",
|
||||
"gloo-timers",
|
||||
"gloo-timers 0.3.0",
|
||||
"http-body-util",
|
||||
"humantime",
|
||||
"hyper 1.8.1",
|
||||
@@ -5731,6 +6130,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-tungstenite",
|
||||
"tokio-util",
|
||||
"tokio_with_wasm",
|
||||
"tracing",
|
||||
"tungstenite 0.20.1",
|
||||
@@ -5799,7 +6199,7 @@ version = "1.4.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
"gloo-timers",
|
||||
"gloo-timers 0.3.0",
|
||||
"js-sys",
|
||||
"nym-bin-common",
|
||||
"nym-gateway-requests",
|
||||
@@ -5813,6 +6213,7 @@ dependencies = [
|
||||
"serde-wasm-bindgen 0.6.5",
|
||||
"serde_json",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tokio_with_wasm",
|
||||
"tsify",
|
||||
"wasm-bindgen",
|
||||
@@ -6488,6 +6889,7 @@ dependencies = [
|
||||
"nym-validator-client",
|
||||
"pnet_packet",
|
||||
"rand 0.8.5",
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.17",
|
||||
@@ -6497,6 +6899,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"utoipa",
|
||||
"vergen-gitcl",
|
||||
"x25519-dalek",
|
||||
]
|
||||
@@ -6831,6 +7234,38 @@ dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-libp2p-wasm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"getrandom 0.2.16",
|
||||
"gloo-timers 0.3.0",
|
||||
"hex",
|
||||
"js-sys",
|
||||
"libp2p",
|
||||
"libp2p-identify",
|
||||
"libp2p-identity",
|
||||
"libp2p-ping",
|
||||
"libp2p-swarm",
|
||||
"log",
|
||||
"nym-client-wasm",
|
||||
"nym-sphinx-addressing",
|
||||
"nym-sphinx-anonymous-replies",
|
||||
"nym-wasm-client-core",
|
||||
"nym-wasm-utils",
|
||||
"parking_lot",
|
||||
"send_wrapper 0.6.0",
|
||||
"serde",
|
||||
"serde-wasm-bindgen 0.6.5",
|
||||
"thiserror 2.0.17",
|
||||
"tokio_with_wasm",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-bindgen-test",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-lp"
|
||||
version = "0.1.0"
|
||||
@@ -6906,6 +7341,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"nym-test-utils",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7097,6 +7533,7 @@ dependencies = [
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tokio-util",
|
||||
"url",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -7125,6 +7562,7 @@ dependencies = [
|
||||
"csv",
|
||||
"cupid",
|
||||
"futures",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"human-repr",
|
||||
"humantime-serde",
|
||||
@@ -7232,7 +7670,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-status-agent"
|
||||
version = "1.0.7"
|
||||
version = "1.1.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -7241,6 +7679,8 @@ dependencies = [
|
||||
"nym-crypto",
|
||||
"nym-node-status-client",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -7249,7 +7689,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-status-api"
|
||||
version = "4.0.12"
|
||||
version = "4.0.14"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
@@ -7270,6 +7710,7 @@ dependencies = [
|
||||
"nym-credentials",
|
||||
"nym-crypto",
|
||||
"nym-ecash-time",
|
||||
"nym-gateway-probe",
|
||||
"nym-http-api-client",
|
||||
"nym-http-api-common",
|
||||
"nym-mixnet-contract-common",
|
||||
@@ -7525,6 +7966,7 @@ dependencies = [
|
||||
"nym-test-utils",
|
||||
"nym-wireguard-types",
|
||||
"serde",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7705,6 +8147,7 @@ dependencies = [
|
||||
"tap",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -7742,6 +8185,7 @@ dependencies = [
|
||||
name = "nym-sphinx"
|
||||
version = "1.20.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nym-crypto",
|
||||
"nym-metrics",
|
||||
"nym-mixnet-contract-common",
|
||||
@@ -7759,6 +8203,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_distr",
|
||||
"serde",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -7808,6 +8253,7 @@ dependencies = [
|
||||
"nym-topology",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"serde",
|
||||
"thiserror 2.0.17",
|
||||
"tracing",
|
||||
"wasm-bindgen",
|
||||
@@ -7856,6 +8302,7 @@ dependencies = [
|
||||
"nym-sphinx-anonymous-replies",
|
||||
"nym-sphinx-params",
|
||||
"nym-sphinx-types",
|
||||
"serde",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
@@ -7996,6 +8443,7 @@ dependencies = [
|
||||
"futures",
|
||||
"log",
|
||||
"nym-test-utils",
|
||||
"serde",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -8366,12 +8814,12 @@ dependencies = [
|
||||
"futures",
|
||||
"ip_network",
|
||||
"ipnetwork",
|
||||
"nym-authenticator-requests",
|
||||
"nym-credential-verification",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-gateway-requests",
|
||||
"nym-gateway-storage",
|
||||
"nym-ip-packet-requests",
|
||||
"nym-metrics",
|
||||
"nym-network-defaults",
|
||||
"nym-node-metrics",
|
||||
@@ -9363,6 +9811,29 @@ dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prometheus-client"
|
||||
version = "0.22.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca"
|
||||
dependencies = [
|
||||
"dtoa",
|
||||
"itoa",
|
||||
"parking_lot",
|
||||
"prometheus-client-derive-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prometheus-client-derive-encode"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost"
|
||||
version = "0.13.5"
|
||||
@@ -9446,6 +9917,28 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quick-protobuf"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-protobuf-codec"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15a0580ab32b169745d7a39db2ba969226ca16738931be152a3209b409de2474"
|
||||
dependencies = [
|
||||
"asynchronous-codec",
|
||||
"bytes",
|
||||
"quick-protobuf",
|
||||
"thiserror 1.0.69",
|
||||
"unsigned-varint 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.9"
|
||||
@@ -10087,6 +10580,17 @@ version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "rw-stream-sink"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8c9026ff5d2f23da5e45bbc283f156383001bfb09c4e44256d02c1a685fe9a1"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"pin-project",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.22"
|
||||
@@ -10289,6 +10793,21 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "send_wrapper"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0"
|
||||
|
||||
[[package]]
|
||||
name = "send_wrapper"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
@@ -12656,6 +13175,18 @@ version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "unsigned-varint"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105"
|
||||
|
||||
[[package]]
|
||||
name = "unsigned-varint"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
@@ -12879,6 +13410,12 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
|
||||
[[package]]
|
||||
name = "vt100"
|
||||
version = "0.16.2"
|
||||
|
||||
+6
-1
@@ -172,6 +172,7 @@ members = [
|
||||
# "wasm/full-nym-wasm", # If we uncomment this again, remember to also uncomment the profile settings below
|
||||
"wasm/mix-fetch",
|
||||
"wasm/node-tester",
|
||||
"wasm/libp2p-nym",
|
||||
"wasm/zknym-lib",
|
||||
"nym-gateway-probe",
|
||||
"integration-tests", "common/nym-lp-transport", "common/nym-kkt-ciphersuite",
|
||||
@@ -185,7 +186,6 @@ default-members = [
|
||||
"nym-credential-proxy/nym-credential-proxy",
|
||||
"nym-node",
|
||||
"nym-node-status-api/nym-node-status-agent",
|
||||
"nym-node-status-api/nym-node-status-api",
|
||||
"nym-statistics-api",
|
||||
"nym-validator-rewarder",
|
||||
"nyx-chain-watcher",
|
||||
@@ -544,6 +544,11 @@ web-sys = "0.3.76"
|
||||
#[patch.crates-io]
|
||||
#sphinx-packet = { path = "../sphinx" }
|
||||
|
||||
# Patch multiaddr with Protocol::Nym support for libp2p-nym transport
|
||||
[patch.crates-io.multiaddr]
|
||||
git = "https://github.com/mfahampshire/rust-multiaddr"
|
||||
branch = "nym-protocol"
|
||||
|
||||
# Profile settings for individual crates
|
||||
|
||||
# Compile-time verified queries do quite a bit of work at compile time. Incremental
|
||||
|
||||
@@ -20,13 +20,12 @@ use nym_crypto::asymmetric::x25519::{PrivateKey, PublicKey};
|
||||
use sha2::Sha256;
|
||||
|
||||
pub type PendingRegistrations = HashMap<PeerPublicKey, RegistrationData>;
|
||||
pub type PrivateIPs = HashMap<IpPair, Taken>;
|
||||
pub type PrivateIPs = HashMap<IpPair, SystemTime>;
|
||||
|
||||
#[cfg(feature = "verify")]
|
||||
pub type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
pub type Nonce = u64;
|
||||
pub type Taken = Option<SystemTime>;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct IpPair {
|
||||
|
||||
@@ -21,7 +21,7 @@ pub struct MockBandwidthController {
|
||||
impl BandwidthTicketProvider for MockBandwidthController {
|
||||
async fn get_ecash_ticket(
|
||||
&self,
|
||||
_ticket_type: TicketType,
|
||||
ticket_type: TicketType,
|
||||
_gateway_id: PublicKey,
|
||||
tickets_to_spend: u32,
|
||||
) -> Result<PreparedCredential, BandwidthControllerError> {
|
||||
@@ -100,6 +100,10 @@ impl BandwidthTicketProvider for MockBandwidthController {
|
||||
let mut credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES)
|
||||
.expect("Failed to deserialize test credential - this is a bug in the test harness");
|
||||
|
||||
// change the ticket type to the requested ticket
|
||||
// note that verification outside mocks is going to fail
|
||||
credential.payment.t_type = ticket_type.to_repr() as u8;
|
||||
|
||||
// Update spend_date to today to pass validation
|
||||
credential.spend_date = OffsetDateTime::now_utc().date();
|
||||
|
||||
|
||||
@@ -57,3 +57,22 @@ where
|
||||
Ok(Some(token))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<T: BandwidthTicketProvider + ?Sized + Send> BandwidthTicketProvider for Box<T> {
|
||||
async fn get_ecash_ticket(
|
||||
&self,
|
||||
ticket_type: TicketType,
|
||||
gateway_id: ed25519::PublicKey,
|
||||
tickets_to_spend: u32,
|
||||
) -> Result<PreparedCredential, BandwidthControllerError> {
|
||||
(**self)
|
||||
.get_ecash_ticket(ticket_type, gateway_id, tickets_to_spend)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError> {
|
||||
(**self).get_upgrade_mode_token().await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ documentation.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
bincode = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
bs58 = { workspace = true }
|
||||
clap = { workspace = true, optional = true }
|
||||
@@ -29,6 +30,7 @@ sha2 = { workspace = true }
|
||||
si-scale = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
url = { workspace = true, features = ["serde"] }
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
time = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync", "macros"] }
|
||||
tracing = { workspace = true }
|
||||
|
||||
@@ -36,6 +36,7 @@ use crate::init::{
|
||||
types::{GatewaySetup, InitialisationResult},
|
||||
};
|
||||
use futures::channel::mpsc;
|
||||
use futures::SinkExt;
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_client_core_config_types::{ForgetMe, RememberMe};
|
||||
use nym_client_core_gateways_storage::{GatewayDetails, GatewaysDetailsStore};
|
||||
@@ -66,7 +67,7 @@ use std::os::raw::c_int as RawFd;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tokio_util::sync::{PollSendError, PollSender};
|
||||
use url::Url;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -112,10 +113,7 @@ pub struct ClientInput {
|
||||
}
|
||||
|
||||
impl ClientInput {
|
||||
pub async fn send(
|
||||
&self,
|
||||
message: InputMessage,
|
||||
) -> Result<(), tokio::sync::mpsc::error::SendError<InputMessage>> {
|
||||
pub async fn send(&mut self, message: InputMessage) -> Result<(), PollSendError<InputMessage>> {
|
||||
self.input_sender.send(message).await
|
||||
}
|
||||
}
|
||||
@@ -745,7 +743,7 @@ where
|
||||
config: &Config,
|
||||
user_agent: Option<UserAgent>,
|
||||
client_stats_id: String,
|
||||
input_sender: Sender<InputMessage>,
|
||||
input_sender: PollSender<InputMessage>,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
) -> ClientStatsSender {
|
||||
tracing::info!("Starting statistics control...");
|
||||
@@ -1013,7 +1011,7 @@ where
|
||||
&self.config,
|
||||
self.user_agent.clone(),
|
||||
generate_client_stats_id(*self_address.identity()),
|
||||
input_sender.clone(),
|
||||
tokio_util::sync::PollSender::new(input_sender.clone()),
|
||||
&shutdown_tracker.clone(),
|
||||
);
|
||||
|
||||
@@ -1139,7 +1137,7 @@ where
|
||||
client_input: ClientInputStatus::AwaitingProducer {
|
||||
client_input: ClientInput {
|
||||
connection_command_sender: client_connection_tx,
|
||||
input_sender,
|
||||
input_sender: PollSender::new(input_sender),
|
||||
client_request_sender,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -26,7 +26,7 @@ use crate::{
|
||||
error::ClientCoreError,
|
||||
};
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-credentials-storage"))]
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage;
|
||||
pub use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage;
|
||||
|
||||
pub use nym_client_core_gateways_storage as gateways_storage;
|
||||
pub use nym_client_core_gateways_storage::{GatewaysDetailsStore, InMemGatewaysDetails};
|
||||
|
||||
@@ -1,22 +1,36 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ClientCoreError;
|
||||
use crate::make_bincode_serializer;
|
||||
use bincode::Options;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_sphinx::receiver::ReconstructedMessage;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryInto;
|
||||
use tokio_util::{
|
||||
bytes::Buf,
|
||||
bytes::BytesMut,
|
||||
codec::{Decoder, Encoder},
|
||||
};
|
||||
|
||||
pub type InputMessageSender = tokio::sync::mpsc::Sender<InputMessage>;
|
||||
pub type InputMessageSender = tokio_util::sync::PollSender<InputMessage>;
|
||||
pub type InputMessageReceiver = tokio::sync::mpsc::Receiver<InputMessage>;
|
||||
|
||||
#[derive(Debug)]
|
||||
const LENGHT_ENCODING_PREFIX_SIZE: usize = 4;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum InputMessage {
|
||||
/// Fire an already prepared mix packets into the network.
|
||||
/// No guarantees are made about it. For example no retransmssion
|
||||
/// will be attempted if it gets dropped.
|
||||
///
|
||||
/// Packets are stored as pre-serialized bytes, which avoids
|
||||
/// requiring Serde on MixPacket.
|
||||
Premade {
|
||||
msgs: Vec<MixPacket>,
|
||||
packet_bytes: Vec<Vec<u8>>,
|
||||
lane: TransmissionLane,
|
||||
},
|
||||
|
||||
@@ -65,12 +79,16 @@ pub enum InputMessage {
|
||||
}
|
||||
|
||||
impl InputMessage {
|
||||
pub fn simple(data: &[u8], recipient: Recipient) -> Self {
|
||||
InputMessage::new_regular(recipient, data.to_vec(), TransmissionLane::General, None)
|
||||
}
|
||||
|
||||
pub fn new_premade(
|
||||
msgs: Vec<MixPacket>,
|
||||
packet_bytes: Vec<Vec<u8>>,
|
||||
lane: TransmissionLane,
|
||||
packet_type: PacketType,
|
||||
) -> Self {
|
||||
let message = InputMessage::Premade { msgs, lane };
|
||||
let message = InputMessage::Premade { packet_bytes, lane };
|
||||
if packet_type == PacketType::Mix {
|
||||
message
|
||||
} else {
|
||||
@@ -185,4 +203,394 @@ impl InputMessage {
|
||||
self.set_max_retransmissions(max_retransmissions);
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
pub fn serialized_size(&self) -> u64 {
|
||||
make_bincode_serializer()
|
||||
.serialized_size(self)
|
||||
.expect("failed to get serialized InputMessage size")
|
||||
+ LENGHT_ENCODING_PREFIX_SIZE as u64
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AdressedInputMessageCodec(pub Recipient);
|
||||
|
||||
impl Encoder<&[u8]> for AdressedInputMessageCodec {
|
||||
type Error = ClientCoreError;
|
||||
|
||||
fn encode(&mut self, item: &[u8], buf: &mut BytesMut) -> Result<(), Self::Error> {
|
||||
let mut codec = InputMessageCodec;
|
||||
let input_message = InputMessage::simple(item, self.0);
|
||||
codec.encode(input_message, buf)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InputMessageCodec;
|
||||
|
||||
impl Encoder<InputMessage> for InputMessageCodec {
|
||||
type Error = ClientCoreError;
|
||||
|
||||
fn encode(&mut self, item: InputMessage, buf: &mut BytesMut) -> Result<(), Self::Error> {
|
||||
#[allow(clippy::expect_used)]
|
||||
let encoded = make_bincode_serializer().serialize(&item)?;
|
||||
let encoded_len = encoded.len() as u32;
|
||||
let mut encoded_with_len = encoded_len.to_le_bytes().to_vec();
|
||||
encoded_with_len.extend(encoded);
|
||||
buf.reserve(encoded_with_len.len());
|
||||
buf.extend_from_slice(&encoded_with_len);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for InputMessageCodec {
|
||||
type Item = InputMessage;
|
||||
type Error = ClientCoreError;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
if buf.len() < LENGHT_ENCODING_PREFIX_SIZE {
|
||||
return Ok(None);
|
||||
}
|
||||
#[allow(clippy::expect_used)]
|
||||
let len = u32::from_le_bytes(buf[0..LENGHT_ENCODING_PREFIX_SIZE].try_into()?) as usize;
|
||||
if buf.len() < len + LENGHT_ENCODING_PREFIX_SIZE {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let decoded = make_bincode_serializer()
|
||||
.deserialize(&buf[LENGHT_ENCODING_PREFIX_SIZE..len + LENGHT_ENCODING_PREFIX_SIZE])?;
|
||||
|
||||
buf.advance(len + LENGHT_ENCODING_PREFIX_SIZE);
|
||||
|
||||
Ok(Some(decoded))
|
||||
}
|
||||
}
|
||||
|
||||
/// Codec for encoding/decoding `ReconstructedMessage` for the AsyncRead interface.
|
||||
///
|
||||
/// This codec was moved from `nymsphinx::receiver` to keep bincode serialization
|
||||
/// out of the core sphinx crate.
|
||||
///
|
||||
/// Uses a simple length-prefixed binary format:
|
||||
/// - 4 bytes: length of encoded message
|
||||
/// - N bytes: encoded message using `ReconstructedMessage::encode()`
|
||||
///
|
||||
/// The message encoding format is:
|
||||
/// - Without sender_tag: `[0][payload...]`
|
||||
/// - With sender_tag: `[1][16-byte tag][payload...]`
|
||||
pub struct ReconstructedMessageCodec;
|
||||
|
||||
impl Encoder<ReconstructedMessage> for ReconstructedMessageCodec {
|
||||
type Error = ClientCoreError;
|
||||
|
||||
fn encode(
|
||||
&mut self,
|
||||
item: ReconstructedMessage,
|
||||
buf: &mut BytesMut,
|
||||
) -> Result<(), Self::Error> {
|
||||
let encoded = item.encode();
|
||||
let encoded_len = encoded.len() as u32;
|
||||
buf.reserve(LENGHT_ENCODING_PREFIX_SIZE + encoded.len());
|
||||
buf.extend_from_slice(&encoded_len.to_le_bytes());
|
||||
buf.extend_from_slice(&encoded);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for ReconstructedMessageCodec {
|
||||
type Item = ReconstructedMessage;
|
||||
type Error = ClientCoreError;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
if buf.len() < LENGHT_ENCODING_PREFIX_SIZE {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let len = u32::from_le_bytes(buf[0..LENGHT_ENCODING_PREFIX_SIZE].try_into()?) as usize;
|
||||
|
||||
if buf.len() < len + LENGHT_ENCODING_PREFIX_SIZE {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let decoded = ReconstructedMessage::decode(
|
||||
&buf[LENGHT_ENCODING_PREFIX_SIZE..len + LENGHT_ENCODING_PREFIX_SIZE],
|
||||
)
|
||||
.map_err(|e| ClientCoreError::CodecError(e.to_string()))?;
|
||||
|
||||
buf.advance(len + LENGHT_ENCODING_PREFIX_SIZE);
|
||||
|
||||
Ok(Some(decoded))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use rand::SeedableRng;
|
||||
|
||||
fn test_recipient() -> Recipient {
|
||||
Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap()
|
||||
}
|
||||
|
||||
fn test_sender_tag() -> AnonymousSenderTag {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
AnonymousSenderTag::new_random(&mut rng)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_all_variants() {
|
||||
let mut codec = InputMessageCodec;
|
||||
{
|
||||
let mut buf = BytesMut::new();
|
||||
let msg = InputMessage::new_anonymous(
|
||||
test_recipient(),
|
||||
vec![1, 2, 3, 4, 5],
|
||||
3,
|
||||
TransmissionLane::General,
|
||||
None,
|
||||
);
|
||||
codec.encode(msg, &mut buf).unwrap();
|
||||
let decoded = codec
|
||||
.decode(&mut buf)
|
||||
.unwrap()
|
||||
.expect("Should decode message");
|
||||
|
||||
match decoded {
|
||||
InputMessage::Anonymous {
|
||||
data, reply_surbs, ..
|
||||
} => {
|
||||
assert_eq!(data, vec![1, 2, 3, 4, 5]);
|
||||
assert_eq!(reply_surbs, 3);
|
||||
}
|
||||
_ => panic!("Expected Anonymous variant"),
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut buf = BytesMut::new();
|
||||
let msg = InputMessage::new_reply(
|
||||
test_sender_tag(),
|
||||
vec![6, 7, 8],
|
||||
TransmissionLane::General,
|
||||
None,
|
||||
);
|
||||
codec.encode(msg, &mut buf).unwrap();
|
||||
let decoded = codec
|
||||
.decode(&mut buf)
|
||||
.unwrap()
|
||||
.expect("Should decode message");
|
||||
|
||||
match decoded {
|
||||
InputMessage::Reply { data, .. } => {
|
||||
assert_eq!(data, vec![6, 7, 8]);
|
||||
}
|
||||
_ => panic!("Expected Reply variant"),
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut buf = BytesMut::new();
|
||||
let inner = InputMessage::new_anonymous(
|
||||
test_recipient(),
|
||||
vec![9, 10],
|
||||
2,
|
||||
TransmissionLane::General,
|
||||
None,
|
||||
);
|
||||
let msg = InputMessage::new_wrapper(inner, PacketType::Mix);
|
||||
codec.encode(msg, &mut buf).unwrap();
|
||||
let decoded = codec
|
||||
.decode(&mut buf)
|
||||
.unwrap()
|
||||
.expect("Should decode message");
|
||||
|
||||
match decoded {
|
||||
InputMessage::MessageWrapper {
|
||||
message,
|
||||
packet_type,
|
||||
} => {
|
||||
assert_eq!(packet_type, PacketType::Mix);
|
||||
match *message {
|
||||
InputMessage::Anonymous {
|
||||
data, reply_surbs, ..
|
||||
} => {
|
||||
assert_eq!(data, vec![9, 10]);
|
||||
assert_eq!(reply_surbs, 2);
|
||||
}
|
||||
_ => panic!("Expected Anonymous inner message"),
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected MessageWrapper variant"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_sequential_messages() {
|
||||
let mut codec = InputMessageCodec;
|
||||
let mut buf = BytesMut::new();
|
||||
|
||||
codec
|
||||
.encode(
|
||||
InputMessage::new_anonymous(
|
||||
test_recipient(),
|
||||
vec![1, 2, 3],
|
||||
1,
|
||||
TransmissionLane::General,
|
||||
None,
|
||||
),
|
||||
&mut buf,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
codec
|
||||
.encode(
|
||||
InputMessage::new_anonymous(
|
||||
test_recipient(),
|
||||
vec![4, 5, 6, 7],
|
||||
2,
|
||||
TransmissionLane::General,
|
||||
None,
|
||||
),
|
||||
&mut buf,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
codec
|
||||
.encode(
|
||||
InputMessage::new_anonymous(
|
||||
test_recipient(),
|
||||
vec![8, 9],
|
||||
3,
|
||||
TransmissionLane::General,
|
||||
None,
|
||||
),
|
||||
&mut buf,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let decoded1 = codec
|
||||
.decode(&mut buf)
|
||||
.unwrap()
|
||||
.expect("Should decode first message");
|
||||
match decoded1 {
|
||||
InputMessage::Anonymous {
|
||||
data, reply_surbs, ..
|
||||
} => {
|
||||
assert_eq!(data, vec![1, 2, 3]);
|
||||
assert_eq!(reply_surbs, 1);
|
||||
}
|
||||
_ => panic!("Wrong variant"),
|
||||
}
|
||||
|
||||
let decoded2 = codec
|
||||
.decode(&mut buf)
|
||||
.unwrap()
|
||||
.expect("Should decode second message");
|
||||
match decoded2 {
|
||||
InputMessage::Anonymous {
|
||||
data, reply_surbs, ..
|
||||
} => {
|
||||
assert_eq!(data, vec![4, 5, 6, 7]);
|
||||
assert_eq!(reply_surbs, 2);
|
||||
}
|
||||
_ => panic!("Wrong variant"),
|
||||
}
|
||||
|
||||
let decoded3 = codec
|
||||
.decode(&mut buf)
|
||||
.unwrap()
|
||||
.expect("Should decode third message");
|
||||
match decoded3 {
|
||||
InputMessage::Anonymous {
|
||||
data, reply_surbs, ..
|
||||
} => {
|
||||
assert_eq!(data, vec![8, 9]);
|
||||
assert_eq!(reply_surbs, 3);
|
||||
}
|
||||
_ => panic!("Wrong variant"),
|
||||
}
|
||||
|
||||
// Buffer should be empty
|
||||
let decoded4 = codec.decode(&mut buf).unwrap();
|
||||
assert!(decoded4.is_none(), "Should have no more messages");
|
||||
assert_eq!(buf.len(), 0, "Buffer should be empty");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_message_handling() {
|
||||
let mut codec = InputMessageCodec;
|
||||
let mut buf = BytesMut::new();
|
||||
// Empty @ beginning
|
||||
assert!(codec.decode(&mut buf).unwrap().is_none());
|
||||
|
||||
let mut buf = BytesMut::from(&[0x10, 0x00][..]);
|
||||
assert!(codec.decode(&mut buf).unwrap().is_none());
|
||||
assert_eq!(buf.len(), 2, "Buffer should be unchanged");
|
||||
|
||||
let mut full_buf = BytesMut::new();
|
||||
codec
|
||||
.encode(
|
||||
InputMessage::new_anonymous(
|
||||
test_recipient(),
|
||||
vec![1, 2, 3, 4, 5],
|
||||
2,
|
||||
TransmissionLane::General,
|
||||
None,
|
||||
),
|
||||
&mut full_buf,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Only first half of the message
|
||||
let partial_len = full_buf.len() / 2;
|
||||
let mut partial_buf = full_buf.split_to(partial_len);
|
||||
|
||||
assert!(codec.decode(&mut partial_buf).unwrap().is_none());
|
||||
assert_eq!(partial_buf.len(), partial_len, "Buffer should be unchanged");
|
||||
|
||||
partial_buf.unsplit(full_buf);
|
||||
let decoded = codec.decode(&mut partial_buf).unwrap();
|
||||
assert!(decoded.is_some(), "Should decode complete message");
|
||||
match decoded.unwrap() {
|
||||
InputMessage::Anonymous { data, .. } => {
|
||||
assert_eq!(data, vec![1, 2, 3, 4, 5]);
|
||||
}
|
||||
_ => panic!("Expected Anonymous variant"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn addressed_codec_compatibility() {
|
||||
let recipient = test_recipient();
|
||||
let data = b"test message payload";
|
||||
|
||||
let mut addressed_codec = AdressedInputMessageCodec(recipient);
|
||||
let mut buf = BytesMut::new();
|
||||
addressed_codec.encode(data.as_ref(), &mut buf).unwrap();
|
||||
|
||||
let mut input_codec = InputMessageCodec;
|
||||
let decoded = input_codec
|
||||
.decode(&mut buf)
|
||||
.unwrap()
|
||||
.expect("Should decode");
|
||||
|
||||
match decoded {
|
||||
InputMessage::Regular {
|
||||
data: decoded_data,
|
||||
recipient: decoded_recipient,
|
||||
lane,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(decoded_data, data, "Data should match");
|
||||
assert_eq!(decoded_recipient, recipient, "Recipient should match");
|
||||
assert_eq!(lane, TransmissionLane::General, "Should use General lane");
|
||||
}
|
||||
_ => panic!("Expected Regular variant"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+24
-4
@@ -45,7 +45,25 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_premade_packets(&mut self, packets: Vec<MixPacket>, lane: TransmissionLane) {
|
||||
async fn handle_premade_packets(&mut self, packet_bytes: Vec<Vec<u8>>, lane: TransmissionLane) {
|
||||
// Deserialize packet bytes back to MixPacket
|
||||
let packets: Vec<MixPacket> = packet_bytes
|
||||
.into_iter()
|
||||
.filter_map(|bytes| {
|
||||
MixPacket::try_from_v2_bytes(&bytes)
|
||||
.map_err(|e| {
|
||||
warn!("Failed to deserialize premade packet: {}", e);
|
||||
e
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.collect();
|
||||
|
||||
if packets.is_empty() {
|
||||
warn!("No valid premade packets to send");
|
||||
return;
|
||||
}
|
||||
|
||||
self.message_handler
|
||||
.send_premade_mix_packets(
|
||||
packets
|
||||
@@ -156,7 +174,9 @@ where
|
||||
self.handle_reply(recipient_tag, data, lane, max_retransmissions)
|
||||
.await;
|
||||
}
|
||||
InputMessage::Premade { msgs, lane } => self.handle_premade_packets(msgs, lane).await,
|
||||
InputMessage::Premade { packet_bytes, lane } => {
|
||||
self.handle_premade_packets(packet_bytes, lane).await
|
||||
}
|
||||
InputMessage::MessageWrapper {
|
||||
message,
|
||||
packet_type,
|
||||
@@ -202,8 +222,8 @@ where
|
||||
self.handle_reply(recipient_tag, data, lane, max_retransmissions)
|
||||
.await;
|
||||
}
|
||||
InputMessage::Premade { msgs, lane } => {
|
||||
self.handle_premade_packets(msgs, lane).await
|
||||
InputMessage::Premade { packet_bytes, lane } => {
|
||||
self.handle_premade_packets(packet_bytes, lane).await
|
||||
}
|
||||
// MessageWrappers can't be nested
|
||||
InputMessage::MessageWrapper { .. } => {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#![warn(clippy::dbg_macro)]
|
||||
|
||||
use crate::client::inbound_messages::{InputMessage, InputMessageSender};
|
||||
use futures::StreamExt;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use nym_client_core_config_types::StatsReporting;
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_statistics_common::clients::{
|
||||
|
||||
@@ -255,6 +255,15 @@ pub enum ClientCoreError {
|
||||
|
||||
#[error("Could not access task registry, {0}")]
|
||||
RegistryAccess(#[from] RegistryAccessError),
|
||||
|
||||
#[error("Serialization error: {0}")]
|
||||
BincodeError(#[from] Box<bincode::ErrorKind>),
|
||||
|
||||
#[error("Could not coarce to array")]
|
||||
ArrayCreationFailure(#[from] std::array::TryFromSliceError),
|
||||
|
||||
#[error("Codec error: {0}")]
|
||||
CodecError(String),
|
||||
}
|
||||
|
||||
impl From<tungstenite::Error> for ClientCoreError {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![allow(deprecated)] // silences clippy warning: use of deprecated associated function `nym_crypto::generic_array::GenericArray::<T, N>::clone_from_slice`: please upgrade to generic-array 1.x - TODO
|
||||
use std::future::Future;
|
||||
|
||||
#[cfg(all(
|
||||
@@ -39,3 +40,10 @@ where
|
||||
{
|
||||
tokio::spawn(future);
|
||||
}
|
||||
|
||||
fn make_bincode_serializer() -> impl bincode::Options {
|
||||
use bincode::Options;
|
||||
bincode::DefaultOptions::new()
|
||||
.with_big_endian()
|
||||
.with_varint_encoding()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![allow(deprecated)] // silences clippy warning: use of deprecated associated function `nym_crypto::generic_array::GenericArray::<T, N>::from_exact_iter`: please upgrade to generic-array 1.x - TODO
|
||||
pub use backend::*;
|
||||
pub use combined::CombinedReplyStorage;
|
||||
pub use key_storage::SentReplyKeys;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![allow(clippy::derivable_impls)]
|
||||
// MAX: surpressing warning for the moment, will be dealt with in a different PR (TODO)
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![allow(deprecated)] // silences clippy warning: deprecated associated function `generic_array::GenericArray::<T, N>::from_exact_iter`: please upgrade to generic-array 1.x - TODO
|
||||
|
||||
#[cfg(feature = "asymmetric")]
|
||||
pub mod asymmetric;
|
||||
pub mod bech32_address_validation;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#![allow(deprecated)] // silences clippy warning: deprecated associated function `nym_crypto::generic_array::GenericArray::<T, N>::clone_from_slice`: please upgrade to generic-array 1.x - TODO
|
||||
|
||||
pub use nym_crypto::generic_array;
|
||||
use nym_crypto::OutputSizeUser;
|
||||
|
||||
@@ -70,7 +70,7 @@ impl BinaryRequest {
|
||||
|
||||
let plaintext = match self {
|
||||
BinaryRequest::ForwardSphinx { packet } => packet.into_v1_bytes()?,
|
||||
BinaryRequest::ForwardSphinxV2 { packet } => packet.into_v2_bytes()?,
|
||||
BinaryRequest::ForwardSphinxV2 { packet } => packet.to_v2_bytes()?,
|
||||
};
|
||||
|
||||
BinaryData::make_encrypted_blob(kind as u8, &plaintext, shared_key)
|
||||
|
||||
@@ -51,6 +51,9 @@ pub const VERCEL_COM_IPS: &[IpAddr] = &[
|
||||
IpAddr::V4(Ipv4Addr::new(198, 169, 1, 193)),
|
||||
];
|
||||
|
||||
pub const NYM_API_CDN: &str = "cdn1.media-platform.net";
|
||||
pub const NYM_API_CDN_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(172, 104, 178, 252))];
|
||||
|
||||
pub const NYM_COM_DOMAIN: &str = "nym.com";
|
||||
pub const NYM_COM_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(76, 76, 21, 22))];
|
||||
|
||||
@@ -88,6 +91,7 @@ pub fn default_static_addrs() -> HashMap<String, Vec<IpAddr>> {
|
||||
m.insert(YELP_FASTLY_DOMAIN.to_string(), YELP_FASTLY_IPS.to_vec());
|
||||
m.insert(VERCEL_APP_DOMAIN.to_string(), VERCEL_APP_IPS.to_vec());
|
||||
m.insert(VERCEL_COM_DOMAIN.to_string(), VERCEL_COM_IPS.to_vec());
|
||||
m.insert(NYM_API_CDN.to_string(), NYM_API_CDN_IPS.to_vec());
|
||||
m.insert(NYM_COM_DOMAIN.to_string(), NYM_COM_IPS.to_vec());
|
||||
m.insert(NYM_STATS_API_DOMAIN.to_string(), NYM_STATS_API_IPS.to_vec());
|
||||
m.insert(NYM_RPC_DOMAIN.to_string(), NYM_RPC_IPS.to_vec());
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![allow(deprecated)]
|
||||
// silences clippy warning: use of deprecated tuple variant `HttpClientError::GenericRequestFailure`: use another more strongly typed variant - this variant is only left for compatibility reasons - TODO
|
||||
|
||||
//! Nym HTTP API Client
|
||||
//!
|
||||
//! Centralizes and implements the core API client functionality. This crate provides custom,
|
||||
@@ -329,14 +332,14 @@ pub enum HttpClientError {
|
||||
|
||||
#[error("failed to send request for {url}: {source}")]
|
||||
RequestSendFailure {
|
||||
url: reqwest::Url,
|
||||
url: Box<reqwest::Url>,
|
||||
#[source]
|
||||
source: ReqwestErrorWrapper,
|
||||
},
|
||||
|
||||
#[error("failed to read response body from {url}: {source}")]
|
||||
ResponseReadFailure {
|
||||
url: reqwest::Url,
|
||||
url: Box<reqwest::Url>,
|
||||
headers: Box<HeaderMap>,
|
||||
status: StatusCode,
|
||||
#[source]
|
||||
@@ -353,7 +356,7 @@ pub enum HttpClientError {
|
||||
},
|
||||
|
||||
#[error("the requested resource could not be found at {url}")]
|
||||
NotFound { url: reqwest::Url },
|
||||
NotFound { url: Box<reqwest::Url> },
|
||||
|
||||
#[error("attempted to use domain fronting and clone a request containing stream data")]
|
||||
AttemptedToCloneStreamRequest,
|
||||
@@ -365,7 +368,7 @@ pub enum HttpClientError {
|
||||
"the request for {url} failed with status '{status}'. no additional error message provided. response headers: {headers:?}"
|
||||
)]
|
||||
RequestFailure {
|
||||
url: reqwest::Url,
|
||||
url: Box<reqwest::Url>,
|
||||
status: StatusCode,
|
||||
headers: Box<HeaderMap>,
|
||||
},
|
||||
@@ -374,7 +377,7 @@ pub enum HttpClientError {
|
||||
"the returned response from {url} was empty. status: '{status}'. response headers: {headers:?}"
|
||||
)]
|
||||
EmptyResponse {
|
||||
url: reqwest::Url,
|
||||
url: Box<reqwest::Url>,
|
||||
status: StatusCode,
|
||||
headers: Box<HeaderMap>,
|
||||
},
|
||||
@@ -383,7 +386,7 @@ pub enum HttpClientError {
|
||||
"failed to resolve request for {url}. status: '{status}'. response headers: {headers:?}. additional error message: {error}"
|
||||
)]
|
||||
EndpointFailure {
|
||||
url: reqwest::Url,
|
||||
url: Box<reqwest::Url>,
|
||||
status: StatusCode,
|
||||
headers: Box<HeaderMap>,
|
||||
error: String,
|
||||
@@ -453,7 +456,7 @@ impl HttpClientError {
|
||||
|
||||
pub fn request_send_error(url: reqwest::Url, source: reqwest::Error) -> Self {
|
||||
HttpClientError::RequestSendFailure {
|
||||
url,
|
||||
url: Box::new(url),
|
||||
source: ReqwestErrorWrapper(source),
|
||||
}
|
||||
}
|
||||
@@ -1517,7 +1520,7 @@ where
|
||||
|
||||
if !allow_empty && let Some(0) = res.content_length() {
|
||||
return Err(HttpClientError::EmptyResponse {
|
||||
url,
|
||||
url: Box::new(url),
|
||||
status,
|
||||
headers: Box::new(headers),
|
||||
});
|
||||
@@ -1530,25 +1533,25 @@ where
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|source| HttpClientError::ResponseReadFailure {
|
||||
url,
|
||||
url: Box::new(url),
|
||||
headers: Box::new(headers.clone()),
|
||||
status,
|
||||
source: ReqwestErrorWrapper(source),
|
||||
})?;
|
||||
decode_raw_response(&headers, full)
|
||||
} else if res.status() == StatusCode::NOT_FOUND {
|
||||
Err(HttpClientError::NotFound { url })
|
||||
Err(HttpClientError::NotFound { url: Box::new(url) })
|
||||
} else {
|
||||
let Ok(plaintext) = res.text().await else {
|
||||
return Err(HttpClientError::RequestFailure {
|
||||
url,
|
||||
url: Box::new(url),
|
||||
status,
|
||||
headers: Box::new(headers),
|
||||
});
|
||||
};
|
||||
|
||||
Err(HttpClientError::EndpointFailure {
|
||||
url,
|
||||
url: Box::new(url),
|
||||
status,
|
||||
headers: Box::new(headers),
|
||||
error: plaintext,
|
||||
|
||||
@@ -51,6 +51,10 @@ pub const NYM_APIS: &[ApiUrlConst] = &[
|
||||
url: "https://nym-frontdoor.global.ssl.fastly.net/api/",
|
||||
front_hosts: Some(&["yelp.global.ssl.fastly.net"]),
|
||||
},
|
||||
ApiUrlConst {
|
||||
url: "https://cdn1.media-platform.net/api/",
|
||||
front_hosts: None,
|
||||
},
|
||||
];
|
||||
|
||||
pub const NYM_VPN_API: &str = "https://nymvpn.com/api/";
|
||||
|
||||
@@ -28,7 +28,7 @@ impl MixnetConnectionBeacon {
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_mixnet_self_ping(&self) -> Result<u64> {
|
||||
async fn send_mixnet_self_ping(&mut self) -> Result<u64> {
|
||||
trace!("Sending mixnet self ping");
|
||||
let (input_message, request_id) = create_self_ping(self.our_address);
|
||||
self.mixnet_client_sender
|
||||
@@ -38,7 +38,7 @@ impl MixnetConnectionBeacon {
|
||||
Ok(request_id)
|
||||
}
|
||||
|
||||
pub async fn run(self, shutdown: CancellationToken) -> Result<()> {
|
||||
pub async fn run(mut self, shutdown: CancellationToken) -> Result<()> {
|
||||
debug!("Mixnet connection beacon is running");
|
||||
let mut ping_interval = tokio::time::interval(MIXNET_SELF_PING_INTERVAL);
|
||||
loop {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::StreamExt;
|
||||
use nym_sdk::mixnet::{MixnetClient, MixnetMessageSender, Recipient};
|
||||
use tracing::{debug, error};
|
||||
|
||||
@@ -22,23 +21,25 @@ pub async fn self_ping_and_wait(
|
||||
wait_for_self_ping_return(mixnet_client, &request_ids).await
|
||||
}
|
||||
|
||||
async fn send_self_pings(our_address: Recipient, mixnet_client: &MixnetClient) -> Result<Vec<u64>> {
|
||||
// Send pings
|
||||
let request_ids = futures::stream::iter(1..=3)
|
||||
.then(|_| async {
|
||||
async fn send_self_pings(
|
||||
our_address: Recipient,
|
||||
mixnet_client: &mut MixnetClient,
|
||||
) -> Result<Vec<u64>> {
|
||||
let sender = mixnet_client.split_sender();
|
||||
|
||||
let futures = (1..=3).map(|_| {
|
||||
let mut sender = sender.clone();
|
||||
async move {
|
||||
let (input_message, request_id) = create_self_ping(our_address);
|
||||
mixnet_client
|
||||
sender
|
||||
.send(input_message)
|
||||
.await
|
||||
.map_err(|err| Error::NymSdkError(Box::new(err)))?;
|
||||
Ok::<u64, Error>(request_id)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
.map_err(|e| Error::NymSdkError(Box::new(e)))?;
|
||||
Ok::<_, Error>(request_id)
|
||||
}
|
||||
});
|
||||
|
||||
// Check the vec of results and return the first error, if any. If there are not errors, unwrap
|
||||
// all the results into a vec of u64s.
|
||||
request_ids.into_iter().collect::<Result<Vec<_>>>()
|
||||
futures::future::try_join_all(futures).await
|
||||
}
|
||||
|
||||
async fn wait_for_self_ping_return(
|
||||
|
||||
@@ -12,8 +12,9 @@ readme.workspace = true
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
tokio = { workspace = true, features = ["net"] }
|
||||
tokio = { workspace = true, features = ["net", "io-util"] }
|
||||
nym-test-utils = { path = "../test-utils", optional = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[features]
|
||||
io-mocks = ["nym-test-utils"]
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
#[cfg(feature = "io-mocks")]
|
||||
use nym_test_utils::mocks::async_read_write::MockIOStream;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
use tokio::net::TcpStream;
|
||||
use tracing::debug;
|
||||
|
||||
// only used in internal code (and tests)
|
||||
#[allow(async_fn_in_trait)]
|
||||
@@ -13,6 +14,78 @@ pub trait LpTransport: AsyncRead + AsyncWrite + Sized {
|
||||
async fn connect(endpoint: SocketAddr) -> std::io::Result<Self>;
|
||||
|
||||
fn set_no_delay(&mut self, nodelay: bool) -> std::io::Result<()>;
|
||||
|
||||
/// Sends a serialised (and optionally encrypted) LP packet over the data stream with length-prefixed framing.
|
||||
///
|
||||
/// Format: 4-byte big-endian u32 length + packet bytes
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `packet_data` - The serialised LP packet to send
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error on network transmission fails.
|
||||
async fn send_serialised_packet(&mut self, packet_data: &[u8]) -> std::io::Result<()>
|
||||
where
|
||||
Self: Unpin,
|
||||
{
|
||||
// Send 4-byte length prefix (u32 big-endian)
|
||||
let len = packet_data.len() as u32;
|
||||
self.write_all(&len.to_be_bytes())
|
||||
.await
|
||||
.inspect_err(|e| debug!("Failed to send packet length: {e}"))?;
|
||||
|
||||
// Send the actual packet data
|
||||
self.write_all(packet_data)
|
||||
.await
|
||||
.inspect_err(|e| debug!("Failed to send packet data: {e}"))?;
|
||||
|
||||
// Flush to ensure data is sent immediately
|
||||
self.flush()
|
||||
.await
|
||||
.inspect_err(|e| debug!("Failed to flush stream: {e}"))?;
|
||||
|
||||
tracing::trace!(
|
||||
"Sent LP packet ({} bytes + 4 byte header)",
|
||||
packet_data.len()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Receives an LP packet from a TCP stream with length-prefixed framing.
|
||||
///
|
||||
/// Format: 4-byte big-endian u32 length + packet bytes
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error on network transmission fails.
|
||||
async fn receive_raw_packet(&mut self) -> std::io::Result<Vec<u8>>
|
||||
where
|
||||
Self: Unpin,
|
||||
{
|
||||
// Read 4-byte length prefix (u32 big-endian)
|
||||
let mut len_buf = [0u8; 4];
|
||||
self.read_exact(&mut len_buf)
|
||||
.await
|
||||
.inspect_err(|e| debug!("Failed to read packet length: {e}"))?;
|
||||
|
||||
let packet_len = u32::from_be_bytes(len_buf) as usize;
|
||||
|
||||
// Sanity check to prevent huge allocations
|
||||
const MAX_PACKET_SIZE: usize = 65536; // 64KB max
|
||||
if packet_len > MAX_PACKET_SIZE {
|
||||
return Err(std::io::Error::other(format!(
|
||||
"Packet size {packet_len} exceeds maximum {MAX_PACKET_SIZE}",
|
||||
)));
|
||||
}
|
||||
|
||||
// Read the actual packet data
|
||||
let mut packet_buf = vec![0u8; packet_len];
|
||||
self.read_exact(&mut packet_buf)
|
||||
.await
|
||||
.inspect_err(|e| debug!("Failed to read packet data: {e}"))?;
|
||||
|
||||
tracing::trace!("Received LP packet ({packet_len} bytes + 4 byte header)");
|
||||
Ok(packet_buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl LpTransport for TcpStream {
|
||||
|
||||
@@ -38,12 +38,25 @@ pub fn sessions_for_tests() -> (LpSession, LpSession) {
|
||||
// Use consistent salt for deterministic tests
|
||||
let salt = [1u8; 32];
|
||||
|
||||
let initiator_session =
|
||||
LpSession::new(receiver_index, true, init.clone(), resp.as_remote(), &salt)
|
||||
.expect("Test session creation failed");
|
||||
let initiator_session = LpSession::new(
|
||||
receiver_index,
|
||||
true,
|
||||
init.clone(),
|
||||
resp.as_remote(),
|
||||
&salt,
|
||||
packet::version::CURRENT,
|
||||
)
|
||||
.expect("Test session creation failed");
|
||||
|
||||
let responder_session = LpSession::new(receiver_index, false, resp, init.as_remote(), &salt)
|
||||
.expect("Test session creation failed");
|
||||
let responder_session = LpSession::new(
|
||||
receiver_index,
|
||||
false,
|
||||
resp,
|
||||
init.as_remote(),
|
||||
&salt,
|
||||
packet::version::CURRENT,
|
||||
)
|
||||
.expect("Test session creation failed");
|
||||
|
||||
(initiator_session, responder_session)
|
||||
}
|
||||
@@ -51,7 +64,7 @@ pub fn sessions_for_tests() -> (LpSession, LpSession) {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::message::LpMessage;
|
||||
use crate::packet::{LpHeader, LpPacket, TRAILER_LEN};
|
||||
use crate::packet::{LpHeader, LpPacket, TRAILER_LEN, version};
|
||||
use crate::session_manager::SessionManager;
|
||||
use crate::{LpError, sessions_for_tests};
|
||||
use bytes::BytesMut;
|
||||
@@ -182,11 +195,19 @@ mod tests {
|
||||
init.clone(),
|
||||
resp.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let _ = remote_manager
|
||||
.create_session_state_machine(receiver_index, false, resp, init.as_remote(), &salt)
|
||||
.create_session_state_machine(
|
||||
receiver_index,
|
||||
false,
|
||||
resp,
|
||||
init.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
// === Packet 1 (Counter 0 - Should succeed) ===
|
||||
let packet1 = LpPacket {
|
||||
|
||||
@@ -239,6 +239,18 @@ pub struct ForwardPacketData {
|
||||
}
|
||||
|
||||
impl ForwardPacketData {
|
||||
pub fn new(
|
||||
target_gateway_identity: ed25519::PublicKey,
|
||||
target_lp_address: String,
|
||||
inner_packet_bytes: Vec<u8>,
|
||||
) -> Self {
|
||||
ForwardPacketData {
|
||||
target_gateway_identity: target_gateway_identity.to_bytes(),
|
||||
target_lp_address,
|
||||
inner_packet_bytes,
|
||||
}
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
// 32 bytes target gateway identity
|
||||
// +
|
||||
|
||||
@@ -203,9 +203,9 @@ impl LpHeader {
|
||||
}
|
||||
|
||||
impl LpHeader {
|
||||
pub fn new(receiver_idx: u32, counter: u64) -> Self {
|
||||
pub fn new(receiver_idx: u32, counter: u64, protocol_version: u8) -> Self {
|
||||
Self {
|
||||
protocol_version: version::CURRENT,
|
||||
protocol_version,
|
||||
reserved: [0u8; 3],
|
||||
receiver_idx,
|
||||
counter,
|
||||
@@ -265,7 +265,7 @@ impl LpHeader {
|
||||
|
||||
Ok(LpHeader {
|
||||
protocol_version,
|
||||
reserved: [0u8; 3],
|
||||
reserved,
|
||||
receiver_idx,
|
||||
counter,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::ClientHelloData;
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_kkt::ciphersuite::{KEM, KEMKeyDigests, SignatureScheme, SigningKeyDigests};
|
||||
use std::collections::HashMap;
|
||||
@@ -29,6 +30,14 @@ impl LpLocalPeer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_client_hello_data(&self, timestamp: u64) -> ClientHelloData {
|
||||
ClientHelloData::new_with_fresh_salt(
|
||||
*self.x25519().public_key(),
|
||||
*self.ed25519().public_key(),
|
||||
timestamp,
|
||||
)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_kem_psq_key(mut self, key: Arc<x25519::KeyPair>) -> Self {
|
||||
self.kem_psq = Some(key);
|
||||
|
||||
@@ -231,7 +231,7 @@ pub struct LpSession {
|
||||
/// Negotiated protocol version from handshake.
|
||||
/// Set during handshake completion from the ClientHello/ServerHello packet header.
|
||||
/// Used for future version negotiation and compatibility checks.
|
||||
negotiated_version: std::sync::atomic::AtomicU8,
|
||||
negotiated_version: u8,
|
||||
}
|
||||
|
||||
// noiserm
|
||||
@@ -276,18 +276,9 @@ impl LpSession {
|
||||
|
||||
/// Returns the negotiated protocol version from the handshake.
|
||||
///
|
||||
/// Defaults to 1 (current LP version). Set during handshake via
|
||||
/// `set_negotiated_version()` when ClientHello/ServerHello is processed.
|
||||
/// Set during `LpSession` creation after sending / receiving `ClientHelloData`
|
||||
pub fn negotiated_version(&self) -> u8 {
|
||||
self.negotiated_version.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
/// Sets the negotiated protocol version from handshake packet header.
|
||||
///
|
||||
/// Should be called during handshake when processing ClientHello (responder)
|
||||
/// or ServerHello (initiator) to record the agreed protocol version.
|
||||
pub fn set_negotiated_version(&self, version: u8) {
|
||||
self.negotiated_version.store(version, Ordering::Release);
|
||||
self.negotiated_version
|
||||
}
|
||||
|
||||
/// Returns the local X25519 public key.
|
||||
@@ -364,12 +355,14 @@ impl LpSession {
|
||||
/// * `local_peer` - This side's LP peer's keys
|
||||
/// * `remote_peer` - The remote's LP peer's keys
|
||||
/// * `salt` - Salt for PSK derivation
|
||||
/// * `protocol_version` - Protocol version to attach in all `LpPacket`s
|
||||
pub fn new(
|
||||
id: u32,
|
||||
is_initiator: bool,
|
||||
local_peer: LpLocalPeer,
|
||||
remote_peer: LpRemotePeer,
|
||||
salt: &[u8; 32],
|
||||
protocol_version: u8,
|
||||
) -> Result<Self, LpError> {
|
||||
// noiserm
|
||||
// if we're LP responder, we **must** set our kem key
|
||||
@@ -444,13 +437,13 @@ impl LpSession {
|
||||
subsession_counter: AtomicU64::new(0),
|
||||
read_only: AtomicBool::new(false),
|
||||
successor_session_id: Mutex::new(None),
|
||||
negotiated_version: std::sync::atomic::AtomicU8::new(1), // Default to version 1
|
||||
negotiated_version: protocol_version,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn next_packet(&self, message: LpMessage) -> Result<LpPacket, LpError> {
|
||||
let counter = self.next_counter();
|
||||
let header = LpHeader::new(self.id(), counter);
|
||||
let header = LpHeader::new(self.id(), counter, self.negotiated_version);
|
||||
let packet = LpPacket::new(header, message);
|
||||
Ok(packet)
|
||||
}
|
||||
@@ -1275,6 +1268,7 @@ impl LpSession {
|
||||
remote_peer: self.remote_peer.clone(),
|
||||
pq_shared_secret: PqSharedSecret::new(pq_secret),
|
||||
subsession_psk,
|
||||
negotiated_version: self.negotiated_version,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1314,8 +1308,12 @@ pub struct SubsessionHandshake {
|
||||
|
||||
/// PQ shared secret inherited from parent (for creating further subsessions)
|
||||
pq_shared_secret: PqSharedSecret,
|
||||
|
||||
/// Subsession PSK (for deriving outer AEAD key)
|
||||
subsession_psk: [u8; 32],
|
||||
|
||||
/// Negotiated protocol version from handshake.
|
||||
negotiated_version: u8,
|
||||
}
|
||||
|
||||
impl SubsessionHandshake {
|
||||
@@ -1415,7 +1413,7 @@ impl SubsessionHandshake {
|
||||
read_only: AtomicBool::new(false),
|
||||
successor_session_id: Mutex::new(None),
|
||||
// Inherit parent's protocol version
|
||||
negotiated_version: std::sync::atomic::AtomicU8::new(1),
|
||||
negotiated_version: self.negotiated_version,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1423,6 +1421,7 @@ impl SubsessionHandshake {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::packet::version;
|
||||
use crate::peer::mock_peers;
|
||||
use crate::{replay::ReplayError, sessions_for_tests};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
@@ -1448,8 +1447,15 @@ mod tests {
|
||||
let salt = [0u8; 32]; // Test salt
|
||||
|
||||
// PSQ will derive the PSK during handshake using X25519 as DHKEM
|
||||
let session = LpSession::new(receiver_index, is_initiator, local, remote.clone(), &salt)
|
||||
.expect("Test session creation failed");
|
||||
let session = LpSession::new(
|
||||
receiver_index,
|
||||
is_initiator,
|
||||
local,
|
||||
remote.clone(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.expect("Test session creation failed");
|
||||
|
||||
// Initialize KKT state to Completed for tests (bypasses KKT exchange)
|
||||
// This simulates having already received the remote party's KEM key via KKT
|
||||
@@ -2112,14 +2118,28 @@ mod tests {
|
||||
let receiver_index: u32 = 55555;
|
||||
let salt = [0u8; 32];
|
||||
|
||||
let initiator_session =
|
||||
LpSession::new(receiver_index, true, init.clone(), bad_resp, &salt).unwrap();
|
||||
let initiator_session = LpSession::new(
|
||||
receiver_index,
|
||||
true,
|
||||
init.clone(),
|
||||
bad_resp,
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Initialize KKT state for test
|
||||
initiator_session.set_kkt_completed_for_test(resp.x25519.public_key());
|
||||
|
||||
let responder_session =
|
||||
LpSession::new(receiver_index, false, resp, bad_init, &salt).unwrap();
|
||||
let responder_session = LpSession::new(
|
||||
receiver_index,
|
||||
false,
|
||||
resp,
|
||||
bad_init,
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
// Initialize KKT state for test
|
||||
responder_session.set_kkt_completed_for_test(init.x25519.public_key());
|
||||
|
||||
@@ -2154,13 +2174,27 @@ mod tests {
|
||||
let receiver_index: u32 = 66666;
|
||||
let salt = [0u8; 32];
|
||||
|
||||
let initiator_session =
|
||||
LpSession::new(receiver_index, true, init.clone(), resp.as_remote(), &salt).unwrap();
|
||||
let initiator_session = LpSession::new(
|
||||
receiver_index,
|
||||
true,
|
||||
init.clone(),
|
||||
resp.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
// Initialize KKT state for test
|
||||
initiator_session.set_kkt_completed_for_test(resp.x25519.public_key());
|
||||
|
||||
let responder_session =
|
||||
LpSession::new(receiver_index, false, resp, bad_init, &salt).unwrap();
|
||||
let responder_session = LpSession::new(
|
||||
receiver_index,
|
||||
false,
|
||||
resp,
|
||||
bad_init,
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
// Initialize KKT state for test
|
||||
responder_session.set_kkt_completed_for_test(init.x25519.public_key());
|
||||
|
||||
|
||||
@@ -58,11 +58,25 @@ mod tests {
|
||||
|
||||
// 4. Create sessions using the pre-built Noise states
|
||||
let peer_a_sm = session_manager_1
|
||||
.create_session_state_machine(receiver_index, true, a.clone(), b.as_remote(), &salt)
|
||||
.create_session_state_machine(
|
||||
receiver_index,
|
||||
true,
|
||||
a.clone(),
|
||||
b.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.expect("Failed to create session A");
|
||||
|
||||
let peer_b_sm = session_manager_2
|
||||
.create_session_state_machine(receiver_index, false, b.clone(), a.as_remote(), &salt)
|
||||
.create_session_state_machine(
|
||||
receiver_index,
|
||||
false,
|
||||
b.clone(),
|
||||
a.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.expect("Failed to create session B");
|
||||
|
||||
// Verify session count
|
||||
@@ -481,11 +495,25 @@ mod tests {
|
||||
let salt = [43u8; 32];
|
||||
|
||||
let peer_a_sm = session_manager_1
|
||||
.create_session_state_machine(receiver_index, true, a.clone(), b.as_remote(), &salt)
|
||||
.create_session_state_machine(
|
||||
receiver_index,
|
||||
true,
|
||||
a.clone(),
|
||||
b.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.expect("Failed to create session A");
|
||||
|
||||
let peer_b_sm = session_manager_2
|
||||
.create_session_state_machine(receiver_index, false, b.clone(), a.as_remote(), &salt)
|
||||
.create_session_state_machine(
|
||||
receiver_index,
|
||||
false,
|
||||
b.clone(),
|
||||
a.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.expect("Failed to create session B");
|
||||
|
||||
// Initialize KKT state for both sessions (test bypass)
|
||||
@@ -659,7 +687,14 @@ mod tests {
|
||||
|
||||
// 2. Create a session (using real noise state)
|
||||
let _session = session_manager
|
||||
.create_session_state_machine(receiver_index, true, a.clone(), b.as_remote(), &salt)
|
||||
.create_session_state_machine(
|
||||
receiver_index,
|
||||
true,
|
||||
a.clone(),
|
||||
b.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.expect("Failed to create session");
|
||||
|
||||
// 3. Try to get a non-existent session
|
||||
@@ -682,6 +717,7 @@ mod tests {
|
||||
a.clone(),
|
||||
b.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.expect("Failed to create temp session");
|
||||
|
||||
@@ -733,6 +769,7 @@ mod tests {
|
||||
}
|
||||
// Remove unused imports if SessionManager methods are no longer direct dependencies
|
||||
// use crate::noise_protocol::{create_noise_state, create_noise_state_responder};
|
||||
use crate::packet::version;
|
||||
use crate::peer::mock_peers;
|
||||
use crate::state_machine::LpData;
|
||||
use crate::{
|
||||
@@ -772,12 +809,26 @@ mod tests {
|
||||
// 3. Create sessions state machines
|
||||
assert!(
|
||||
session_manager_1
|
||||
.create_session_state_machine(receiver_index, true, a.clone(), b.as_remote(), &salt,) // Initiator
|
||||
.create_session_state_machine(
|
||||
receiver_index,
|
||||
true,
|
||||
a.clone(),
|
||||
b.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT
|
||||
) // Initiator
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
session_manager_2
|
||||
.create_session_state_machine(receiver_index, false, b, a.as_remote(), &salt,) // Responder
|
||||
.create_session_state_machine(
|
||||
receiver_index,
|
||||
false,
|
||||
b,
|
||||
a.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT
|
||||
) // Responder
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
|
||||
@@ -172,8 +172,16 @@ impl SessionManager {
|
||||
local_peer: LpLocalPeer,
|
||||
remote_peer: LpRemotePeer,
|
||||
salt: &[u8; 32],
|
||||
protocol_version: u8,
|
||||
) -> Result<u32, LpError> {
|
||||
let sm = LpStateMachine::new(receiver_index, is_initiator, local_peer, remote_peer, salt)?;
|
||||
let sm = LpStateMachine::new(
|
||||
receiver_index,
|
||||
is_initiator,
|
||||
local_peer,
|
||||
remote_peer,
|
||||
salt,
|
||||
protocol_version,
|
||||
)?;
|
||||
|
||||
self.state_machines.insert(receiver_index, sm);
|
||||
Ok(receiver_index)
|
||||
@@ -204,6 +212,7 @@ impl SessionManager {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::packet::version;
|
||||
use crate::peer::{mock_peers, random_peer};
|
||||
use nym_test_utils::helpers::deterministic_rng;
|
||||
|
||||
@@ -218,7 +227,14 @@ mod tests {
|
||||
let receiver_index: u32 = 1001;
|
||||
|
||||
let sm_1_id = manager
|
||||
.create_session_state_machine(receiver_index, true, local, peer1.as_remote(), &salt)
|
||||
.create_session_state_machine(
|
||||
receiver_index,
|
||||
true,
|
||||
local,
|
||||
peer1.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let retrieved = manager.state_machine_exists(sm_1_id);
|
||||
@@ -239,7 +255,14 @@ mod tests {
|
||||
let receiver_index: u32 = 2002;
|
||||
|
||||
let sm_1_id = manager
|
||||
.create_session_state_machine(receiver_index, true, local, peer1.as_remote(), &salt)
|
||||
.create_session_state_machine(
|
||||
receiver_index,
|
||||
true,
|
||||
local,
|
||||
peer1.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let removed = manager.remove_state_machine(sm_1_id);
|
||||
@@ -262,15 +285,36 @@ mod tests {
|
||||
let salt = [49u8; 32];
|
||||
|
||||
let sm_1 = manager
|
||||
.create_session_state_machine(3001, true, local.clone(), peer1.as_remote(), &salt)
|
||||
.create_session_state_machine(
|
||||
3001,
|
||||
true,
|
||||
local.clone(),
|
||||
peer1.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let sm_2 = manager
|
||||
.create_session_state_machine(3002, true, local.clone(), peer2.as_remote(), &salt)
|
||||
.create_session_state_machine(
|
||||
3002,
|
||||
true,
|
||||
local.clone(),
|
||||
peer2.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let sm_3 = manager
|
||||
.create_session_state_machine(3003, true, local.clone(), peer3.as_remote(), &salt)
|
||||
.create_session_state_machine(
|
||||
3003,
|
||||
true,
|
||||
local.clone(),
|
||||
peer3.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(manager.session_count(), 3);
|
||||
@@ -298,6 +342,7 @@ mod tests {
|
||||
init,
|
||||
resp.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
);
|
||||
|
||||
assert!(sm.is_ok());
|
||||
|
||||
@@ -256,16 +256,25 @@ impl LpStateMachine {
|
||||
/// * `local_peer` - This side's LP peer's keys
|
||||
/// * `remote_peer` - The remote's LP peer's keys
|
||||
/// * `salt` - Fresh salt for PSK derivation (must be unique per session)
|
||||
/// * `protocol_version` - Protocol version to attach in all `LpPacket`s
|
||||
pub fn new(
|
||||
receiver_index: u32,
|
||||
is_initiator: bool,
|
||||
local_peer: LpLocalPeer,
|
||||
remote_peer: LpRemotePeer,
|
||||
salt: &[u8; 32],
|
||||
protocol_version: u8,
|
||||
) -> Result<Self, LpError> {
|
||||
// Create the session with both Ed25519 (for PSQ auth) and derived X25519 keys (for Noise)
|
||||
// receiver_index is client-proposed, passed through directly
|
||||
let session = LpSession::new(receiver_index, is_initiator, local_peer, remote_peer, salt)?;
|
||||
let session = LpSession::new(
|
||||
receiver_index,
|
||||
is_initiator,
|
||||
local_peer,
|
||||
remote_peer,
|
||||
salt,
|
||||
protocol_version,
|
||||
)?;
|
||||
|
||||
Ok(LpStateMachine {
|
||||
state: LpState::ReadyToHandshake {
|
||||
@@ -1144,6 +1153,7 @@ impl LpStateMachine {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::packet::version;
|
||||
use crate::peer::mock_peers;
|
||||
|
||||
#[test]
|
||||
@@ -1155,8 +1165,14 @@ mod tests {
|
||||
|
||||
let receiver_index: u32 = 77777;
|
||||
|
||||
let initiator_sm =
|
||||
LpStateMachine::new(receiver_index, true, init.clone(), resp.as_remote(), &salt);
|
||||
let initiator_sm = LpStateMachine::new(
|
||||
receiver_index,
|
||||
true,
|
||||
init.clone(),
|
||||
resp.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
);
|
||||
assert!(initiator_sm.is_ok());
|
||||
let initiator_sm = initiator_sm.unwrap();
|
||||
assert!(matches!(
|
||||
@@ -1166,8 +1182,14 @@ mod tests {
|
||||
let init_session = initiator_sm.session().unwrap();
|
||||
assert!(init_session.is_initiator());
|
||||
|
||||
let responder_sm =
|
||||
LpStateMachine::new(receiver_index, false, resp, init.as_remote(), &salt);
|
||||
let responder_sm = LpStateMachine::new(
|
||||
receiver_index,
|
||||
false,
|
||||
resp,
|
||||
init.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
);
|
||||
assert!(responder_sm.is_ok());
|
||||
let responder_sm = responder_sm.unwrap();
|
||||
assert!(matches!(
|
||||
@@ -1196,6 +1218,7 @@ mod tests {
|
||||
init.clone(),
|
||||
resp.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1205,6 +1228,7 @@ mod tests {
|
||||
resp,
|
||||
init.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1389,8 +1413,15 @@ mod tests {
|
||||
let receiver_index: u32 = 99901;
|
||||
|
||||
// Create initiator state machine
|
||||
let mut initiator =
|
||||
LpStateMachine::new(receiver_index, true, init, resp.as_remote(), &salt).unwrap();
|
||||
let mut initiator = LpStateMachine::new(
|
||||
receiver_index,
|
||||
true,
|
||||
init,
|
||||
resp.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Verify initial state
|
||||
assert!(matches!(initiator.state, LpState::ReadyToHandshake { .. }));
|
||||
@@ -1409,8 +1440,15 @@ mod tests {
|
||||
let receiver_index: u32 = 99902;
|
||||
|
||||
// Create responder state machine
|
||||
let mut responder =
|
||||
LpStateMachine::new(receiver_index, false, resp, init.as_remote(), &salt).unwrap();
|
||||
let mut responder = LpStateMachine::new(
|
||||
receiver_index,
|
||||
false,
|
||||
resp,
|
||||
init.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Verify initial state
|
||||
assert!(matches!(responder.state, LpState::ReadyToHandshake { .. }));
|
||||
@@ -1430,12 +1468,25 @@ mod tests {
|
||||
let receiver_index: u32 = 99903;
|
||||
|
||||
// Create both state machines
|
||||
let mut initiator =
|
||||
LpStateMachine::new(receiver_index, true, init.clone(), resp.as_remote(), &salt)
|
||||
.unwrap();
|
||||
let mut initiator = LpStateMachine::new(
|
||||
receiver_index,
|
||||
true,
|
||||
init.clone(),
|
||||
resp.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut responder =
|
||||
LpStateMachine::new(receiver_index, false, resp, init.as_remote(), &salt).unwrap();
|
||||
let mut responder = LpStateMachine::new(
|
||||
receiver_index,
|
||||
false,
|
||||
resp,
|
||||
init.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Step 1: Initiator starts handshake, sends KKT request
|
||||
let init_action = initiator.process_input(LpInput::StartHandshake);
|
||||
@@ -1477,9 +1528,15 @@ mod tests {
|
||||
let receiver_index: u32 = 99904;
|
||||
|
||||
// Create initiator state machine
|
||||
let mut initiator =
|
||||
LpStateMachine::new(receiver_index, true, init.clone(), resp.as_remote(), &salt)
|
||||
.unwrap();
|
||||
let mut initiator = LpStateMachine::new(
|
||||
receiver_index,
|
||||
true,
|
||||
init.clone(),
|
||||
resp.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Start handshake to enter KKTExchange state
|
||||
initiator.process_input(LpInput::StartHandshake);
|
||||
@@ -1499,9 +1556,15 @@ mod tests {
|
||||
let receiver_index: u32 = 99905;
|
||||
|
||||
// Create initiator state machine
|
||||
let mut initiator =
|
||||
LpStateMachine::new(receiver_index, true, init.clone(), resp.as_remote(), &salt)
|
||||
.unwrap();
|
||||
let mut initiator = LpStateMachine::new(
|
||||
receiver_index,
|
||||
true,
|
||||
init.clone(),
|
||||
resp.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Start handshake to enter KKTExchange state
|
||||
initiator.process_input(LpInput::StartHandshake);
|
||||
@@ -1534,10 +1597,25 @@ mod tests {
|
||||
let receiver_index: u32 = 111111;
|
||||
|
||||
// Create state machines - Alice is initiator, Bob is responder
|
||||
let mut alice =
|
||||
LpStateMachine::new(receiver_index, true, a.clone(), b.as_remote(), &salt).unwrap();
|
||||
let mut alice = LpStateMachine::new(
|
||||
receiver_index,
|
||||
true,
|
||||
a.clone(),
|
||||
b.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut bob = LpStateMachine::new(receiver_index, false, b, a.as_remote(), &salt).unwrap();
|
||||
let mut bob = LpStateMachine::new(
|
||||
receiver_index,
|
||||
false,
|
||||
b,
|
||||
a.as_remote(),
|
||||
&salt,
|
||||
version::CURRENT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// --- Complete KKT Exchange ---
|
||||
// Alice starts handshake
|
||||
|
||||
@@ -13,6 +13,8 @@ rand = { workspace = true }
|
||||
rand_distr = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
log = { workspace = true }
|
||||
|
||||
nym-sphinx-acknowledgements = { workspace = true }
|
||||
nym-sphinx-addressing = { workspace = true }
|
||||
@@ -47,11 +49,5 @@ features = ["sync"]
|
||||
|
||||
[features]
|
||||
default = ["sphinx"]
|
||||
sphinx = [
|
||||
"nym-sphinx-params/sphinx",
|
||||
"nym-sphinx-types/sphinx",
|
||||
]
|
||||
outfox = [
|
||||
"nym-sphinx-params/outfox",
|
||||
"nym-sphinx-types/outfox",
|
||||
]
|
||||
sphinx = ["nym-sphinx-params/sphinx", "nym-sphinx-types/sphinx"]
|
||||
outfox = ["nym-sphinx-params/outfox", "nym-sphinx-types/outfox"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#![allow(deprecated)] // silences clippy warning: deprecated associated function `nym_crypto::generic_array::GenericArray::<T, N>::clone_from_slice`: please upgrade to generic-array 1.x - TODO
|
||||
|
||||
pub mod identifier;
|
||||
pub mod key;
|
||||
|
||||
@@ -12,6 +12,7 @@ rand = { workspace = true }
|
||||
bs58 = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
||||
nym-crypto = { workspace = true, features = ["stream_cipher", "rand"] }
|
||||
nym-sphinx-addressing = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![allow(deprecated)] // silences clippy warning: deprecated struct `nym_crypto::generic_array::GenericArray`: please upgrade to generic-array 1.x - TODO
|
||||
pub mod encryption_key;
|
||||
pub mod reply_surb;
|
||||
pub mod requests;
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::{ReplySurbError, ReplySurbWithKeyRotation};
|
||||
use nym_sphinx_addressing::clients::{Recipient, RecipientFormattingError};
|
||||
use nym_sphinx_params::key_rotation::InvalidSphinxKeyRotation;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::mem;
|
||||
use thiserror::Error;
|
||||
@@ -30,7 +31,7 @@ pub enum InvalidAnonymousSenderTagRepresentation {
|
||||
InvalidLength { received: usize, expected: usize },
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
|
||||
pub struct AnonymousSenderTag([u8; SENDER_TAG_SIZE]);
|
||||
|
||||
|
||||
@@ -13,3 +13,4 @@ nym-sphinx-params = { workspace = true }
|
||||
nym-sphinx-types = { workspace = true, features = ["sphinx", "outfox"] }
|
||||
nym-sphinx-anonymous-replies = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
@@ -174,13 +174,15 @@ impl MixPacket {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn into_v2_bytes(self) -> Result<Vec<u8>, MixPacketFormattingError> {
|
||||
pub fn to_v2_bytes(&self) -> Result<Vec<u8>, MixPacketFormattingError> {
|
||||
Ok(std::iter::once(self.packet_type as u8)
|
||||
.chain(std::iter::once(self.key_rotation as u8))
|
||||
.chain(self.next_hop.as_bytes())
|
||||
.chain(self.packet.to_bytes()?)
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: test for serialization and errors!
|
||||
pub fn into_v2_bytes(self) -> Result<Vec<u8>, MixPacketFormattingError> {
|
||||
self.to_v2_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::io;
|
||||
|
||||
use crate::message::{NymMessage, NymMessageError, PaddedMessage, PlainMessage};
|
||||
use nym_crypto::aes::cipher::{KeyIvInit, StreamCipher};
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
@@ -9,6 +11,7 @@ use nym_crypto::symmetric::stream_cipher;
|
||||
use nym_crypto::symmetric::stream_cipher::CipherKey;
|
||||
use nym_sphinx_anonymous_replies::SurbEncryptionKey;
|
||||
use nym_sphinx_anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx_anonymous_replies::requests::SENDER_TAG_SIZE;
|
||||
use nym_sphinx_chunking::ChunkingError;
|
||||
use nym_sphinx_chunking::fragment::Fragment;
|
||||
use nym_sphinx_chunking::reconstruction::MessageReconstructor;
|
||||
@@ -17,8 +20,27 @@ use nym_sphinx_params::{
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
// TODO: should this live in this file?
|
||||
#[derive(Debug)]
|
||||
/// Error when decoding a `ReconstructedMessage` from bytes.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ReconstructedMessageError {
|
||||
#[error("Not enough bytes to decode message: expected at least {expected}, got {received}")]
|
||||
TooShort { expected: usize, received: usize },
|
||||
|
||||
#[error("Invalid sender tag flag: expected 0 or 1, got {0}")]
|
||||
InvalidSenderTagFlag(u8),
|
||||
}
|
||||
|
||||
/// A message that has been reconstructed from sphinx packets.
|
||||
///
|
||||
/// Format:
|
||||
/// This type uses a simple binary encoding for serialization:
|
||||
/// - Without sender_tag: `[0][payload...]`
|
||||
/// - With sender_tag: `[1][16-byte tag][payload...]`
|
||||
///
|
||||
/// The first byte indicates whether a sender tag is present (1) or not (0).
|
||||
/// If present, it is followed by the 16-byte sender tag. The remaining bytes
|
||||
/// are the message payload.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ReconstructedMessage {
|
||||
/// The actual plaintext message that was received.
|
||||
pub message: Vec<u8>,
|
||||
@@ -45,6 +67,71 @@ impl ReconstructedMessage {
|
||||
pub fn into_inner(self) -> (Vec<u8>, Option<AnonymousSenderTag>) {
|
||||
self.into()
|
||||
}
|
||||
|
||||
/// Encodes this message into bytes.
|
||||
///
|
||||
/// Format:
|
||||
/// - Without sender_tag: `[0][payload...]`
|
||||
/// - With sender_tag: `[1][16-byte tag][payload...]`
|
||||
pub fn encode(&self) -> Vec<u8> {
|
||||
match &self.sender_tag {
|
||||
Some(tag) => {
|
||||
let mut buf = Vec::with_capacity(1 + SENDER_TAG_SIZE + self.message.len());
|
||||
buf.push(1);
|
||||
buf.extend_from_slice(&tag.to_bytes());
|
||||
buf.extend_from_slice(&self.message);
|
||||
buf
|
||||
}
|
||||
None => {
|
||||
let mut buf = Vec::with_capacity(1 + self.message.len());
|
||||
buf.push(0);
|
||||
buf.extend_from_slice(&self.message);
|
||||
buf
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decodes a message from bytes.
|
||||
///
|
||||
/// Format:
|
||||
/// - Without sender_tag: `[0][payload...]`
|
||||
/// - With sender_tag: `[1][16-byte tag][payload...]`
|
||||
pub fn decode(bytes: &[u8]) -> Result<Self, ReconstructedMessageError> {
|
||||
if bytes.is_empty() {
|
||||
return Err(ReconstructedMessageError::TooShort {
|
||||
expected: 1,
|
||||
received: 0,
|
||||
});
|
||||
}
|
||||
|
||||
match bytes[0] {
|
||||
0 => Ok(ReconstructedMessage {
|
||||
message: bytes[1..].to_vec(),
|
||||
sender_tag: None,
|
||||
}),
|
||||
1 => {
|
||||
if bytes.len() < 1 + SENDER_TAG_SIZE {
|
||||
return Err(ReconstructedMessageError::TooShort {
|
||||
expected: 1 + SENDER_TAG_SIZE,
|
||||
received: bytes.len(),
|
||||
});
|
||||
}
|
||||
let tag_bytes: [u8; SENDER_TAG_SIZE] = bytes[1..1 + SENDER_TAG_SIZE]
|
||||
.try_into()
|
||||
.expect("slice length verified above");
|
||||
Ok(ReconstructedMessage {
|
||||
message: bytes[1 + SENDER_TAG_SIZE..].to_vec(),
|
||||
sender_tag: Some(AnonymousSenderTag::from_bytes(tag_bytes)),
|
||||
})
|
||||
}
|
||||
flag => Err(ReconstructedMessageError::InvalidSenderTagFlag(flag)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the encoded size of this message.
|
||||
pub fn encoded_size(&self) -> usize {
|
||||
1 + self.sender_tag.map_or(0, |_| SENDER_TAG_SIZE) + self.message.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PlainMessage> for ReconstructedMessage {
|
||||
@@ -75,6 +162,9 @@ pub enum MessageRecoveryError {
|
||||
|
||||
#[error("Failed to recover message fragment - {0}")]
|
||||
FragmentRecoveryError(#[from] ChunkingError),
|
||||
|
||||
#[error("Failed to recover message fragment - {0}")]
|
||||
MessageRecoveryError(#[from] io::Error),
|
||||
}
|
||||
|
||||
pub trait MessageReceiver {
|
||||
@@ -185,3 +275,60 @@ impl MessageReceiver for SphinxMessageReceiver {
|
||||
&mut self.reconstructor
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn encode_decode_without_sender_tag() {
|
||||
let msg = ReconstructedMessage {
|
||||
message: b"hello world".to_vec(),
|
||||
sender_tag: None,
|
||||
};
|
||||
|
||||
let encoded = msg.encode();
|
||||
assert_eq!(encoded[0], 0); // flag byte
|
||||
assert_eq!(&encoded[1..], b"hello world");
|
||||
|
||||
let decoded = ReconstructedMessage::decode(&encoded).unwrap();
|
||||
assert_eq!(decoded.message, b"hello world");
|
||||
assert!(decoded.sender_tag.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_with_sender_tag() {
|
||||
let tag = AnonymousSenderTag::from_bytes([42u8; SENDER_TAG_SIZE]);
|
||||
let msg = ReconstructedMessage {
|
||||
message: b"hello world".to_vec(),
|
||||
sender_tag: Some(tag),
|
||||
};
|
||||
|
||||
let encoded = msg.encode();
|
||||
assert_eq!(encoded[0], 1); // flag byte
|
||||
assert_eq!(&encoded[1..1 + SENDER_TAG_SIZE], &[42u8; SENDER_TAG_SIZE]);
|
||||
assert_eq!(&encoded[1 + SENDER_TAG_SIZE..], b"hello world");
|
||||
|
||||
let decoded = ReconstructedMessage::decode(&encoded).unwrap();
|
||||
assert_eq!(decoded.message, b"hello world");
|
||||
assert_eq!(
|
||||
decoded.sender_tag.unwrap().to_bytes(),
|
||||
[42u8; SENDER_TAG_SIZE]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encoded_size_matches() {
|
||||
let msg_no_tag = ReconstructedMessage {
|
||||
message: b"test".to_vec(),
|
||||
sender_tag: None,
|
||||
};
|
||||
assert_eq!(msg_no_tag.encoded_size(), msg_no_tag.encode().len());
|
||||
|
||||
let msg_with_tag = ReconstructedMessage {
|
||||
message: b"test".to_vec(),
|
||||
sender_tag: Some(AnonymousSenderTag::from_bytes([0u8; SENDER_TAG_SIZE])),
|
||||
};
|
||||
assert_eq!(msg_with_tag.encoded_size(), msg_with_tag.encode().len());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ workspace = true
|
||||
[dependencies]
|
||||
bincode = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
tracing = { workspace = true }
|
||||
|
||||
nym-authenticator-requests = { workspace = true }
|
||||
nym-credentials-interface = { workspace = true }
|
||||
|
||||
@@ -11,10 +11,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
|
||||
pub use lp_messages::{
|
||||
LpDvpnRegistrationRequest, LpMixnetGatewayData, LpMixnetRegistrationRequest,
|
||||
LpRegistrationData, LpRegistrationRequest, LpRegistrationResponse, RegistrationMode,
|
||||
};
|
||||
pub use lp_messages::*;
|
||||
pub use serialisation::BincodeError;
|
||||
|
||||
mod lp_messages;
|
||||
@@ -30,7 +27,7 @@ pub struct NymNodeInformation {
|
||||
pub version: AuthenticatorVersion,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct WireguardConfiguration {
|
||||
#[serde(with = "bs58_x25519_pubkey")]
|
||||
pub public_key: x25519::PublicKey,
|
||||
@@ -45,6 +42,10 @@ pub struct NymNodeLPInformation {
|
||||
pub expected_kem_key_hashes: HashMap<KEM, KEMKeyDigests>,
|
||||
pub expected_signing_key_hashes: HashMap<SignatureScheme, KEMKeyDigests>,
|
||||
pub x25519: x25519::PublicKey,
|
||||
|
||||
/// Supported protocol version of the remote gateway.
|
||||
/// Included in case we have to downgrade our version.
|
||||
pub lp_protocol_version: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
||||
@@ -4,65 +4,22 @@
|
||||
//! LP (Lewes Protocol) registration message types shared between client and gateway.
|
||||
|
||||
use crate::WireguardConfiguration;
|
||||
use crate::dvpn::{
|
||||
LpDvpnRegistrationFinalisation, LpDvpnRegistrationInitialRequest,
|
||||
LpDvpnRegistrationRequestMessage, LpDvpnRegistrationRequestMessageContent,
|
||||
LpDvpnRegistrationResponseMessage, LpDvpnRegistrationResponseMessageContent,
|
||||
RequiresCredentialResponse,
|
||||
};
|
||||
use crate::mixnet::{
|
||||
LpMixnetGatewayData, LpMixnetRegistrationRequestMessage, LpMixnetRegistrationResponseMessage,
|
||||
LpMixnetRegistrationResponseMessageContent,
|
||||
};
|
||||
use crate::serialisation::{BincodeError, BincodeOptions, lp_bincode_serializer};
|
||||
use nym_credentials_interface::{CredentialSpendingData, TicketType};
|
||||
use nym_authenticator_requests::models::BandwidthClaim;
|
||||
use nym_credentials_interface::TicketType;
|
||||
use nym_crypto::aes::cipher::crypto_common::rand_core::{CryptoRng, RngCore};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Registration request sent by client after LP handshake
|
||||
/// Aligned with existing authenticator registration flow
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LpRegistrationRequest {
|
||||
/// Mode specific registration data
|
||||
pub registration_data: LpRegistrationData,
|
||||
|
||||
/// Unix timestamp for replay protection
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
impl LpRegistrationRequest {
|
||||
pub fn mode(&self) -> RegistrationMode {
|
||||
match self.registration_data {
|
||||
LpRegistrationData::Dvpn { .. } => RegistrationMode::Dvpn,
|
||||
LpRegistrationData::Mixnet { .. } => RegistrationMode::Mixnet,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum LpRegistrationData {
|
||||
/// dVPN mode - register as WireGuard peer (most common)
|
||||
Dvpn {
|
||||
data: Box<LpDvpnRegistrationRequest>,
|
||||
},
|
||||
|
||||
/// Mixnet mode - register for mixnet routing via IPR
|
||||
Mixnet { data: LpMixnetRegistrationRequest },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LpDvpnRegistrationRequest {
|
||||
/// Client's WireGuard public key (for dVPN mode)
|
||||
pub wg_public_key: nym_wireguard_types::PeerPublicKey,
|
||||
|
||||
/// Bandwidth credential for payment
|
||||
pub credential: CredentialSpendingData,
|
||||
|
||||
/// Ticket type for bandwidth allocation
|
||||
pub ticket_type: TicketType,
|
||||
|
||||
/// Preshared key to be used for the connection
|
||||
pub psk: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LpMixnetRegistrationRequest {
|
||||
/// Client's ed25519 public key (identity)
|
||||
///
|
||||
/// Used to derive DestinationAddressBytes for ActiveClientsStore lookup.
|
||||
pub client_ed25519_pubkey: ed25519::PublicKey,
|
||||
}
|
||||
use tracing::error;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum RegistrationMode {
|
||||
@@ -73,49 +30,112 @@ pub enum RegistrationMode {
|
||||
Mixnet,
|
||||
}
|
||||
|
||||
/// Gateway data for mixnet mode registration
|
||||
///
|
||||
/// Contains the gateway's identity and sphinx key needed for the client
|
||||
/// to construct its full nym Recipient address.
|
||||
/// Registration request sent by client after LP handshake
|
||||
/// Aligned with existing authenticator registration flow
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LpMixnetGatewayData {
|
||||
/// Gateway's ed25519 identity public key
|
||||
///
|
||||
/// Forms part of the client's nym Recipient address.
|
||||
pub gateway_identity: ed25519::PublicKey,
|
||||
// TODO: what we really need in here is the address of internal IPR
|
||||
pub struct LpRegistrationRequest {
|
||||
/// Mode specific registration data
|
||||
pub registration_data: LpRegistrationRequestData,
|
||||
|
||||
/// Unix timestamp for replay protection
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
impl LpRegistrationRequest {
|
||||
pub fn mode(&self) -> RegistrationMode {
|
||||
match self.registration_data {
|
||||
LpRegistrationRequestData::Dvpn { .. } => RegistrationMode::Dvpn,
|
||||
LpRegistrationRequestData::Mixnet { .. } => RegistrationMode::Mixnet,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum LpRegistrationRequestData {
|
||||
/// dVPN mode - register as WireGuard peer (most common)
|
||||
Dvpn {
|
||||
data: Box<LpDvpnRegistrationRequestMessage>,
|
||||
},
|
||||
|
||||
/// Mixnet mode - register for mixnet routing via IPR
|
||||
Mixnet {
|
||||
data: LpMixnetRegistrationRequestMessage,
|
||||
},
|
||||
}
|
||||
|
||||
/// Registration response from gateway
|
||||
/// Contains GatewayData for compatibility with existing client code
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LpRegistrationResponse {
|
||||
/// Whether registration succeeded
|
||||
pub success: bool,
|
||||
/// The status of this registration after the last received client message
|
||||
pub status: RegistrationStatus,
|
||||
|
||||
/// Error message if registration failed
|
||||
pub error: Option<String>,
|
||||
/// Mode specific registration response
|
||||
pub response_data: LpRegistrationResponseData,
|
||||
}
|
||||
|
||||
/// Gateway configuration data for dVPN mode (WireGuard)
|
||||
/// This matches what WireguardRegistrationResult expects
|
||||
pub gateway_data: Option<WireguardConfiguration>,
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum LpRegistrationResponseData {
|
||||
/// dVPN mode - register as WireGuard peer (most common)
|
||||
Dvpn {
|
||||
data: LpDvpnRegistrationResponseMessage,
|
||||
},
|
||||
|
||||
/// Gateway data for mixnet mode
|
||||
///
|
||||
/// Contains gateway identity and sphinx key needed for nym address construction.
|
||||
/// Only populated for Mixnet mode registrations.
|
||||
pub lp_gateway_data: Option<LpMixnetGatewayData>,
|
||||
/// Mixnet mode - register for mixnet routing via IPR
|
||||
Mixnet {
|
||||
data: LpMixnetRegistrationResponseMessage,
|
||||
},
|
||||
}
|
||||
|
||||
/// Allocated bandwidth in bytes
|
||||
pub allocated_bandwidth: i64,
|
||||
/// Represents the registration status after the last received client message.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum RegistrationStatus {
|
||||
/// The registration has been completed successfully
|
||||
Completed,
|
||||
|
||||
/// The registration has failed
|
||||
Failed,
|
||||
|
||||
/// To complete registration the client needs to send additional data,
|
||||
/// e.g. a credential. it is context dependent.
|
||||
PendingMoreData,
|
||||
}
|
||||
|
||||
impl RegistrationStatus {
|
||||
pub fn is_successful(&self) -> bool {
|
||||
matches!(self, RegistrationStatus::Completed)
|
||||
}
|
||||
|
||||
pub fn is_failed(&self) -> bool {
|
||||
matches!(self, RegistrationStatus::Failed)
|
||||
}
|
||||
|
||||
pub fn is_pending(&self) -> bool {
|
||||
matches!(self, RegistrationStatus::PendingMoreData)
|
||||
}
|
||||
}
|
||||
|
||||
fn current_timestamp() -> u64 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.inspect_err(|_| error!("the current timestamp predates unix epoch!"))
|
||||
.unwrap_or_default()
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
impl LpRegistrationRequest {
|
||||
/// Create a new dVPN registration request
|
||||
pub fn new_dvpn<R>(
|
||||
/// Helper wrapping timestamp extraction
|
||||
fn new(registration_data: LpRegistrationRequestData) -> LpRegistrationRequest {
|
||||
Self {
|
||||
registration_data,
|
||||
timestamp: current_timestamp(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new dVPN registration initialisation request
|
||||
pub fn new_initial_dvpn<R>(
|
||||
rng: &mut R,
|
||||
wg_public_key: nym_wireguard_types::PeerPublicKey,
|
||||
credential: CredentialSpendingData,
|
||||
ticket_type: TicketType,
|
||||
) -> Self
|
||||
where
|
||||
@@ -124,29 +144,32 @@ impl LpRegistrationRequest {
|
||||
let mut psk = [0u8; 32];
|
||||
rng.fill_bytes(&mut psk);
|
||||
|
||||
Self {
|
||||
registration_data: LpRegistrationData::Dvpn {
|
||||
data: Box::new(LpDvpnRegistrationRequest {
|
||||
wg_public_key,
|
||||
credential,
|
||||
ticket_type,
|
||||
psk,
|
||||
}),
|
||||
},
|
||||
#[allow(clippy::expect_used)]
|
||||
timestamp: std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("System time before UNIX epoch")
|
||||
.as_secs(),
|
||||
}
|
||||
Self::new(LpRegistrationRequestData::Dvpn {
|
||||
data: Box::new(LpDvpnRegistrationRequestMessage {
|
||||
content: LpDvpnRegistrationRequestMessageContent::InitialRequest(
|
||||
LpDvpnRegistrationInitialRequest {
|
||||
wg_public_key,
|
||||
psk,
|
||||
ticket_type,
|
||||
},
|
||||
),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_finalise_dvpn(credential: BandwidthClaim) -> Self {
|
||||
Self::new(LpRegistrationRequestData::Dvpn {
|
||||
data: Box::new(LpDvpnRegistrationRequestMessage {
|
||||
content: LpDvpnRegistrationRequestMessageContent::Finalisation(
|
||||
LpDvpnRegistrationFinalisation { credential },
|
||||
),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
/// Validate the request timestamp is within acceptable bounds
|
||||
pub fn validate_timestamp(&self, max_skew_secs: u64) -> bool {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
let now = current_timestamp();
|
||||
|
||||
(now as i64 - self.timestamp as i64).abs() <= max_skew_secs as i64
|
||||
}
|
||||
@@ -164,35 +187,95 @@ impl LpRegistrationRequest {
|
||||
|
||||
impl LpRegistrationResponse {
|
||||
/// Create a success response with GatewayData (for dVPN mode)
|
||||
pub fn success(allocated_bandwidth: i64, gateway_data: WireguardConfiguration) -> Self {
|
||||
pub fn success_dvpn(config: WireguardConfiguration, available_bandwidth: i64) -> Self {
|
||||
Self {
|
||||
success: true,
|
||||
error: None,
|
||||
gateway_data: Some(gateway_data),
|
||||
lp_gateway_data: None,
|
||||
allocated_bandwidth,
|
||||
status: RegistrationStatus::Completed,
|
||||
response_data: LpRegistrationResponseData::Dvpn {
|
||||
data: LpDvpnRegistrationResponseMessage {
|
||||
content: LpDvpnRegistrationResponseMessageContent::CompletedRegistration(
|
||||
dvpn::CompletedRegistrationResponse {
|
||||
config,
|
||||
available_bandwidth,
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a success response for mixnet mode with LpGatewayData
|
||||
pub fn success_mixnet(allocated_bandwidth: i64, lp_gateway_data: LpMixnetGatewayData) -> Self {
|
||||
pub fn success_mixnet(config: LpMixnetGatewayData, available_bandwidth: i64) -> Self {
|
||||
Self {
|
||||
success: true,
|
||||
error: None,
|
||||
gateway_data: None,
|
||||
lp_gateway_data: Some(lp_gateway_data),
|
||||
allocated_bandwidth,
|
||||
status: RegistrationStatus::Completed,
|
||||
response_data: LpRegistrationResponseData::Mixnet {
|
||||
data: LpMixnetRegistrationResponseMessage {
|
||||
content: LpMixnetRegistrationResponseMessageContent::CompletedRegistration(
|
||||
mixnet::CompletedRegistrationResponse {
|
||||
config,
|
||||
available_bandwidth,
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an error response
|
||||
pub fn error(error: String) -> Self {
|
||||
Self {
|
||||
success: false,
|
||||
error: Some(error),
|
||||
gateway_data: None,
|
||||
lp_gateway_data: None,
|
||||
allocated_bandwidth: 0,
|
||||
pub fn error(error: impl Into<String>, mode: RegistrationMode) -> Self {
|
||||
let response_data = match mode {
|
||||
RegistrationMode::Dvpn => LpRegistrationResponseData::Dvpn {
|
||||
data: LpDvpnRegistrationResponseMessage::error(error),
|
||||
},
|
||||
RegistrationMode::Mixnet => LpRegistrationResponseData::Mixnet {
|
||||
data: LpMixnetRegistrationResponseMessage::error(error),
|
||||
},
|
||||
};
|
||||
LpRegistrationResponse {
|
||||
status: RegistrationStatus::Failed,
|
||||
response_data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_dvpn_credential() -> Self {
|
||||
LpRegistrationResponse {
|
||||
status: RegistrationStatus::PendingMoreData,
|
||||
response_data: LpRegistrationResponseData::Dvpn {
|
||||
data: LpDvpnRegistrationResponseMessage {
|
||||
content: LpDvpnRegistrationResponseMessageContent::RequiresCredential(
|
||||
RequiresCredentialResponse,
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_dvpn_response(self) -> Option<LpDvpnRegistrationResponseMessage> {
|
||||
match self.response_data {
|
||||
LpRegistrationResponseData::Dvpn { data } => Some(data),
|
||||
LpRegistrationResponseData::Mixnet { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_mixnet_response(self) -> Option<LpMixnetRegistrationResponseMessage> {
|
||||
match self.response_data {
|
||||
LpRegistrationResponseData::Mixnet { data } => Some(data),
|
||||
LpRegistrationResponseData::Dvpn { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error_message(&self) -> Option<&str> {
|
||||
match &self.response_data {
|
||||
LpRegistrationResponseData::Dvpn { data } => match &data.content {
|
||||
LpDvpnRegistrationResponseMessageContent::RegistrationFailure(response) => {
|
||||
Some(&response.error)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
LpRegistrationResponseData::Mixnet { data } => match &data.content {
|
||||
LpMixnetRegistrationResponseMessageContent::RegistrationFailure(response) => {
|
||||
Some(&response.error)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,16 +290,170 @@ impl LpRegistrationResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub mod dvpn {
|
||||
use crate::WireguardConfiguration;
|
||||
use nym_authenticator_requests::models::BandwidthClaim;
|
||||
use nym_credentials_interface::TicketType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// client
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LpDvpnRegistrationRequestMessage {
|
||||
pub content: LpDvpnRegistrationRequestMessageContent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum LpDvpnRegistrationRequestMessageContent {
|
||||
InitialRequest(LpDvpnRegistrationInitialRequest),
|
||||
Finalisation(LpDvpnRegistrationFinalisation),
|
||||
// in theory, we could also extend it with Bandwidth-related messages,
|
||||
// but that shouldn't really be the responsibility of a Registration client.
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LpDvpnRegistrationInitialRequest {
|
||||
/// Client's WireGuard public key (for dVPN mode)
|
||||
pub wg_public_key: nym_wireguard_types::PeerPublicKey,
|
||||
|
||||
/// Preshared key to be used for the connection
|
||||
pub psk: [u8; 32],
|
||||
|
||||
/// Type of the ticket/gateway we're going to register with
|
||||
pub ticket_type: TicketType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LpDvpnRegistrationFinalisation {
|
||||
/// Ecash credential
|
||||
pub credential: BandwidthClaim,
|
||||
}
|
||||
|
||||
// gateway
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LpDvpnRegistrationResponseMessage {
|
||||
pub content: LpDvpnRegistrationResponseMessageContent,
|
||||
}
|
||||
|
||||
impl LpDvpnRegistrationResponseMessage {
|
||||
pub fn error(error: impl Into<String>) -> Self {
|
||||
LpDvpnRegistrationResponseMessage {
|
||||
content: LpDvpnRegistrationResponseMessageContent::RegistrationFailure(
|
||||
RegistrationFailureResponse {
|
||||
error: error.into(),
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum LpDvpnRegistrationResponseMessageContent {
|
||||
RequiresCredential(RequiresCredentialResponse),
|
||||
CompletedRegistration(CompletedRegistrationResponse),
|
||||
RegistrationFailure(RegistrationFailureResponse),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct CompletedRegistrationResponse {
|
||||
/// Gateway configuration data for dVPN mode (WireGuard)
|
||||
/// This matches what WireguardRegistrationResult expects
|
||||
pub config: WireguardConfiguration,
|
||||
|
||||
/// The bandwidth available to this client,
|
||||
pub available_bandwidth: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct RequiresCredentialResponse;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RegistrationFailureResponse {
|
||||
pub error: String,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod mixnet {
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// client
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LpMixnetRegistrationRequestMessage {
|
||||
pub content: LpMixnetRegistrationRequestContent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LpMixnetRegistrationRequestContent {
|
||||
/// Client's ed25519 public key (identity)
|
||||
///
|
||||
/// Used to derive DestinationAddressBytes for ActiveClientsStore lookup.
|
||||
pub client_ed25519_pubkey: ed25519::PublicKey,
|
||||
}
|
||||
|
||||
// gateway
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LpMixnetRegistrationResponseMessage {
|
||||
pub content: LpMixnetRegistrationResponseMessageContent,
|
||||
}
|
||||
|
||||
impl LpMixnetRegistrationResponseMessage {
|
||||
pub fn error(error: impl Into<String>) -> Self {
|
||||
LpMixnetRegistrationResponseMessage {
|
||||
content: LpMixnetRegistrationResponseMessageContent::RegistrationFailure(
|
||||
RegistrationFailureResponse {
|
||||
error: error.into(),
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum LpMixnetRegistrationResponseMessageContent {
|
||||
CompletedRegistration(CompletedRegistrationResponse),
|
||||
RegistrationFailure(RegistrationFailureResponse),
|
||||
}
|
||||
|
||||
/// Gateway data for mixnet mode registration
|
||||
///
|
||||
/// Contains the gateway's identity and sphinx key needed for the client
|
||||
/// to construct its full nym Recipient address.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct LpMixnetGatewayData {
|
||||
/// Gateway's ed25519 identity public key
|
||||
///
|
||||
/// Forms part of the client's nym Recipient address.
|
||||
pub gateway_identity: ed25519::PublicKey,
|
||||
// TODO: what we really need in here is the address of internal IPR
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CompletedRegistrationResponse {
|
||||
/// Gateway data for mixnet mode
|
||||
///
|
||||
/// Contains gateway identity and sphinx key needed for nym address construction.
|
||||
pub config: LpMixnetGatewayData,
|
||||
|
||||
/// The bandwidth available to this client,
|
||||
pub available_bandwidth: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RegistrationFailureResponse {
|
||||
pub error: String,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_test_utils::helpers::deterministic_rng;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
// ==================== Helper Functions ====================
|
||||
|
||||
fn create_test_gateway_data() -> WireguardConfiguration {
|
||||
use std::net::Ipv6Addr;
|
||||
|
||||
WireguardConfiguration {
|
||||
public_key: nym_crypto::asymmetric::x25519::PublicKey::from(
|
||||
nym_sphinx::PublicKey::from([1u8; 32]),
|
||||
@@ -231,37 +468,60 @@ mod tests {
|
||||
|
||||
// ==================== LpRegistrationResponse Tests ====================
|
||||
|
||||
#[test]
|
||||
fn test_lp_registration_response_success() {
|
||||
let gateway_data = create_test_gateway_data();
|
||||
let allocated_bandwidth = 1_000_000_000;
|
||||
|
||||
let response = LpRegistrationResponse::success(allocated_bandwidth, gateway_data.clone());
|
||||
|
||||
assert!(response.success);
|
||||
assert!(response.error.is_none());
|
||||
assert!(response.gateway_data.is_some());
|
||||
assert_eq!(response.allocated_bandwidth, allocated_bandwidth);
|
||||
|
||||
let returned_gw_data = response
|
||||
.gateway_data
|
||||
.expect("Gateway data should be present in success response");
|
||||
assert_eq!(returned_gw_data.public_key, gateway_data.public_key);
|
||||
assert_eq!(returned_gw_data.private_ipv4, gateway_data.private_ipv4);
|
||||
assert_eq!(returned_gw_data.private_ipv6, gateway_data.private_ipv6);
|
||||
assert_eq!(returned_gw_data.endpoint, gateway_data.endpoint);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lp_registration_response_error() {
|
||||
let error_msg = String::from("Insufficient bandwidth");
|
||||
|
||||
let response = LpRegistrationResponse::error(error_msg.clone());
|
||||
let response_mixnet =
|
||||
LpRegistrationResponse::error(error_msg.clone(), RegistrationMode::Mixnet);
|
||||
let response_dvpn =
|
||||
LpRegistrationResponse::error(error_msg.clone(), RegistrationMode::Dvpn);
|
||||
|
||||
assert!(!response.success);
|
||||
assert_eq!(response.error, Some(error_msg));
|
||||
assert!(response.gateway_data.is_none());
|
||||
assert_eq!(response.allocated_bandwidth, 0);
|
||||
assert!(response_mixnet.status.is_failed());
|
||||
assert!(response_dvpn.status.is_failed());
|
||||
|
||||
// check mixnet
|
||||
let LpRegistrationResponseData::Mixnet { data } = response_mixnet.response_data else {
|
||||
panic!("unexpected response")
|
||||
};
|
||||
|
||||
let LpMixnetRegistrationResponseMessageContent::RegistrationFailure(failure) = data.content
|
||||
else {
|
||||
panic!("unexpected response")
|
||||
};
|
||||
assert_eq!(failure.error, error_msg);
|
||||
|
||||
// check dvpn
|
||||
let LpRegistrationResponseData::Dvpn { data } = response_dvpn.response_data else {
|
||||
panic!("unexpected response")
|
||||
};
|
||||
|
||||
let LpDvpnRegistrationResponseMessageContent::RegistrationFailure(failure) = data.content
|
||||
else {
|
||||
panic!("unexpected response")
|
||||
};
|
||||
assert_eq!(failure.error, error_msg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lp_registration_response_success_dvpn() {
|
||||
let cfg = create_test_gateway_data();
|
||||
let allocated_bandwidth = 500_000_000;
|
||||
|
||||
let response = LpRegistrationResponse::success_dvpn(cfg, allocated_bandwidth);
|
||||
assert!(response.status.is_successful());
|
||||
|
||||
let LpRegistrationResponseData::Dvpn { data } = response.response_data else {
|
||||
panic!("unexpected response")
|
||||
};
|
||||
|
||||
let LpDvpnRegistrationResponseMessageContent::CompletedRegistration(complete) =
|
||||
data.content
|
||||
else {
|
||||
panic!("unexpected response")
|
||||
};
|
||||
assert_eq!(complete.config, cfg);
|
||||
assert_eq!(complete.available_bandwidth, allocated_bandwidth);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -274,17 +534,20 @@ mod tests {
|
||||
};
|
||||
let allocated_bandwidth = 500_000_000;
|
||||
|
||||
let response = LpRegistrationResponse::success_mixnet(allocated_bandwidth, lp_gateway_data);
|
||||
let response =
|
||||
LpRegistrationResponse::success_mixnet(lp_gateway_data.clone(), allocated_bandwidth);
|
||||
assert!(response.status.is_successful());
|
||||
|
||||
assert!(response.success);
|
||||
assert!(response.error.is_none());
|
||||
assert!(response.gateway_data.is_none());
|
||||
assert!(response.lp_gateway_data.is_some());
|
||||
assert_eq!(response.allocated_bandwidth, allocated_bandwidth);
|
||||
let LpRegistrationResponseData::Mixnet { data } = response.response_data else {
|
||||
panic!("unexpected response")
|
||||
};
|
||||
|
||||
let gw_data = response
|
||||
.lp_gateway_data
|
||||
.expect("LpGatewayData should be present");
|
||||
assert_eq!(gw_data.gateway_identity, *valid_key.public_key());
|
||||
let LpMixnetRegistrationResponseMessageContent::CompletedRegistration(complete) =
|
||||
data.content
|
||||
else {
|
||||
panic!("unexpected response")
|
||||
};
|
||||
assert_eq!(complete.config, lp_gateway_data);
|
||||
assert_eq!(complete.available_bandwidth, allocated_bandwidth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ serde = { workspace = true, features = ["derive"] } # for config serialization/d
|
||||
tap = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] }
|
||||
tokio-util = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
nym-bandwidth-controller = { workspace = true }
|
||||
|
||||
@@ -7,6 +7,7 @@ use super::{SocksVersion, RESERVED, SOCKS4_VERSION, SOCKS5_VERSION};
|
||||
use crate::config;
|
||||
use futures::channel::mpsc;
|
||||
use futures::task::{Context, Poll};
|
||||
use futures::SinkExt;
|
||||
use log::*;
|
||||
use nym_client_core::client::inbound_messages::{InputMessage, InputMessageSender};
|
||||
use nym_service_providers_common::interface::{ProviderInterfaceVersion, RequestVersion};
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
|
||||
use crate::proxy_runner::MixProxySender;
|
||||
use bytes::Bytes;
|
||||
use futures::SinkExt;
|
||||
use log::{debug, error};
|
||||
use nym_socks5_requests::{ConnectionId, SocketData};
|
||||
use std::io;
|
||||
|
||||
pub(crate) struct OrderedMessageSender<F, S> {
|
||||
pub(crate) struct OrderedMessageSender<F, S: Send + 'static> {
|
||||
connection_id: ConnectionId,
|
||||
// addresses are provided for better logging
|
||||
local_destination_address: String,
|
||||
@@ -18,7 +19,7 @@ pub(crate) struct OrderedMessageSender<F, S> {
|
||||
mix_message_adapter: F,
|
||||
}
|
||||
|
||||
impl<F, S> OrderedMessageSender<F, S>
|
||||
impl<F, S: Send + 'static> OrderedMessageSender<F, S>
|
||||
where
|
||||
F: Fn(SocketData) -> S,
|
||||
{
|
||||
@@ -55,7 +56,7 @@ where
|
||||
(self.mix_message_adapter)(data)
|
||||
}
|
||||
|
||||
async fn send_message(&self, message: S) {
|
||||
async fn send_message(&mut self, message: S) {
|
||||
if self.mixnet_sender.send(message).await.is_err() {
|
||||
panic!("BatchRealMessageReceiver has stopped receiving!")
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ async fn wait_for_lane(
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run_inbound<F, S>(
|
||||
pub(super) async fn run_inbound<F, S: Send>(
|
||||
mut reader: OwnedReadHalf,
|
||||
mut message_sender: OrderedMessageSender<F, S>,
|
||||
connection_id: ConnectionId,
|
||||
|
||||
@@ -9,6 +9,7 @@ use nym_task::ShutdownTracker;
|
||||
use std::fmt::Debug;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tokio::{net::TcpStream, sync::Notify};
|
||||
use tokio_util::sync::PollSender;
|
||||
|
||||
mod inbound;
|
||||
mod outbound;
|
||||
@@ -35,7 +36,7 @@ impl From<(Vec<u8>, bool)> for ProxyMessage {
|
||||
}
|
||||
}
|
||||
|
||||
pub type MixProxySender<S> = tokio::sync::mpsc::Sender<S>;
|
||||
pub type MixProxySender<S> = PollSender<S>;
|
||||
pub type MixProxyReader<S> = tokio::sync::mpsc::Receiver<S>;
|
||||
|
||||
// TODO: when we finally get to implementing graceful shutdown,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![allow(deprecated)]
|
||||
use aes_gcm::aead::{Aead, Nonce};
|
||||
use aes_gcm::{AeadCore, AeadInPlace, KeyInit};
|
||||
use rand::{thread_rng, CryptoRng, Fill, RngCore};
|
||||
|
||||
@@ -15,6 +15,7 @@ thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "sync"] }
|
||||
tokio-util = { workspace = true, features = ["rt"] }
|
||||
tracing = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
||||
workspace = true
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
// const LANE_CONSIDERED_CLEAR: usize = 10;
|
||||
|
||||
pub type ConnectionId = u64;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum TransmissionLane {
|
||||
General,
|
||||
// we need to treat surb-related requests and responses at higher priority
|
||||
|
||||
@@ -26,7 +26,7 @@ impl From<&PeerControlRequest> for PeerControlRequestTypeV2 {
|
||||
fn from(req: &PeerControlRequest) -> Self {
|
||||
match req {
|
||||
PeerControlRequest::AddPeer { .. } => PeerControlRequestTypeV2::AddPeer,
|
||||
PeerControlRequest::RegisterPeer { .. } => PeerControlRequestTypeV2::AddPeer,
|
||||
PeerControlRequest::AllocatePeerIpPair { .. } => PeerControlRequestTypeV2::AddPeer,
|
||||
PeerControlRequest::RemovePeer { .. } => PeerControlRequestTypeV2::RemovePeer,
|
||||
PeerControlRequest::QueryPeer { .. } => PeerControlRequestTypeV2::QueryPeer,
|
||||
PeerControlRequest::GetClientBandwidthByKey { .. } => {
|
||||
@@ -41,6 +41,7 @@ impl From<&PeerControlRequest> for PeerControlRequestTypeV2 {
|
||||
PeerControlRequest::GetVerifierByIp { ip, .. } => {
|
||||
PeerControlRequestTypeV2::GetVerifierByIp { ip: *ip }
|
||||
}
|
||||
PeerControlRequest::ReleaseIpPair { .. } => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,7 +114,7 @@ impl MockPeerControllerV2 {
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
PeerControlRequest::RegisterPeer { response_tx, .. } => {
|
||||
PeerControlRequest::AllocatePeerIpPair { response_tx, .. } => {
|
||||
response_tx
|
||||
.send(
|
||||
*response
|
||||
@@ -176,6 +177,15 @@ impl MockPeerControllerV2 {
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
PeerControlRequest::ReleaseIpPair { response_tx, .. } => {
|
||||
response_tx
|
||||
.send(
|
||||
*response
|
||||
.downcast()
|
||||
.expect("registered response has mismatched type"),
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ nym-credential-verification = { workspace = true }
|
||||
nym-crypto = { workspace = true, features = ["asymmetric"] }
|
||||
nym-gateway-storage = { workspace = true }
|
||||
nym-gateway-requests = { workspace = true }
|
||||
nym-ip-packet-requests = { workspace = true }
|
||||
nym-authenticator-requests = { workspace = true }
|
||||
nym-metrics = { workspace = true }
|
||||
nym-network-defaults = { workspace = true }
|
||||
nym-task = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::ip_pool::IpPair;
|
||||
|
||||
impl From<IpPair> for nym_authenticator_requests::v6::registration::IpPair {
|
||||
fn from(ip_pair: IpPair) -> Self {
|
||||
nym_authenticator_requests::v6::registration::IpPair {
|
||||
ipv4: ip_pair.ipv4,
|
||||
ipv6: ip_pair.ipv6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IpPair> for nym_authenticator_requests::v5::registration::IpPair {
|
||||
fn from(ip_pair: IpPair) -> Self {
|
||||
nym_authenticator_requests::v5::registration::IpPair {
|
||||
ipv4: ip_pair.ipv4,
|
||||
ipv6: ip_pair.ipv6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IpPair> for nym_authenticator_requests::v4::registration::IpPair {
|
||||
fn from(ip_pair: IpPair) -> Self {
|
||||
nym_authenticator_requests::v4::registration::IpPair {
|
||||
ipv4: ip_pair.ipv4,
|
||||
ipv6: ip_pair.ipv6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl From<nym_authenticator_requests::v6::registration::IpPair> for IpPair {
|
||||
fn from(ip_pair: nym_authenticator_requests::v6::registration::IpPair) -> Self {
|
||||
IpPair {
|
||||
ipv4: ip_pair.ipv4,
|
||||
ipv6: ip_pair.ipv6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nym_authenticator_requests::v5::registration::IpPair> for IpPair {
|
||||
fn from(ip_pair: nym_authenticator_requests::v5::registration::IpPair) -> Self {
|
||||
IpPair {
|
||||
ipv4: ip_pair.ipv4,
|
||||
ipv6: ip_pair.ipv6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nym_authenticator_requests::v4::registration::IpPair> for IpPair {
|
||||
fn from(ip_pair: nym_authenticator_requests::v4::registration::IpPair) -> Self {
|
||||
IpPair {
|
||||
ipv4: ip_pair.ipv4,
|
||||
ipv6: ip_pair.ipv6,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,56 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod compat;
|
||||
|
||||
use defguard_wireguard_rs::host::Peer;
|
||||
use ipnetwork::IpNetwork;
|
||||
use nym_ip_packet_requests::IpPair;
|
||||
use rand::seq::IteratorRandom;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use std::sync::Arc;
|
||||
use std::time::SystemTime;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::time::Instant;
|
||||
use tracing::{trace, warn};
|
||||
|
||||
// helper to convert peer's allocation into an `IpPair`
|
||||
pub fn allocated_ip_pair(peer: &Peer) -> Option<IpPair> {
|
||||
for allowed_ip in &peer.allowed_ips {
|
||||
// Extract IPv4 and IPv6 from peer's allowed_ips
|
||||
if let IpAddr::V4(ipv4) = allowed_ip.address {
|
||||
// Find corresponding IPv6
|
||||
if let Some(ipv6_mask) = peer
|
||||
.allowed_ips
|
||||
.iter()
|
||||
.find(|ip| matches!(ip.address, IpAddr::V6(_)))
|
||||
&& let IpAddr::V6(ipv6) = ipv6_mask.address
|
||||
{
|
||||
return Some(IpPair::new(ipv4, ipv6));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Hash)]
|
||||
pub struct IpPair {
|
||||
pub ipv4: Ipv4Addr,
|
||||
pub ipv6: Ipv6Addr,
|
||||
}
|
||||
|
||||
impl IpPair {
|
||||
pub fn new(ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Self {
|
||||
IpPair { ipv4, ipv6 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for IpPair {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "IPv4: {}, IPv6: {}", self.ipv4, self.ipv6)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the state of an IP allocation
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -83,12 +125,8 @@ impl IpPool {
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
"Initialized IP pool with {} address pairs from {}/{} and {}/{}",
|
||||
"Initialized IP pool with {} address pairs from {ipv4_network}/{ipv4_prefix} and {ipv6_network}/{ipv6_prefix}",
|
||||
allocations.len(),
|
||||
ipv4_network,
|
||||
ipv4_prefix,
|
||||
ipv6_network,
|
||||
ipv6_prefix
|
||||
);
|
||||
|
||||
Ok(IpPool {
|
||||
@@ -98,24 +136,28 @@ impl IpPool {
|
||||
|
||||
/// Allocate a free IP pair from the pool
|
||||
///
|
||||
/// Randomly selects an available IP pair and marks it as allocated.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns `IpPoolError::NoFreeIp` if no IPs are available
|
||||
pub async fn allocate(&self) -> Result<IpPair, IpPoolError> {
|
||||
let mut pool = self.allocations.write().await;
|
||||
|
||||
// Find a free IP and allocate it
|
||||
let assignment_start = Instant::now();
|
||||
let free_ip = pool
|
||||
.iter_mut()
|
||||
.filter(|(_, state)| matches!(state, AllocationState::Free))
|
||||
.choose(&mut rand::thread_rng())
|
||||
.ok_or(IpPoolError::NoFreeIp)?;
|
||||
let taken = assignment_start.elapsed();
|
||||
trace!("assigning free ip pair took {taken:?}");
|
||||
if taken > Duration::from_millis(500) {
|
||||
warn!("assigning free ip pair took {taken:?}");
|
||||
}
|
||||
|
||||
let ip_pair = *free_ip.0;
|
||||
*free_ip.1 = AllocationState::Allocated(SystemTime::now());
|
||||
|
||||
tracing::debug!("Allocated IP pair: {}", ip_pair);
|
||||
tracing::debug!("Allocated IP pair: {ip_pair}");
|
||||
Ok(ip_pair)
|
||||
}
|
||||
|
||||
@@ -126,7 +168,7 @@ impl IpPool {
|
||||
let mut pool = self.allocations.write().await;
|
||||
if let Some(state) = pool.get_mut(&ip_pair) {
|
||||
*state = AllocationState::Free;
|
||||
tracing::debug!("Released IP pair: {}", ip_pair);
|
||||
tracing::debug!("Released IP pair: {ip_pair}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,9 +179,9 @@ impl IpPool {
|
||||
let mut pool = self.allocations.write().await;
|
||||
if let Some(state) = pool.get_mut(&ip_pair) {
|
||||
*state = AllocationState::Allocated(SystemTime::now());
|
||||
tracing::debug!("Marked IP pair as used: {}", ip_pair);
|
||||
tracing::debug!("Marked IP pair as used: {ip_pair}");
|
||||
} else {
|
||||
tracing::warn!("Attempted to mark unknown IP pair as used: {}", ip_pair);
|
||||
tracing::warn!("Attempted to mark unknown IP pair as used: {ip_pair}");
|
||||
}
|
||||
}
|
||||
|
||||
+18
-34
@@ -2,9 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![cfg_attr(not(target_os = "linux"), allow(dead_code))]
|
||||
// #![warn(clippy::pedantic)]
|
||||
// #![warn(clippy::expect_used)]
|
||||
// #![warn(clippy::unwrap_used)]
|
||||
|
||||
use defguard_wireguard_rs::{
|
||||
WGApi, WireguardInterfaceApi, error::WireguardInterfaceError, host::Peer, key::Key,
|
||||
@@ -16,9 +13,6 @@ use std::sync::Arc;
|
||||
use tokio::sync::mpsc::{self, Receiver, Sender};
|
||||
use tracing::error;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use nym_ip_packet_requests::IpPair;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use nym_network_defaults::constants::WG_TUN_BASE_NAME;
|
||||
|
||||
@@ -28,6 +22,7 @@ pub mod peer_controller;
|
||||
pub mod peer_handle;
|
||||
pub mod peer_storage_manager;
|
||||
|
||||
pub use defguard_wireguard_rs::host::Peer as DefguardPeer;
|
||||
pub use error::Error;
|
||||
pub use ip_pool::{IpPool, IpPoolError};
|
||||
pub use nym_wireguard_types::Config as WireguardConfig;
|
||||
@@ -287,6 +282,22 @@ pub async fn start_wireguard(
|
||||
peer_bandwidth_managers.insert(peer.public_key.clone(), (bandwidth_manager, peer.clone()));
|
||||
}
|
||||
|
||||
// Initialize IP pool from configuration
|
||||
info!("Initializing IP pool for WireGuard peer allocation");
|
||||
let ip_pool = IpPool::new(
|
||||
wireguard_data.inner.config().private_ipv4,
|
||||
wireguard_data.inner.config().private_network_prefix_v4,
|
||||
wireguard_data.inner.config().private_ipv6,
|
||||
wireguard_data.inner.config().private_network_prefix_v6,
|
||||
)?;
|
||||
|
||||
// Mark existing peer IPs as used in the pool
|
||||
for peer in &peers {
|
||||
if let Some(ip_pair) = crate::ip_pool::allocated_ip_pair(peer) {
|
||||
ip_pool.mark_used(ip_pair).await;
|
||||
}
|
||||
}
|
||||
|
||||
wg_api.create_interface()?;
|
||||
let interface_config = InterfaceConfiguration {
|
||||
name: ifname.clone(),
|
||||
@@ -295,7 +306,7 @@ pub async fn start_wireguard(
|
||||
wireguard_data.inner.config().private_ipv4,
|
||||
))],
|
||||
port: wireguard_data.inner.config().announced_tunnel_port,
|
||||
peers: peers.clone(), // Clone since we need to use peers later to mark IPs as used
|
||||
peers,
|
||||
mtu: None,
|
||||
};
|
||||
info!(
|
||||
@@ -349,33 +360,6 @@ pub async fn start_wireguard(
|
||||
|
||||
let host = wg_api.read_interface_data()?;
|
||||
|
||||
// Initialize IP pool from configuration
|
||||
info!("Initializing IP pool for WireGuard peer allocation");
|
||||
let ip_pool = IpPool::new(
|
||||
wireguard_data.inner.config().private_ipv4,
|
||||
wireguard_data.inner.config().private_network_prefix_v4,
|
||||
wireguard_data.inner.config().private_ipv6,
|
||||
wireguard_data.inner.config().private_network_prefix_v6,
|
||||
)?;
|
||||
|
||||
// Mark existing peer IPs as used in the pool
|
||||
for peer in &peers {
|
||||
for allowed_ip in &peer.allowed_ips {
|
||||
// Extract IPv4 and IPv6 from peer's allowed_ips
|
||||
if let IpAddr::V4(ipv4) = allowed_ip.address {
|
||||
// Find corresponding IPv6
|
||||
if let Some(ipv6_mask) = peer
|
||||
.allowed_ips
|
||||
.iter()
|
||||
.find(|ip| matches!(ip.address, IpAddr::V6(_)))
|
||||
&& let IpAddr::V6(ipv6) = ipv6_mask.address
|
||||
{
|
||||
ip_pool.mark_used(IpPair::new(ipv4, ipv6)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let wg_api = std::sync::Arc::new(wg_api);
|
||||
let mut controller = PeerController::new(
|
||||
ecash_manager,
|
||||
|
||||
@@ -19,6 +19,7 @@ use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
|
||||
use crate::ip_pool::IpPair;
|
||||
pub use defguard_wireguard_rs::key::Key;
|
||||
|
||||
pub fn mock_peer_controller(
|
||||
@@ -70,7 +71,8 @@ impl From<&Key> for KeyWrapper {
|
||||
#[derive(Hash, PartialOrd, PartialEq, Clone, Debug, Eq)]
|
||||
pub enum PeerControlRequestType {
|
||||
AddPeer { public_key: KeyWrapper },
|
||||
RegisterPeer { public_key: KeyWrapper },
|
||||
AllocatePeerIpPair {},
|
||||
ReleaseIpPair { ip_pair: IpPair },
|
||||
RemovePeer { key: KeyWrapper },
|
||||
QueryPeer { key: KeyWrapper },
|
||||
GetClientBandwidthByKey { key: KeyWrapper },
|
||||
@@ -83,7 +85,8 @@ impl PeerControlRequestType {
|
||||
pub fn peer_key(&self) -> Option<KeyWrapper> {
|
||||
match self {
|
||||
PeerControlRequestType::AddPeer { public_key } => Some(public_key.clone()),
|
||||
PeerControlRequestType::RegisterPeer { public_key } => Some(public_key.clone()),
|
||||
PeerControlRequestType::AllocatePeerIpPair {} => None,
|
||||
PeerControlRequestType::ReleaseIpPair { .. } => None,
|
||||
PeerControlRequestType::RemovePeer { key } => Some(key.clone()),
|
||||
PeerControlRequestType::QueryPeer { key } => Some(key.clone()),
|
||||
PeerControlRequestType::GetClientBandwidthByKey { key } => Some(key.clone()),
|
||||
@@ -104,11 +107,12 @@ impl From<&PeerControlRequest> for PeerControlRequestType {
|
||||
PeerControlRequest::AddPeer { peer, .. } => PeerControlRequestType::AddPeer {
|
||||
public_key: (&peer.public_key).into(),
|
||||
},
|
||||
PeerControlRequest::RegisterPeer {
|
||||
registration_data, ..
|
||||
} => PeerControlRequestType::RegisterPeer {
|
||||
public_key: (®istration_data.public_key).into(),
|
||||
},
|
||||
PeerControlRequest::AllocatePeerIpPair { .. } => {
|
||||
PeerControlRequestType::AllocatePeerIpPair {}
|
||||
}
|
||||
PeerControlRequest::ReleaseIpPair { ip_pair, .. } => {
|
||||
PeerControlRequestType::ReleaseIpPair { ip_pair: *ip_pair }
|
||||
}
|
||||
PeerControlRequest::RemovePeer { key, .. } => {
|
||||
PeerControlRequestType::RemovePeer { key: key.into() }
|
||||
}
|
||||
@@ -166,9 +170,6 @@ pub struct MockPeerControllerState {
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PeerState {
|
||||
/// Has IpPair been allocated to the peer?
|
||||
pub register_success: bool,
|
||||
|
||||
// in the future maybe we could extend it with `ClientBandwidth` information
|
||||
/// Has the client handle been spawned
|
||||
pub add_success: bool,
|
||||
@@ -265,14 +266,13 @@ impl MockPeerController {
|
||||
}
|
||||
response_tx.send_downcasted(response.content)
|
||||
}
|
||||
PeerControlRequest::RegisterPeer { response_tx, .. } => {
|
||||
let key = typ.peer_key_unchecked();
|
||||
let peer = peers_guard.peers.entry(key).or_default();
|
||||
if response.success {
|
||||
peer.register_success = true;
|
||||
}
|
||||
PeerControlRequest::AllocatePeerIpPair { response_tx, .. } => {
|
||||
response_tx.send_downcasted(response.content)
|
||||
}
|
||||
PeerControlRequest::ReleaseIpPair {
|
||||
response_tx,
|
||||
ip_pair: _,
|
||||
} => response_tx.send_downcasted(response.content),
|
||||
PeerControlRequest::RemovePeer { response_tx, .. } => {
|
||||
let key = typ.peer_key_unchecked();
|
||||
if response.success {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::ip_pool::allocated_ip_pair;
|
||||
use crate::{
|
||||
IpPool,
|
||||
error::{Error, Result},
|
||||
@@ -27,14 +28,14 @@ use nym_wireguard_types::{
|
||||
};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::{
|
||||
net::{IpAddr, SocketAddr},
|
||||
net::IpAddr,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
use tokio::sync::{RwLock, mpsc};
|
||||
use tokio_stream::{StreamExt, wrappers::IntervalStream};
|
||||
use tracing::{debug, error, info, trace};
|
||||
|
||||
pub use nym_ip_packet_requests::IpPair;
|
||||
pub use crate::ip_pool::IpPair;
|
||||
|
||||
#[cfg(feature = "mock")]
|
||||
pub mod mock;
|
||||
@@ -43,35 +44,30 @@ pub mod mock;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PeerRegistrationData {
|
||||
pub public_key: Key,
|
||||
pub preshared_key: Option<Key>,
|
||||
pub endpoint: Option<SocketAddr>,
|
||||
pub persistent_keepalive_interval: Option<u16>,
|
||||
pub preshared_key: Key,
|
||||
// pub endpoint: Option<SocketAddr>,
|
||||
// pub persistent_keepalive_interval: Option<u16>,
|
||||
}
|
||||
|
||||
impl PeerRegistrationData {
|
||||
pub fn new(public_key: Key) -> Self {
|
||||
pub fn new(public_key: Key, psk: Key) -> Self {
|
||||
Self {
|
||||
public_key,
|
||||
preshared_key: None,
|
||||
endpoint: None,
|
||||
persistent_keepalive_interval: None,
|
||||
preshared_key: psk,
|
||||
// endpoint: None,
|
||||
// persistent_keepalive_interval: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_preshared_key(mut self, key: Key) -> Self {
|
||||
self.preshared_key = Some(key);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_endpoint(mut self, endpoint: SocketAddr) -> Self {
|
||||
self.endpoint = Some(endpoint);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_keepalive(mut self, interval: u16) -> Self {
|
||||
self.persistent_keepalive_interval = Some(interval);
|
||||
self
|
||||
}
|
||||
//
|
||||
// pub fn with_endpoint(mut self, endpoint: SocketAddr) -> Self {
|
||||
// self.endpoint = Some(endpoint);
|
||||
// self
|
||||
// }
|
||||
//
|
||||
// pub fn with_keepalive(mut self, interval: u16) -> Self {
|
||||
// self.persistent_keepalive_interval = Some(interval);
|
||||
// self
|
||||
// }
|
||||
}
|
||||
|
||||
pub enum PeerControlRequest {
|
||||
@@ -80,10 +76,14 @@ pub enum PeerControlRequest {
|
||||
peer: Peer,
|
||||
response_tx: oneshot::Sender<AddPeerControlResponse>,
|
||||
},
|
||||
/// Register a new peer and allocate IPs from the pool
|
||||
RegisterPeer {
|
||||
registration_data: PeerRegistrationData,
|
||||
response_tx: oneshot::Sender<RegisterPeerControlResponse>,
|
||||
/// Attempt to allocate an IP pair from the pool
|
||||
AllocatePeerIpPair {
|
||||
response_tx: oneshot::Sender<AllocatePeerControlResponse>,
|
||||
},
|
||||
/// Attempt to return an IP pair back to the pool
|
||||
ReleaseIpPair {
|
||||
response_tx: oneshot::Sender<ReleaseIpPairControlResponse>,
|
||||
ip_pair: IpPair,
|
||||
},
|
||||
RemovePeer {
|
||||
key: Key,
|
||||
@@ -114,7 +114,8 @@ pub enum PeerControlRequest {
|
||||
}
|
||||
|
||||
pub type AddPeerControlResponse = Result<()>;
|
||||
pub type RegisterPeerControlResponse = Result<IpPair>;
|
||||
pub type AllocatePeerControlResponse = Result<IpPair>;
|
||||
pub type ReleaseIpPairControlResponse = Result<()>;
|
||||
pub type RemovePeerControlResponse = Result<()>;
|
||||
pub type QueryPeerControlResponse = Result<Option<Peer>>;
|
||||
pub type GetClientBandwidthControlResponse = Result<ClientBandwidth>;
|
||||
@@ -211,6 +212,13 @@ impl PeerController {
|
||||
.remove_wireguard_peer(&key.to_string())
|
||||
.await?;
|
||||
self.bw_storage_managers.remove(key);
|
||||
|
||||
if let Ok(Some(peer)) = self.handle_query_peer_by_key(key).await
|
||||
&& let Some(ip_pair) = allocated_ip_pair(&peer)
|
||||
{
|
||||
self.ip_pool.release(ip_pair).await
|
||||
}
|
||||
|
||||
let ret = self.wg_api.remove_peer(key);
|
||||
if ret.is_err() {
|
||||
nym_metrics::inc!("wg_peer_removal_failed");
|
||||
@@ -294,10 +302,7 @@ impl PeerController {
|
||||
///
|
||||
/// This only allocates IPs - the caller must handle database storage and
|
||||
/// then call AddPeer with a complete Peer struct.
|
||||
async fn handle_register_request(
|
||||
&mut self,
|
||||
_registration_data: PeerRegistrationData,
|
||||
) -> Result<IpPair> {
|
||||
async fn handle_ip_allocation_request(&mut self) -> Result<IpPair> {
|
||||
nym_metrics::inc!("wg_ip_allocation_attempts");
|
||||
|
||||
// Allocate IP pair from pool
|
||||
@@ -308,11 +313,16 @@ impl PeerController {
|
||||
.map_err(|e| Error::IpPool(e.to_string()))?;
|
||||
|
||||
nym_metrics::inc!("wg_ip_allocation_success");
|
||||
tracing::debug!("Allocated IP pair: {}", ip_pair);
|
||||
tracing::debug!("Allocated IP pair: {ip_pair}");
|
||||
|
||||
Ok(ip_pair)
|
||||
}
|
||||
|
||||
/// Return IP pair back to the pool
|
||||
async fn handle_ip_release(&mut self, ip_pair: IpPair) {
|
||||
self.ip_pool.release(ip_pair).await
|
||||
}
|
||||
|
||||
async fn ip_to_key(&self, ip: IpAddr) -> Result<Option<Key>> {
|
||||
Ok(self
|
||||
.bw_storage_managers
|
||||
@@ -477,6 +487,62 @@ impl PeerController {
|
||||
);
|
||||
}
|
||||
|
||||
async fn handle_peer_control_request(&mut self, msg: PeerControlRequest) {
|
||||
match msg {
|
||||
PeerControlRequest::AddPeer { peer, response_tx } => {
|
||||
response_tx.send(self.handle_add_request(&peer).await).ok();
|
||||
}
|
||||
PeerControlRequest::AllocatePeerIpPair { response_tx } => {
|
||||
response_tx
|
||||
.send(self.handle_ip_allocation_request().await)
|
||||
.ok();
|
||||
}
|
||||
PeerControlRequest::ReleaseIpPair {
|
||||
response_tx,
|
||||
ip_pair,
|
||||
} => {
|
||||
self.handle_ip_release(ip_pair).await;
|
||||
response_tx.send(Ok(())).ok();
|
||||
}
|
||||
PeerControlRequest::RemovePeer { key, response_tx } => {
|
||||
response_tx.send(self.remove_peer(&key).await).ok();
|
||||
}
|
||||
PeerControlRequest::QueryPeer { key, response_tx } => {
|
||||
response_tx
|
||||
.send(self.handle_query_peer_by_key(&key).await)
|
||||
.ok();
|
||||
}
|
||||
PeerControlRequest::GetClientBandwidthByKey { key, response_tx } => {
|
||||
response_tx
|
||||
.send(self.handle_get_client_bandwidth_by_key(&key).await)
|
||||
.ok();
|
||||
}
|
||||
PeerControlRequest::GetClientBandwidthByIp { ip, response_tx } => {
|
||||
response_tx
|
||||
.send(self.handle_get_client_bandwidth_by_ip(ip).await)
|
||||
.ok();
|
||||
}
|
||||
PeerControlRequest::GetVerifierByKey {
|
||||
key,
|
||||
credential,
|
||||
response_tx,
|
||||
} => {
|
||||
response_tx
|
||||
.send(self.handle_query_verifier_by_key(&key, *credential).await)
|
||||
.ok();
|
||||
}
|
||||
PeerControlRequest::GetVerifierByIp {
|
||||
ip,
|
||||
credential,
|
||||
response_tx,
|
||||
} => {
|
||||
response_tx
|
||||
.send(self.handle_query_verifier_by_ip(ip, *credential).await)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) {
|
||||
info!("started wireguard peer controller");
|
||||
loop {
|
||||
@@ -504,30 +570,7 @@ impl PeerController {
|
||||
}
|
||||
msg = self.request_rx.recv() => {
|
||||
match msg {
|
||||
Some(PeerControlRequest::AddPeer { peer, response_tx }) => {
|
||||
response_tx.send(self.handle_add_request(&peer).await).ok();
|
||||
}
|
||||
Some(PeerControlRequest::RegisterPeer { registration_data, response_tx }) => {
|
||||
response_tx.send(self.handle_register_request(registration_data).await).ok();
|
||||
}
|
||||
Some(PeerControlRequest::RemovePeer { key, response_tx }) => {
|
||||
response_tx.send(self.remove_peer(&key).await).ok();
|
||||
}
|
||||
Some(PeerControlRequest::QueryPeer { key, response_tx }) => {
|
||||
response_tx.send(self.handle_query_peer_by_key(&key).await).ok();
|
||||
}
|
||||
Some(PeerControlRequest::GetClientBandwidthByKey { key, response_tx }) => {
|
||||
response_tx.send(self.handle_get_client_bandwidth_by_key(&key).await).ok();
|
||||
}
|
||||
Some(PeerControlRequest::GetClientBandwidthByIp { ip, response_tx }) => {
|
||||
response_tx.send(self.handle_get_client_bandwidth_by_ip(ip).await).ok();
|
||||
}
|
||||
Some(PeerControlRequest::GetVerifierByKey { key, credential, response_tx }) => {
|
||||
response_tx.send(self.handle_query_verifier_by_key(&key, *credential).await).ok();
|
||||
}
|
||||
Some(PeerControlRequest::GetVerifierByIp { ip, credential, response_tx }) => {
|
||||
response_tx.send(self.handle_query_verifier_by_ip(ip, *credential).await).ok();
|
||||
}
|
||||
Some(request) => self.handle_peer_control_request(request).await,
|
||||
None => {
|
||||
trace!("PeerController [main loop]: stopping since channel closed");
|
||||
break;
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0.83%
|
||||
0.87%
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
34.648
|
||||
33.087
|
||||
|
||||
@@ -1 +1 @@
|
||||
Thursday, January 15th 2026, 09:44:55 UTC
|
||||
Wednesday, January 28th 2026, 09:28:07 UTC
|
||||
|
||||
@@ -76,6 +76,10 @@ Options:
|
||||
Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet or if it also accepts non-paying clients [env: NYMNODE_ENFORCE_ZK_NYMS=] [possible values: true, false]
|
||||
--mnemonic <MNEMONIC>
|
||||
Custom cosmos wallet mnemonic used for zk-nym redemption. If no value is provided, a fresh mnemonic is going to be generated [env: NYMNODE_MNEMONIC=]
|
||||
--upgrade-mode-attestation-url <UPGRADE_MODE_ATTESTATION_URL>
|
||||
Endpoint to query to retrieve current upgrade mode attestation. This argument should never be set outside testnets and local networks [env: NYMNODE_UPGRADE_MODE_ATTESTATION_URL=]
|
||||
--upgrade-mode-attester-public-key <UPGRADE_MODE_ATTESTER_PUBLIC_KEY>
|
||||
Expected public key of the entity signing the published attestation. This argument should never be set outside testnets and local networks [env: NYMNODE_UPGRADE_MODE_ATTESTER_PUBKEY=]
|
||||
--upstream-exit-policy-url <UPSTREAM_EXIT_POLICY_URL>
|
||||
Specifies the url for an upstream source of the exit policy used by this node [env: NYMNODE_UPSTREAM_EXIT_POLICY=]
|
||||
--open-proxy <OPEN_PROXY>
|
||||
|
||||
@@ -25,12 +25,12 @@
|
||||
| [LiteServer](https://liteserver.nl) | Netherlands | Yes, on by default | Yes | Very reliable Dutch provider. They do allow Relay nodes but for Exit nodes you need to contact them. Always check T&C https://liteserver.nl/legal | 07/2024 |
|
||||
| [Lowendbox](https://lowendbox.com/category/dedicated-servers) | | | | Just an aggregator with good offers | 07/2025 |
|
||||
| [M247](https://m247.com/eu/services/host/dedicated-servers/) | UK, Austria, Br, Sw, Jp, Poland, Fr, USA, Netherlands | Yes | No | nan | 07/2025 |
|
||||
| [Mebilcom](https://www.melbicom.net/dedicatedserver/) | NL, USA, DE, UAE, NG, ESP, IN, IT, FR, LT, SG, BG, LV, PL | nan | No | nan | 07/2025 |
|
||||
| [Mebilcom](https://www.melbicom.net/dedicatedserver/) | NL, US, DE, UAE, NG, ESP, IN, IT, FR, LT, SG, BG, LV, PL | nan | No | nan | 07/2025 |
|
||||
| [Mevspace](https://mevspace.com) | Poland | Yes, on by default | Yes | Flexible Polish providers with 3 DCs in Poland. They do allow Tor Exit nodes but you may need a dedicated server for this. Make sure you open a ticket to check. As of today's date, they have 48h for 1 EUR tariff | 07/2024 |
|
||||
| [Misaka](https://www.misaka.io/) | South Africa | Yes, native support | No | Very Expensive | 05/2024 |
|
||||
| [NiceVPS](https://nicevps.net/) | Netherlands | Yes | nan | nan | 07/2025 |
|
||||
| [Njalla](https://nja.la) | Sweden | Yes | Yes | Privacy vandguards! The biggest VPS 45 is 3 cores only, but it works better than many “larger” servers on the market. | 05/2024 |
|
||||
| [OVH](https://us.ovhcloud.com/bare-metal/rise/rise-3/) | USA, DE, FR, UK, PL, CA | | No | Not all locations always available | 07/2025 |
|
||||
| [OVH](https://us.ovhcloud.com/bare-metal/rise/rise-3/) | USA, DE, FR, UK, PL, CA | | No | Exit nodes not allowed on VPS offering, see their [Service Specific Terms](https://us.ovhcloud.com/legal/service-specific-terms/). Not all locations always available | 09/2025 |
|
||||
| [Oneprovider](https://oneprovider.com/en/dedicated-servers/ipv6) | PL, FR, NL, UA, USA, BG, RO, DK, ESP, NO, CZ, RS, IE, IT, UK, HU, CH, SK, AT, BE, BA, HK, JP, SG, LU, AU, SWE, UAE, BR, CR, MX, GR, CL, MA, AR | Yes | No | nan | 07/2025 |
|
||||
| [PrivateLayer](https://privatelayer.com) | Swiss | Yes | Yes | Slow customer response | 07/2025 |
|
||||
| [Privex](https://www.privex.io/tor-exit-policy/) | USA, Germany, Sweden | Yes | Yes | nan | 07/2025 |
|
||||
|
||||
@@ -49,6 +49,136 @@ This page displays a full list of all the changes during our release cycle from
|
||||
|
||||
<VarInfo />
|
||||
|
||||
## `v2026.2-oscypek`
|
||||
|
||||
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2026.2-oscypek)
|
||||
- [`nym-node`](nodes/nym-node.mdx) version `1.24.0`
|
||||
|
||||
```sh
|
||||
nym-node
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2026-01-27T14:54:15.579821601Z
|
||||
Build Version: 1.24.0
|
||||
Commit SHA: 83bf9dc7cc2b01f65cab671733f2bf6c3abd471d
|
||||
Commit Date: 2026-01-27T15:46:52.000000000+01:00
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.91.1
|
||||
rustc Channel: stable
|
||||
cargo Profile: release
|
||||
```
|
||||
|
||||
### Operators Updates & Tools
|
||||
|
||||
<Callout type="warning" emoji="⚠️">
|
||||
**This release comes with breaking changes - please follow the [steps below](#oscypek-special-update) before upgrading!**
|
||||
|
||||
Secondly, the outcome of [NIP-7: Nym Exit Policy Update - Opening Ports for Steam, Discord & SSH](https://governator.nym.com/proposal/prop-281e9ec1-8e10-4e97-848c-311823e83f61), is added to the [Network tunnel manager (NTM)](https://github.com/nymtech/nym/blob/develop/scripts/nym-node-setup/network-tunnel-manager.sh) and operators are required to rerun the tool on their servers as [documented here](update-nym-exit-policy).
|
||||
</ Callout>
|
||||
|
||||
#### `oscypek` special update
|
||||
|
||||
This release brings changes which would lead into a *foreign constraint bug* if operators just switched binaries and restarted the node. To prevent it we need to do a little `sqlite` tweak on the node database.
|
||||
|
||||
To simplify this, we made **a build in command, which operators must run after getting the new binary, but beofre restarting the node.**
|
||||
|
||||
These are the steps to follow:
|
||||
|
||||
<Steps>
|
||||
###### 1. Get `oscypek` binary
|
||||
- SSH to your machine as root
|
||||
- Navigate to the destination where you have `nym-node` binary
|
||||
- Get the latest binary and provide it with permissions to run
|
||||
```sh
|
||||
curl -L "https://github.com/nymtech/nym/releases/download/nym-binaries-v2026.2-oscypek/nym-node" -o nym-node && \
|
||||
chmod +x nym-node
|
||||
```
|
||||
|
||||
###### 2. Run `debug` command
|
||||
```sh
|
||||
./nym-node debug reset-providers-gateway-dbs --id <ID>
|
||||
```
|
||||
|
||||
###### 3. Restart your node
|
||||
- Restart the `nym-node.service`
|
||||
```sh
|
||||
systemctl restart nym-node
|
||||
```
|
||||
- Additionaly look for starus or serivice journal
|
||||
```sh
|
||||
service nym-node status
|
||||
# or
|
||||
journalctl -u nym-node -f --all
|
||||
```
|
||||
</ Steps>
|
||||
|
||||
#### Update Nym exit policy
|
||||
|
||||
As a result of [NIP-7: Nym Exit Policy Update - Opening Ports for Steam, Discord & SSH](https://governator.nym.com/proposal/prop-281e9ec1-8e10-4e97-848c-311823e83f61), we updated [`network-tunnel-manager.sh` (NTM)](https://github.com/nymtech/nym/blob/develop/scripts/nym-node-setup/network-tunnel-manager.sh). Every operator is required to download and re-run the current version of NTM on the servers hosting Nym nodes.
|
||||
|
||||
These are the steps for the exit policy update, using NTM.
|
||||
|
||||
<Steps>
|
||||
###### 1. Get the new NTM
|
||||
- Download the updated NTM and make executable
|
||||
```sh
|
||||
curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh -o ./network-tunnel-manager.sh && \
|
||||
chmod +x network-tunnel-manager.sh
|
||||
```
|
||||
|
||||
###### 2. Update exit policy
|
||||
- To be sure that your routing is clean, run this command:
|
||||
```sh
|
||||
./network-tunnel-manager.sh complete_networking_configuration
|
||||
```
|
||||
</ Steps>
|
||||
|
||||
### Features
|
||||
|
||||
- [Deriving `Serialize` for `GatewayData`](https://github.com/nymtech/nym/pull/6314): Deriving `Serialize` for gateway data, that will be used by the diagnostic tool in the `vpn-client` repo
|
||||
|
||||
- [DNS static table pre-resolve](https://github.com/nymtech/nym/pull/6297): This PR adds pre-resolve stage that returns addres if we have used static table previously. This ensures that we don't continually suffer the penalty of a lookup timeout, while also allowing for the possibility of going back to the default internal secure resolver if one or more nameservers becomes usable again at a future time.
|
||||
|
||||
- [Add `Copy+Clone` to `nym_api_provider::Config`](https://github.com/nymtech/nym/pull/6296): Add `Copy+Clone` to `nym_client_core::client::topology_control::nym_api_provider::Config`
|
||||
|
||||
- [LP Registration + Telescoping + Gateway Probe Localnet Mode](https://github.com/nymtech/nym/pull/6286): Combines LP registration protocol implementation, adds telescoping/nested sessions support, adds localnet mode for `gateway-probe` testing, integrates KKT & PSQ cryptographic primitives
|
||||
|
||||
- [Minor DNS improvements](https://github.com/nymtech/nym/pull/6283): Increase timeouts back to 10 seconds for overall lookup and 5 seconds per query, gnore unreliable test, remove JIT resolution in http client as it is at best not useful, and at worst increasing timeout
|
||||
|
||||
- [HTTP client without default features](https://github.com/nymtech/nym/pull/6281): Fix compile issue caused when using the http client using `default-features=false`
|
||||
|
||||
- [DNS: reduce number of attempts](https://github.com/nymtech/nym/pull/6278): Reduce number of retry attempts performed by hickory to `0`, define `new_resolver` as infallible, use `ResolverOpts` to build builder
|
||||
|
||||
- [Fallback gateway listener and remove legacy key support](https://github.com/nymtech/nym/pull/6249)
|
||||
|
||||
- [Fix assertion](https://github.com/nymtech/nym/pull/6238)
|
||||
|
||||
- [Initial changes to support extra configurable parameters and to print…](https://github.com/nymtech/nym/pull/6237): This branch adds support for the additional configurable parameters introduced in `nicolas/sdk-param-support-debug` in the nym vpn client branch and also debugging messages to verify that it works
|
||||
|
||||
- [Data Observatory](https://github.com/nymtech/nym/pull/6172): This PR adds the Data Observatory that is:
|
||||
- chain scraper
|
||||
- parses Cosmos SDK messages
|
||||
- parses Cosmwasm messages
|
||||
- stores data in pgsql
|
||||
|
||||
### Bugfix
|
||||
|
||||
- [Downgrade gateway protocol to clients proposed version](https://github.com/nymtech/nym/pull/6377)
|
||||
|
||||
- [Ack fix](https://github.com/nymtech/nym/pull/6364)
|
||||
|
||||
- [Sqlite transaction escalation was causing errors ](https://github.com/nymtech/nym/pull/6299): Getting tickets from credential storage requires a transaction doing a read and then a write. Running registration in parallel was causing sqlite to return errors, because it can't escalate two transactions, only one.
|
||||
|
||||
- [Use proper mixing delay instead of poisson delay in cover traffic](https://github.com/nymtech/nym/pull/6269): Currently the secondary cover traffic loop uses its Poisson process delays instead of a proper mixing delay, this PR fixes that
|
||||
|
||||
### Refactors & Maintenance
|
||||
|
||||
- [Update nix to `v0.30.1`](https://github.com/nymtech/nym/pull/6316)
|
||||
|
||||
- [Rremove repetitive words in comment](https://github.com/nymtech/nym/pull/6313)
|
||||
|
||||
- [Clippy fixes and use fixed rust version from `REQUIRED_RUSTC_VERSION`](https://github.com/nymtech/nym/pull/6295)
|
||||
|
||||
|
||||
## `v2026.1-niolo`
|
||||
|
||||
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2026.1-niolo)
|
||||
@@ -120,7 +250,7 @@ Please, let us know how that worked for you.
|
||||
|
||||
- [`gateway-probe` fixes for run-local](https://github.com/nymtech/nym/pull/6212)
|
||||
|
||||
- [Upgrade mode: VPN adjustments](https://github.com/nymtech/nym/pull/6189): This PR further builds up on [\#6174](https://github.com/nymtech/nym/pull/6174) to include changes required by the VPN-client to fully support the upgrade mode, what is relevant here is that this PR modifies the credential storage to allow it to storage an opaque `emergency credential` that lets it be shared between sessions (if it is still valid) ]
|
||||
- [Upgrade mode: VPN adjustments](https://github.com/nymtech/nym/pull/6189): This PR further builds up on [\#6174](https://github.com/nymtech/nym/pull/6174) to include changes required by the VPN-client to fully support the upgrade mode, what is relevant here is that this PR modifies the credential storage to allow it to storage an opaque `emergency credential` that lets it be shared between sessions (if it is still valid)
|
||||
|
||||
- [Add weighted scoring to NS API](https://github.com/nymtech/nym/pull/6144)
|
||||
|
||||
|
||||
@@ -21,11 +21,11 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](.
|
||||
```sh
|
||||
nym-node
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2025-12-02T16:21:03.251191389Z
|
||||
Build Version: 1.23.0
|
||||
Commit SHA: 46fe1bc8191f42aa27f34743c96e9e9f26453d87
|
||||
Commit Date: 2025-12-02T15:29:30.000000000Z
|
||||
Commit Branch: release/2025.22-niolo
|
||||
Build Timestamp: 2026-01-27T14:54:15.579821601Z
|
||||
Build Version: 1.24.0
|
||||
Commit SHA: 83bf9dc7cc2b01f65cab671733f2bf6c3abd471d
|
||||
Commit Date: 2026-01-27T15:46:52.000000000+01:00
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.91.1
|
||||
rustc Channel: stable
|
||||
cargo Profile: release
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub use crate::node::client_handling::websocket::connection_handler::authenticated::RequestHandlingError;
|
||||
use crate::node::internal_service_providers::authenticator::error::AuthenticatorError;
|
||||
use crate::node::wireguard::GatewayWireguardError;
|
||||
use nym_credential_verification::upgrade_mode::UpgradeModeEnableError;
|
||||
use nym_gateway_stats_storage::error::StatsStorageError;
|
||||
use nym_gateway_storage::error::GatewayStorageError;
|
||||
use nym_ip_packet_router::error::IpPacketRouterError;
|
||||
@@ -12,8 +15,6 @@ use nym_validator_client::ValidatorClientError;
|
||||
use std::net::IpAddr;
|
||||
use thiserror::Error;
|
||||
|
||||
pub use crate::node::client_handling::websocket::connection_handler::authenticated::RequestHandlingError;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GatewayError {
|
||||
#[error("the configured version of the gateway ({config_version}) is incompatible with the binary version ({binary_version})")]
|
||||
@@ -155,6 +156,12 @@ pub enum GatewayError {
|
||||
|
||||
#[error("Invalid SystemTime: {0}")]
|
||||
InvalidSystemTime(#[from] std::time::SystemTimeError),
|
||||
|
||||
#[error(transparent)]
|
||||
UpgradeModeEnable(#[from] UpgradeModeEnableError),
|
||||
|
||||
#[error(transparent)]
|
||||
WireguardFailure(#[from] GatewayWireguardError),
|
||||
}
|
||||
|
||||
impl From<ClientCoreError> for GatewayError {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::node::wireguard::GatewayWireguardError;
|
||||
use ipnetwork::IpNetworkError;
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
use nym_credential_verification::upgrade_mode::UpgradeModeEnableError;
|
||||
@@ -82,9 +83,6 @@ pub enum AuthenticatorError {
|
||||
#[error("internal data corruption: {0}")]
|
||||
InternalDataCorruption(String),
|
||||
|
||||
#[error("peers can't be interacted with anymore")]
|
||||
PeerInteractionStopped,
|
||||
|
||||
#[error("unknown version number")]
|
||||
UnknownVersion,
|
||||
|
||||
@@ -108,6 +106,9 @@ pub enum AuthenticatorError {
|
||||
|
||||
#[error(transparent)]
|
||||
UpgradeModeEnable(#[from] UpgradeModeEnableError),
|
||||
|
||||
#[error(transparent)]
|
||||
WireguardFailure(#[from] GatewayWireguardError),
|
||||
}
|
||||
|
||||
impl AuthenticatorError {
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::node::internal_service_providers::authenticator::{
|
||||
config::Config, error::AuthenticatorError, peer_manager::PeerManager,
|
||||
seen_credential_cache::SeenCredentialCache,
|
||||
config::Config, error::AuthenticatorError, seen_credential_cache::SeenCredentialCache,
|
||||
};
|
||||
use crate::node::wireguard::PeerManager;
|
||||
use defguard_wireguard_rs::net::IpAddrMask;
|
||||
use defguard_wireguard_rs::{host::Peer, key::Key};
|
||||
use futures::StreamExt;
|
||||
@@ -35,7 +35,6 @@ use nym_sphinx::receiver::ReconstructedMessage;
|
||||
use nym_task::ShutdownToken;
|
||||
use nym_wireguard::WireguardGatewayData;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use rand::{prelude::IteratorRandom, thread_rng};
|
||||
use std::cmp::max;
|
||||
use std::{
|
||||
net::IpAddr,
|
||||
@@ -53,15 +52,15 @@ const DEFAULT_REGISTRATION_TIMEOUT_CHECK: Duration = Duration::from_secs(60); //
|
||||
const DEFAULT_WG_CLIENT_BANDWIDTH_THRESHOLD: i64 = 1024 * 1024 * 1024;
|
||||
|
||||
pub(crate) struct RegisteredAndFree {
|
||||
registration_in_progres: PendingRegistrations,
|
||||
free_private_network_ips: PrivateIPs,
|
||||
registration_in_progress: PendingRegistrations,
|
||||
taken_private_network_ips: PrivateIPs,
|
||||
}
|
||||
|
||||
impl RegisteredAndFree {
|
||||
pub(crate) fn new(free_private_network_ips: PrivateIPs) -> Self {
|
||||
pub(crate) fn new() -> Self {
|
||||
RegisteredAndFree {
|
||||
registration_in_progres: Default::default(),
|
||||
free_private_network_ips,
|
||||
registration_in_progress: Default::default(),
|
||||
taken_private_network_ips: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,7 +89,6 @@ pub(crate) struct MixnetListener {
|
||||
impl MixnetListener {
|
||||
pub fn new(
|
||||
config: Config,
|
||||
free_private_network_ips: PrivateIPs,
|
||||
wireguard_gateway_data: WireguardGatewayData,
|
||||
mixnet_client: nym_sdk::mixnet::MixnetClient,
|
||||
upgrade_mode: UpgradeModeDetails,
|
||||
@@ -101,7 +99,7 @@ impl MixnetListener {
|
||||
MixnetListener {
|
||||
config,
|
||||
mixnet_client,
|
||||
registered_and_free: RwLock::new(RegisteredAndFree::new(free_private_network_ips)),
|
||||
registered_and_free: RwLock::new(RegisteredAndFree::new()),
|
||||
peer_manager: PeerManager::new(wireguard_gateway_data),
|
||||
upgrade_mode,
|
||||
ecash_verifier,
|
||||
@@ -134,39 +132,35 @@ impl MixnetListener {
|
||||
async fn remove_stale_registrations(&self) -> Result<(), AuthenticatorError> {
|
||||
let mut registered_and_free = self.registered_and_free.write().await;
|
||||
let registered_values: Vec<_> = registered_and_free
|
||||
.registration_in_progres
|
||||
.registration_in_progress
|
||||
.values()
|
||||
.cloned()
|
||||
.collect();
|
||||
for reg in registered_values {
|
||||
let ip = registered_and_free
|
||||
.free_private_network_ips
|
||||
let timestamp = registered_and_free
|
||||
.taken_private_network_ips
|
||||
.get_mut(®.gateway_data.private_ips)
|
||||
.ok_or(AuthenticatorError::InternalDataCorruption(format!(
|
||||
"IPs {} should be present",
|
||||
reg.gateway_data.private_ips
|
||||
)))?;
|
||||
|
||||
let Some(timestamp) = ip else {
|
||||
registered_and_free
|
||||
.registration_in_progres
|
||||
.remove(®.gateway_data.pub_key());
|
||||
tracing::debug!(
|
||||
"Removed stale registration of {}",
|
||||
reg.gateway_data.pub_key()
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let duration = SystemTime::now().duration_since(*timestamp).map_err(|_| {
|
||||
AuthenticatorError::InternalDataCorruption(
|
||||
"set timestamp shouldn't have been set in the future".to_string(),
|
||||
)
|
||||
})?;
|
||||
if duration > DEFAULT_REGISTRATION_TIMEOUT_CHECK {
|
||||
*ip = None;
|
||||
registered_and_free
|
||||
.registration_in_progres
|
||||
.registration_in_progress
|
||||
.remove(®.gateway_data.pub_key());
|
||||
registered_and_free
|
||||
.taken_private_network_ips
|
||||
.remove(®.gateway_data.private_ips);
|
||||
self.peer_manager
|
||||
.release_ip_pair(reg.gateway_data.private_ips.into())
|
||||
.await?;
|
||||
|
||||
tracing::debug!(
|
||||
"Removed stale registration of {}",
|
||||
reg.gateway_data.pub_key()
|
||||
@@ -187,7 +181,7 @@ impl MixnetListener {
|
||||
let nonce: u64 = fastrand::u64(..);
|
||||
let mut registered_and_free = self.registered_and_free.write().await;
|
||||
if let Some(registration_data) = registered_and_free
|
||||
.registration_in_progres
|
||||
.registration_in_progress
|
||||
.get(&remote_public)
|
||||
{
|
||||
let gateway_data = registration_data.gateway_data.clone();
|
||||
@@ -383,19 +377,18 @@ impl MixnetListener {
|
||||
return Ok((bytes, reply_to));
|
||||
}
|
||||
|
||||
let private_ip_ref = registered_and_free
|
||||
.free_private_network_ips
|
||||
.iter_mut()
|
||||
.filter(|r| r.1.is_none())
|
||||
.choose(&mut thread_rng())
|
||||
.ok_or(AuthenticatorError::NoFreeIp)?;
|
||||
let private_ips = *private_ip_ref.0;
|
||||
// mark it as used, even though it's not final
|
||||
*private_ip_ref.1 = Some(SystemTime::now());
|
||||
let ip_allocation = self.peer_manager.allocate_peer_ip_pair().await?;
|
||||
self.registered_and_free
|
||||
.write()
|
||||
.await
|
||||
.taken_private_network_ips
|
||||
.insert(ip_allocation.into(), SystemTime::now());
|
||||
|
||||
let gateway_data = GatewayClient::new(
|
||||
self.keypair().private_key(),
|
||||
remote_public.inner(),
|
||||
*private_ip_ref.0,
|
||||
ip_allocation.into(),
|
||||
nonce,
|
||||
);
|
||||
let registration_data = latest::registration::RegistrationData {
|
||||
@@ -404,7 +397,7 @@ impl MixnetListener {
|
||||
wg_port: self.config.authenticator.tunnel_announced_port,
|
||||
};
|
||||
registered_and_free
|
||||
.registration_in_progres
|
||||
.registration_in_progress
|
||||
.insert(remote_public, registration_data.clone());
|
||||
let bytes = match AuthenticatorVersion::from(protocol) {
|
||||
AuthenticatorVersion::V1 => {
|
||||
@@ -414,7 +407,7 @@ impl MixnetListener {
|
||||
gateway_data: v1::registration::GatewayClient::new(
|
||||
self.keypair().private_key(),
|
||||
remote_public.inner(),
|
||||
private_ips.ipv4.into(),
|
||||
ip_allocation.ipv4.into(),
|
||||
nonce,
|
||||
),
|
||||
wg_port: registration_data.wg_port,
|
||||
@@ -432,7 +425,7 @@ impl MixnetListener {
|
||||
gateway_data: v2::registration::GatewayClient::new(
|
||||
self.keypair().private_key(),
|
||||
remote_public.inner(),
|
||||
private_ips.ipv4.into(),
|
||||
ip_allocation.ipv4.into(),
|
||||
nonce,
|
||||
),
|
||||
wg_port: registration_data.wg_port,
|
||||
@@ -450,7 +443,7 @@ impl MixnetListener {
|
||||
gateway_data: v3::registration::GatewayClient::new(
|
||||
self.keypair().private_key(),
|
||||
remote_public.inner(),
|
||||
private_ips.ipv4.into(),
|
||||
ip_allocation.ipv4.into(),
|
||||
nonce,
|
||||
),
|
||||
wg_port: registration_data.wg_port,
|
||||
@@ -541,7 +534,7 @@ impl MixnetListener {
|
||||
) -> AuthenticatorHandleResult {
|
||||
let mut registered_and_free = self.registered_and_free.write().await;
|
||||
let registration_data = registered_and_free
|
||||
.registration_in_progres
|
||||
.registration_in_progress
|
||||
.get(&final_message.gateway_client_pub_key())
|
||||
.ok_or(AuthenticatorError::RegistrationNotInProgress)?
|
||||
.clone();
|
||||
@@ -592,11 +585,11 @@ impl MixnetListener {
|
||||
.storage()
|
||||
.remove_wireguard_peer(&public_key)
|
||||
.await?;
|
||||
return Err(e);
|
||||
return Err(e.into());
|
||||
}
|
||||
|
||||
registered_and_free
|
||||
.registration_in_progres
|
||||
.registration_in_progress
|
||||
.remove(&final_message.gateway_client_pub_key());
|
||||
|
||||
let bytes = match AuthenticatorVersion::from(protocol) {
|
||||
@@ -935,7 +928,7 @@ impl MixnetListener {
|
||||
|
||||
// When an incoming mixnet message triggers a response that we send back.
|
||||
async fn handle_response(
|
||||
&self,
|
||||
&mut self,
|
||||
response: Vec<u8>,
|
||||
recipient: Option<Recipient>,
|
||||
sender_tag: Option<AnonymousSenderTag>,
|
||||
|
||||
@@ -3,21 +3,19 @@
|
||||
|
||||
use crate::node::internal_service_providers::authenticator::error::AuthenticatorError;
|
||||
use futures::channel::oneshot;
|
||||
use ipnetwork::IpNetwork;
|
||||
use nym_client_core::{HardcodedTopologyProvider, TopologyProvider};
|
||||
use nym_credential_verification::upgrade_mode::UpgradeModeDetails;
|
||||
use nym_sdk::{mixnet::Recipient, GatewayTransceiver};
|
||||
use nym_task::ShutdownTracker;
|
||||
use nym_wireguard::WireguardGatewayData;
|
||||
use std::{net::IpAddr, path::Path, sync::Arc, time::SystemTime};
|
||||
use std::{path::Path, sync::Arc};
|
||||
|
||||
pub use config::Config;
|
||||
use nym_credential_verification::upgrade_mode::UpgradeModeDetails;
|
||||
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod mixnet_client;
|
||||
pub mod mixnet_listener;
|
||||
mod peer_manager;
|
||||
mod seen_credential_cache;
|
||||
|
||||
pub struct OnStartData {
|
||||
@@ -40,7 +38,6 @@ pub struct Authenticator {
|
||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send + Sync>>,
|
||||
wireguard_gateway_data: WireguardGatewayData,
|
||||
ecash_verifier: Arc<dyn nym_credential_verification::ecash::traits::EcashManager + Send + Sync>,
|
||||
used_private_network_ips: Vec<IpAddr>,
|
||||
shutdown: ShutdownTracker,
|
||||
on_start: Option<oneshot::Sender<OnStartData>>,
|
||||
}
|
||||
@@ -50,7 +47,6 @@ impl Authenticator {
|
||||
config: Config,
|
||||
upgrade_mode_state: UpgradeModeDetails,
|
||||
wireguard_gateway_data: WireguardGatewayData,
|
||||
used_private_network_ips: Vec<IpAddr>,
|
||||
ecash_verifier: Arc<
|
||||
dyn nym_credential_verification::ecash::traits::EcashManager + Send + Sync,
|
||||
>,
|
||||
@@ -64,7 +60,6 @@ impl Authenticator {
|
||||
custom_gateway_transceiver: None,
|
||||
ecash_verifier,
|
||||
wireguard_gateway_data,
|
||||
used_private_network_ips,
|
||||
shutdown,
|
||||
on_start: None,
|
||||
}
|
||||
@@ -135,26 +130,8 @@ impl Authenticator {
|
||||
|
||||
let self_address = *mixnet_client.nym_address();
|
||||
|
||||
let used_private_network_ips =
|
||||
std::collections::BTreeSet::from_iter(self.used_private_network_ips.iter());
|
||||
let private_ip_network = IpNetwork::new(
|
||||
self.config.authenticator.private_ipv4.into(),
|
||||
self.config.authenticator.private_network_prefix_v4,
|
||||
)?;
|
||||
let now = SystemTime::now();
|
||||
let free_private_network_ips = private_ip_network
|
||||
.iter()
|
||||
.map(|ip: IpAddr| {
|
||||
if used_private_network_ips.contains(&ip) {
|
||||
(ip.into(), Some(now))
|
||||
} else {
|
||||
(ip.into(), None)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let mixnet_listener = crate::node::internal_service_providers::authenticator::mixnet_listener::MixnetListener::new(
|
||||
let mixnet_listener = mixnet_listener::MixnetListener::new(
|
||||
self.config,
|
||||
free_private_network_ips,
|
||||
self.wireguard_gateway_data,
|
||||
mixnet_client,
|
||||
self.upgrade_mode_state,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use super::registration::process_registration;
|
||||
use super::LpHandlerState;
|
||||
use super::{LpHandlerState, ReceiverIndex};
|
||||
use crate::error::GatewayError;
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_lp::state_machine::{LpAction, LpData, LpDataKind, LpInput};
|
||||
@@ -12,7 +11,7 @@ use nym_lp::{
|
||||
};
|
||||
use nym_lp_transport::traits::LpTransport;
|
||||
use nym_metrics::{add_histogram_obs, inc};
|
||||
use nym_registration_common::LpRegistrationRequest;
|
||||
use nym_registration_common::{LpRegistrationRequest, RegistrationStatus};
|
||||
use std::net::SocketAddr;
|
||||
use std::time::Duration;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
@@ -293,6 +292,10 @@ where
|
||||
use nym_lp::state_machine::{LpInput, LpStateMachine};
|
||||
let remote = self.remote_addr;
|
||||
|
||||
// if we managed to parse out the header, i.e. we have code supporting whatever
|
||||
// protocol version has been sent - use that one instead
|
||||
let protocol_version = packet.header().protocol_version;
|
||||
|
||||
// Extract ClientHello data
|
||||
let hello_data = match packet.message() {
|
||||
LpMessage::ClientHello(hello_data) => hello_data,
|
||||
@@ -326,8 +329,10 @@ where
|
||||
// Send Collision response to tell client to retry with new receiver_index
|
||||
// No outer key - this is before PSK derivation
|
||||
// Note: Do NOT set binding on collision - client may retry with new receiver_index
|
||||
let collision_packet =
|
||||
LpPacket::new(LpHeader::new(receiver_index, 0), LpMessage::Collision);
|
||||
let collision_packet = LpPacket::new(
|
||||
LpHeader::new(receiver_index, 0, protocol_version),
|
||||
LpMessage::Collision,
|
||||
);
|
||||
self.send_lp_packet(collision_packet, None).await?;
|
||||
|
||||
return Ok(());
|
||||
@@ -345,6 +350,7 @@ where
|
||||
self.state.local_lp_peer.clone(),
|
||||
hello_data.to_remote_peer(),
|
||||
&hello_data.salt,
|
||||
protocol_version,
|
||||
)
|
||||
.map_err(|e| {
|
||||
inc!("lp_client_hello_failed");
|
||||
@@ -375,7 +381,10 @@ where
|
||||
|
||||
// Send Ack to confirm ClientHello received
|
||||
// No outer key - this is before PSK derivation
|
||||
let ack_packet = LpPacket::new(LpHeader::new(receiver_index, 0), LpMessage::Ack);
|
||||
let ack_packet = LpPacket::new(
|
||||
LpHeader::new(receiver_index, 0, protocol_version),
|
||||
LpMessage::Ack,
|
||||
);
|
||||
self.send_lp_packet(ack_packet, None).await?;
|
||||
|
||||
Ok(())
|
||||
@@ -432,13 +441,15 @@ where
|
||||
self.remote_addr, receiver_idx
|
||||
);
|
||||
|
||||
let session = state_entry.value().state.session().map_err(|err| {
|
||||
GatewayError::LpHandshakeError(format!("no session available: {err}"))
|
||||
})?;
|
||||
|
||||
// Get outer key for Ack encryption before releasing borrow
|
||||
let outer_key = state_entry
|
||||
.value()
|
||||
.state
|
||||
.session()
|
||||
.ok()
|
||||
.and_then(|s| s.outer_aead_key());
|
||||
let outer_key = session.outer_aead_key();
|
||||
|
||||
// Get previously negotiated protocol version for header creation
|
||||
let negotiated_version = session.negotiated_version();
|
||||
|
||||
// Move state machine to session_states (already in Transport state)
|
||||
// We keep the state machine (not just session) to enable
|
||||
@@ -462,7 +473,10 @@ where
|
||||
inc!("lp_handshakes_success");
|
||||
|
||||
// Send Ack to confirm handshake completion to the client
|
||||
let ack_packet = LpPacket::new(LpHeader::new(receiver_idx, 0), LpMessage::Ack);
|
||||
let ack_packet = LpPacket::new(
|
||||
LpHeader::new(receiver_idx, 0, negotiated_version),
|
||||
LpMessage::Ack,
|
||||
);
|
||||
trace!(
|
||||
"Moved session {} to transport mode, sending Ack",
|
||||
receiver_idx
|
||||
@@ -742,11 +756,11 @@ where
|
||||
/// Handle registration request on an established session
|
||||
async fn handle_registration_request(
|
||||
&mut self,
|
||||
receiver_idx: u32,
|
||||
receiver_idx: ReceiverIndex,
|
||||
request: LpRegistrationRequest,
|
||||
) -> Result<(), GatewayError> {
|
||||
// Process registration (might modify state)
|
||||
let response = process_registration(request, &self.state).await;
|
||||
let response = self.state.process_registration(receiver_idx, request).await;
|
||||
let response_bytes = response.serialise().map_err(|e| {
|
||||
GatewayError::LpProtocolError(format!("Failed to serialize response: {e}"))
|
||||
})?;
|
||||
@@ -754,13 +768,23 @@ where
|
||||
self.send_response_packet(receiver_idx, response_bytes, LpDataKind::Registration)
|
||||
.await?;
|
||||
|
||||
if response.success {
|
||||
info!("LP registration successful for {})", self.remote_addr);
|
||||
} else {
|
||||
warn!(
|
||||
"LP registration failed for {}: {:?}",
|
||||
self.remote_addr, response.error
|
||||
);
|
||||
match response.status {
|
||||
RegistrationStatus::Completed => {
|
||||
info!("LP registration successful for {}", self.remote_addr);
|
||||
}
|
||||
RegistrationStatus::Failed => {
|
||||
warn!(
|
||||
"LP registration failed for {}: {:?}",
|
||||
self.remote_addr,
|
||||
response.error_message()
|
||||
);
|
||||
}
|
||||
RegistrationStatus::PendingMoreData => {
|
||||
info!(
|
||||
"we required more deta from {} to complete registration",
|
||||
self.remote_addr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1220,15 +1244,23 @@ where
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::node::lp_listener::{LpConfig, LpDebug};
|
||||
use crate::node::wireguard::PeerManager;
|
||||
use crate::node::ActiveClientsStore;
|
||||
use bytes::BytesMut;
|
||||
use nym_credential_verification::upgrade_mode::{
|
||||
UpgradeModeCheckConfig, UpgradeModeCheckRequestSender, UpgradeModeDetails,
|
||||
};
|
||||
use nym_credential_verification::UpgradeModeState;
|
||||
use nym_lp::codec::{parse_lp_packet, serialize_lp_packet};
|
||||
use nym_lp::message::{ClientHelloData, EncryptedDataPayload, HandshakeData, LpMessage};
|
||||
use nym_lp::packet::{LpHeader, LpPacket};
|
||||
use nym_lp::peer::LpLocalPeer;
|
||||
use nym_wireguard::{PeerControlRequest, WireguardConfig, WireguardGatewayData};
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use std::sync::Arc;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
// ==================== Test Helpers ====================
|
||||
|
||||
/// Create a minimal test state for handler tests
|
||||
@@ -1236,6 +1268,23 @@ mod tests {
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
fn wireguard_data(
|
||||
keys: Arc<x25519::KeyPair>,
|
||||
) -> (WireguardGatewayData, Receiver<PeerControlRequest>) {
|
||||
// some sensible default values (ports don't matter anyway)
|
||||
let cfg = WireguardConfig {
|
||||
bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 51822),
|
||||
private_ipv4: Ipv4Addr::new(10, 1, 0, 1),
|
||||
private_ipv6: Ipv6Addr::new(0xfc01, 0, 0, 0, 0, 0, 0, 0x1), // fc01::1,
|
||||
announced_tunnel_port: 51822,
|
||||
announced_metadata_port: 51830,
|
||||
private_network_prefix_v4: 16,
|
||||
private_network_prefix_v6: 112,
|
||||
};
|
||||
|
||||
WireguardGatewayData::new(cfg, keys)
|
||||
}
|
||||
|
||||
// Create in-memory storage for testing
|
||||
let storage = nym_gateway_storage::GatewayStorage::init(":memory:", 100)
|
||||
.await
|
||||
@@ -1262,6 +1311,20 @@ mod tests {
|
||||
let id_keys = Arc::new(ed25519::KeyPair::new(&mut OsRng));
|
||||
let x_keys = Arc::new(id_keys.to_x25519());
|
||||
|
||||
let (wireguard_data, _) = wireguard_data(x_keys.clone());
|
||||
|
||||
let (um_recheck_tx, _) = futures::channel::mpsc::unbounded();
|
||||
|
||||
let upgrade_mode_state = UpgradeModeState::new(*id_keys.public_key());
|
||||
let upgrade_mode_details = UpgradeModeDetails::new(
|
||||
UpgradeModeCheckConfig {
|
||||
// essentially we never want to trigger this in our tests
|
||||
min_staleness_recheck: Duration::from_nanos(1),
|
||||
},
|
||||
UpgradeModeCheckRequestSender::new(um_recheck_tx),
|
||||
upgrade_mode_state.clone(),
|
||||
);
|
||||
|
||||
let lp_peer = LpLocalPeer::new(id_keys, x_keys.clone()).with_kem_psq_key(x_keys);
|
||||
|
||||
LpHandlerState {
|
||||
@@ -1272,12 +1335,13 @@ mod tests {
|
||||
local_lp_peer: lp_peer,
|
||||
metrics: nym_node_metrics::NymNodeMetrics::default(),
|
||||
active_clients_store: ActiveClientsStore::new(),
|
||||
wg_peer_controller: None,
|
||||
wireguard_data: None,
|
||||
upgrade_mode: upgrade_mode_details,
|
||||
outbound_mix_sender: mix_sender,
|
||||
handshake_states: Arc::new(dashmap::DashMap::new()),
|
||||
session_states: Arc::new(dashmap::DashMap::new()),
|
||||
registrations_in_progress: Default::default(),
|
||||
forward_semaphore,
|
||||
peer_manager: Arc::new(PeerManager::new(wireguard_data)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+189
-119
@@ -68,30 +68,36 @@
|
||||
// They can be exported via Prometheus format using the metrics endpoint.
|
||||
|
||||
use crate::error::GatewayError;
|
||||
use crate::node::lp_listener::registration::RegistrationsInProgress;
|
||||
use crate::node::ActiveClientsStore;
|
||||
use dashmap::DashMap;
|
||||
use nym_config::serde_helpers::de_maybe_port;
|
||||
use nym_credential_verification::ecash::traits::EcashManager;
|
||||
use nym_credential_verification::upgrade_mode::UpgradeModeDetails;
|
||||
use nym_gateway_storage::GatewayStorage;
|
||||
use nym_lp::state_machine::LpStateMachine;
|
||||
pub use nym_mixnet_client::forwarder::{
|
||||
mix_forwarding_channels, MixForwardingReceiver, MixForwardingSender,
|
||||
};
|
||||
use nym_node_metrics::NymNodeMetrics;
|
||||
use nym_task::ShutdownTracker;
|
||||
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::{mpsc, Semaphore};
|
||||
use tokio::sync::Semaphore;
|
||||
use tracing::*;
|
||||
|
||||
use crate::node::wireguard::PeerManager;
|
||||
pub use nym_lp::peer::LpLocalPeer;
|
||||
pub use nym_mixnet_client::forwarder::{
|
||||
mix_forwarding_channels, MixForwardingReceiver, MixForwardingSender,
|
||||
};
|
||||
pub use nym_wireguard::{PeerControlRequest, WireguardGatewayData};
|
||||
|
||||
mod data_handler;
|
||||
pub mod handler;
|
||||
mod registration;
|
||||
|
||||
pub type ReceiverIndex = u32;
|
||||
|
||||
/// Configuration for LP listener
|
||||
#[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(default)]
|
||||
@@ -111,7 +117,7 @@ pub struct LpConfig {
|
||||
pub announce_control_port: Option<u16>,
|
||||
|
||||
/// Custom announced port for listening for the UDP LP data traffic.
|
||||
/// If unspecified, the value from the `data_bind_address` will be used instead
|
||||
/// If unspecified, the value from the `data_bind_address` will be used instead
|
||||
/// (default: None)
|
||||
#[serde(deserialize_with = "de_maybe_port")]
|
||||
pub announce_data_port: Option<u16>,
|
||||
@@ -178,6 +184,10 @@ pub struct LpDebug {
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub demoted_session_ttl: Duration,
|
||||
|
||||
/// Maximum age of in-progress dVPN registration before cleanup (default: 60s)
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub pending_registration_ttl: Duration,
|
||||
|
||||
/// How often to run the state cleanup task (default: 5 minutes)
|
||||
///
|
||||
/// The cleanup task scans for and removes stale handshakes and sessions.
|
||||
@@ -247,6 +257,9 @@ impl LpDebug {
|
||||
// 5 minutes - balances memory reclamation with task overhead
|
||||
pub const DEFAULT_STATE_CLEANUP_INTERVAL: Duration = Duration::from_secs(300);
|
||||
|
||||
// 1 minute - enough for client to send retrieve credential from its storage and send it across
|
||||
pub const DEFAULT_PENDING_REGISTRATION_TTL: Duration = Duration::from_secs(60);
|
||||
|
||||
// Limits concurrent outbound connections to prevent fd exhaustion
|
||||
pub const DEFAULT_MAX_CONCURRENT_FORWARDS: usize = 1000;
|
||||
}
|
||||
@@ -260,6 +273,7 @@ impl Default for LpDebug {
|
||||
handshake_ttl: Self::DEFAULT_HANDSHAKE_TTL,
|
||||
session_ttl: Self::DEFAULT_SESSION_TTL,
|
||||
demoted_session_ttl: Self::DEFAULT_DEMOTED_SESSION_TTL,
|
||||
pending_registration_ttl: Self::DEFAULT_PENDING_REGISTRATION_TTL,
|
||||
state_cleanup_interval: Self::DEFAULT_STATE_CLEANUP_INTERVAL,
|
||||
max_concurrent_forwards: Self::DEFAULT_MAX_CONCURRENT_FORWARDS,
|
||||
}
|
||||
@@ -311,12 +325,12 @@ impl<T> TimestampedState<T> {
|
||||
}
|
||||
|
||||
/// Get age since creation
|
||||
pub fn age(&self) -> std::time::Duration {
|
||||
pub fn age(&self) -> Duration {
|
||||
self.created_at.elapsed()
|
||||
}
|
||||
|
||||
/// Get time since last activity
|
||||
pub fn since_activity(&self) -> std::time::Duration {
|
||||
pub fn since_activity(&self) -> Duration {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
@@ -332,8 +346,7 @@ impl<T> TimestampedState<T> {
|
||||
#[derive(Clone)]
|
||||
pub struct LpHandlerState {
|
||||
/// Ecash verifier for bandwidth credentials
|
||||
pub ecash_verifier:
|
||||
Arc<dyn nym_credential_verification::ecash::traits::EcashManager + Send + Sync>,
|
||||
pub ecash_verifier: Arc<dyn EcashManager + Send + Sync>,
|
||||
|
||||
/// Storage backend for persistence
|
||||
pub storage: GatewayStorage,
|
||||
@@ -347,11 +360,12 @@ pub struct LpHandlerState {
|
||||
/// Active clients tracking
|
||||
pub active_clients_store: ActiveClientsStore,
|
||||
|
||||
/// WireGuard peer controller channel (for dVPN registrations)
|
||||
pub wg_peer_controller: Option<mpsc::Sender<PeerControlRequest>>,
|
||||
/// Current state of the Upgrade Mode as perceived by this gateway
|
||||
pub upgrade_mode: UpgradeModeDetails,
|
||||
|
||||
/// WireGuard gateway data (contains keypair and config)
|
||||
pub wireguard_data: Option<WireguardGatewayData>,
|
||||
/// alongside helpers for managing peers
|
||||
pub peer_manager: Arc<PeerManager>,
|
||||
|
||||
/// LP configuration (for timestamp validation, etc.)
|
||||
pub lp_config: LpConfig,
|
||||
@@ -369,7 +383,7 @@ pub struct LpHandlerState {
|
||||
/// state moves to session_states map.
|
||||
///
|
||||
/// Wrapped in TimestampedState for TTL-based cleanup of stale handshakes.
|
||||
pub handshake_states: Arc<DashMap<u32, TimestampedState<LpStateMachine>>>,
|
||||
pub handshake_states: Arc<DashMap<ReceiverIndex, TimestampedState<LpStateMachine>>>,
|
||||
|
||||
/// Established sessions keyed by session_id
|
||||
///
|
||||
@@ -383,7 +397,11 @@ pub struct LpHandlerState {
|
||||
/// subsession/rekeying support. The state machine handles subsession initiation
|
||||
/// (SubsessionKK1/KK2/Ready) during transport phase, allowing long-lived connections
|
||||
/// to rekey without re-authentication.
|
||||
pub session_states: Arc<DashMap<u32, TimestampedState<LpStateMachine>>>,
|
||||
pub session_states: Arc<DashMap<ReceiverIndex, TimestampedState<LpStateMachine>>>,
|
||||
|
||||
/// In-progress dVPN registrations that require additional data (e.g. credentials)
|
||||
/// to finalise.
|
||||
pub registrations_in_progress: RegistrationsInProgress,
|
||||
|
||||
/// Semaphore limiting concurrent forward connections
|
||||
///
|
||||
@@ -559,30 +577,32 @@ impl LpListener {
|
||||
///
|
||||
/// The task automatically stops when the shutdown signal is received.
|
||||
fn spawn_state_cleanup_task(&self) -> tokio::task::JoinHandle<()> {
|
||||
let peer_manager = Arc::clone(&self.handler_state.peer_manager);
|
||||
let handshake_states = Arc::clone(&self.handler_state.handshake_states);
|
||||
let session_states = Arc::clone(&self.handler_state.session_states);
|
||||
let pending_registrations = self.handler_state.registrations_in_progress.clone();
|
||||
let dbg_cfg = self.handler_state.lp_config.debug;
|
||||
|
||||
let handshake_ttl = dbg_cfg.handshake_ttl;
|
||||
let session_ttl = dbg_cfg.session_ttl;
|
||||
let demoted_session_ttl = dbg_cfg.demoted_session_ttl;
|
||||
let pending_reg_ttl = dbg_cfg.pending_registration_ttl;
|
||||
let interval = dbg_cfg.state_cleanup_interval;
|
||||
let shutdown = self.shutdown.clone_shutdown_token();
|
||||
let metrics = self.handler_state.metrics.clone();
|
||||
|
||||
info!(
|
||||
"Starting LP state cleanup task (handshake_ttl={}s, session_ttl={}s, demoted_ttl={}s, interval={}s)",
|
||||
handshake_ttl.as_secs(), session_ttl.as_secs(), demoted_session_ttl.as_secs(), interval.as_secs()
|
||||
"Starting LP state cleanup task (handshake_ttl={}s, session_ttl={}s, demoted_ttl={}s, reg_ttl={}s, interval={}s)",
|
||||
handshake_ttl.as_secs(), session_ttl.as_secs(), demoted_session_ttl.as_secs(),pending_reg_ttl.as_secs(), interval.as_secs()
|
||||
);
|
||||
|
||||
self.shutdown.try_spawn_named(
|
||||
Self::cleanup_loop(
|
||||
cleanup_task::cleanup_loop(
|
||||
peer_manager,
|
||||
handshake_states,
|
||||
session_states,
|
||||
handshake_ttl,
|
||||
session_ttl,
|
||||
demoted_session_ttl,
|
||||
interval,
|
||||
pending_registrations,
|
||||
dbg_cfg,
|
||||
shutdown,
|
||||
metrics,
|
||||
),
|
||||
@@ -590,104 +610,6 @@ impl LpListener {
|
||||
)
|
||||
}
|
||||
|
||||
/// Background loop for cleaning up stale state entries
|
||||
///
|
||||
/// Runs periodically to scan handshake_states and session_states maps,
|
||||
/// removing entries that have exceeded their TTL.
|
||||
///
|
||||
/// Demoted sessions (ReadOnlyTransport) use shorter TTL since they
|
||||
/// only need to drain in-flight packets after subsession promotion.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn cleanup_loop(
|
||||
handshake_states: Arc<DashMap<u32, TimestampedState<LpStateMachine>>>,
|
||||
session_states: Arc<DashMap<u32, TimestampedState<LpStateMachine>>>,
|
||||
handshake_ttl: Duration,
|
||||
session_ttl: Duration,
|
||||
demoted_session_ttl: Duration,
|
||||
interval: Duration,
|
||||
shutdown: nym_task::ShutdownToken,
|
||||
_metrics: NymNodeMetrics,
|
||||
) {
|
||||
use nym_lp::state_machine::LpStateBare;
|
||||
use nym_metrics::inc_by;
|
||||
|
||||
let mut cleanup_interval = tokio::time::interval(interval);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
|
||||
_ = shutdown.cancelled() => {
|
||||
debug!("LP state cleanup task: received shutdown signal");
|
||||
break;
|
||||
}
|
||||
|
||||
_ = cleanup_interval.tick() => {
|
||||
let start = std::time::Instant::now();
|
||||
let mut hs_removed = 0u64;
|
||||
let mut ss_removed = 0u64;
|
||||
let mut demoted_removed = 0u64;
|
||||
|
||||
// Remove stale handshakes (based on age since creation)
|
||||
handshake_states.retain(|_, timestamped| {
|
||||
if timestamped.age() > handshake_ttl {
|
||||
hs_removed += 1;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
// Remove stale sessions (based on time since last activity)
|
||||
// Use shorter TTL for demoted (ReadOnlyTransport) sessions
|
||||
session_states.retain(|_, timestamped| {
|
||||
let is_demoted = timestamped.state.bare_state() == LpStateBare::ReadOnlyTransport;
|
||||
let ttl = if is_demoted {
|
||||
demoted_session_ttl
|
||||
} else {
|
||||
session_ttl
|
||||
};
|
||||
|
||||
if timestamped.since_activity() > ttl {
|
||||
if is_demoted {
|
||||
demoted_removed += 1;
|
||||
} else {
|
||||
ss_removed += 1;
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
if hs_removed > 0 || ss_removed > 0 || demoted_removed > 0 {
|
||||
let duration = start.elapsed();
|
||||
info!(
|
||||
"LP state cleanup: removed {} handshakes, {} sessions, {} demoted (took {:.3}s)",
|
||||
hs_removed,
|
||||
ss_removed,
|
||||
demoted_removed,
|
||||
duration.as_secs_f64()
|
||||
);
|
||||
|
||||
// Track metrics
|
||||
if hs_removed > 0 {
|
||||
inc_by!("lp_states_cleanup_handshake_removed", hs_removed as i64);
|
||||
}
|
||||
if ss_removed > 0 {
|
||||
inc_by!("lp_states_cleanup_session_removed", ss_removed as i64);
|
||||
}
|
||||
if demoted_removed > 0 {
|
||||
inc_by!("lp_states_cleanup_demoted_removed", demoted_removed as i64);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("LP state cleanup task shutdown complete");
|
||||
}
|
||||
|
||||
fn active_lp_connections(&self) -> usize {
|
||||
self.handler_state
|
||||
.metrics
|
||||
@@ -695,3 +617,151 @@ impl LpListener {
|
||||
.active_lp_connections_count()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod cleanup_task {
|
||||
use crate::node::lp_listener::registration::RegistrationsInProgress;
|
||||
use crate::node::lp_listener::{LpDebug, TimestampedState};
|
||||
use crate::node::wireguard::PeerManager;
|
||||
use dashmap::DashMap;
|
||||
use nym_lp::state_machine::LpStateBare;
|
||||
use nym_lp::LpStateMachine;
|
||||
use nym_metrics::inc_by;
|
||||
use nym_node_metrics::NymNodeMetrics;
|
||||
use std::sync::Arc;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
async fn perform_cleanup(
|
||||
peer_manager: &PeerManager,
|
||||
handshake_states: &Arc<DashMap<u32, TimestampedState<LpStateMachine>>>,
|
||||
session_states: &Arc<DashMap<u32, TimestampedState<LpStateMachine>>>,
|
||||
registrations_in_progress: &RegistrationsInProgress,
|
||||
cfg: LpDebug,
|
||||
) {
|
||||
let handshake_ttl = cfg.handshake_ttl;
|
||||
let session_ttl = cfg.session_ttl;
|
||||
let demoted_session_ttl = cfg.demoted_session_ttl;
|
||||
let pending_registration_ttl = cfg.pending_registration_ttl;
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
let mut hs_removed = 0u64;
|
||||
let mut ss_removed = 0u64;
|
||||
let mut pending_reg_removed = 0u64;
|
||||
let mut demoted_removed = 0u64;
|
||||
|
||||
// Remove stale handshakes (based on age since creation)
|
||||
handshake_states.retain(|_, timestamped| {
|
||||
if timestamped.age() > handshake_ttl {
|
||||
hs_removed += 1;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
// Remove stale sessions (based on time since last activity)
|
||||
// Use shorter TTL for demoted (ReadOnlyTransport) sessions
|
||||
session_states.retain(|_, timestamped| {
|
||||
let is_demoted = timestamped.state.bare_state() == LpStateBare::ReadOnlyTransport;
|
||||
let ttl = if is_demoted {
|
||||
demoted_session_ttl
|
||||
} else {
|
||||
session_ttl
|
||||
};
|
||||
|
||||
if timestamped.since_activity() > ttl {
|
||||
if is_demoted {
|
||||
demoted_removed += 1;
|
||||
} else {
|
||||
ss_removed += 1;
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
// Remove stale registrations (based on time since last activity)
|
||||
let mut reg_guard = registrations_in_progress.lock().await;
|
||||
let mut stale_registrations = Vec::new();
|
||||
for (k, timestamped) in reg_guard.iter() {
|
||||
if timestamped.age() > pending_registration_ttl {
|
||||
stale_registrations.push(*k)
|
||||
}
|
||||
}
|
||||
|
||||
for to_remove in stale_registrations {
|
||||
pending_reg_removed += 1;
|
||||
|
||||
// SAFETY: we never dropped the guard and the entry existed
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let entry = reg_guard.remove(&to_remove).unwrap();
|
||||
if let Err(err) = peer_manager
|
||||
.release_ip_pair(entry.state.allocated_ip_pair())
|
||||
.await
|
||||
{
|
||||
error!("failed to release allocated ip pair: {err}")
|
||||
}
|
||||
}
|
||||
|
||||
if hs_removed > 0 || ss_removed > 0 || demoted_removed > 0 || pending_reg_removed > 0 {
|
||||
let duration = start.elapsed();
|
||||
info!(
|
||||
"LP state cleanup: removed {hs_removed} handshakes, {pending_reg_removed} pending registrations, {ss_removed} sessions, {demoted_removed} demoted (took {:.3}s)",
|
||||
duration.as_secs_f64()
|
||||
);
|
||||
|
||||
// Track metrics
|
||||
if hs_removed > 0 {
|
||||
inc_by!("lp_states_cleanup_handshake_removed", hs_removed as i64);
|
||||
}
|
||||
if ss_removed > 0 {
|
||||
inc_by!("lp_states_cleanup_session_removed", ss_removed as i64);
|
||||
}
|
||||
if demoted_removed > 0 {
|
||||
inc_by!("lp_states_cleanup_demoted_removed", demoted_removed as i64);
|
||||
}
|
||||
if pending_reg_removed > 0 {
|
||||
inc_by!(
|
||||
"lp_states_cleanup_pending_registrations_removed",
|
||||
pending_reg_removed as i64
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Background loop for cleaning up stale state entries
|
||||
///
|
||||
/// Runs periodically to scan handshake_states and session_states maps,
|
||||
/// removing entries that have exceeded their TTL.
|
||||
///
|
||||
/// Demoted sessions (ReadOnlyTransport) use shorter TTL since they
|
||||
/// only need to drain in-flight packets after subsession promotion.
|
||||
pub(crate) async fn cleanup_loop(
|
||||
peer_manager: Arc<PeerManager>,
|
||||
handshake_states: Arc<DashMap<u32, TimestampedState<LpStateMachine>>>,
|
||||
session_states: Arc<DashMap<u32, TimestampedState<LpStateMachine>>>,
|
||||
registrations_in_progress: RegistrationsInProgress,
|
||||
cfg: LpDebug,
|
||||
shutdown: nym_task::ShutdownToken,
|
||||
_metrics: NymNodeMetrics,
|
||||
) {
|
||||
let interval = cfg.state_cleanup_interval;
|
||||
|
||||
let mut cleanup_interval = tokio::time::interval(interval);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.cancelled() => {
|
||||
debug!("LP state cleanup task: received shutdown signal");
|
||||
break;
|
||||
}
|
||||
_ = cleanup_interval.tick() => {
|
||||
perform_cleanup(&peer_manager, &handshake_states, &session_states, ®istrations_in_progress, cfg).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("LP state cleanup task shutdown complete");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use super::LpHandlerState;
|
||||
use super::{LpHandlerState, ReceiverIndex, TimestampedState};
|
||||
use crate::error::GatewayError;
|
||||
use crate::node::client_handling::websocket::message_receiver::IsActive;
|
||||
use defguard_wireguard_rs::host::Peer;
|
||||
use defguard_wireguard_rs::key::Key;
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use nym_authenticator_requests::models::BandwidthClaim;
|
||||
use nym_credential_verification::ecash::traits::EcashManager;
|
||||
use nym_credential_verification::{
|
||||
bandwidth_storage_manager::BandwidthStorageManager, BandwidthFlushingBehaviourConfig,
|
||||
ClientBandwidth, CredentialVerifier,
|
||||
};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_credentials_interface::{BandwidthCredential, CredentialSpendingData, TicketType};
|
||||
use nym_crypto::asymmetric::encryption::KeyPair;
|
||||
use nym_gateway_requests::models::CredentialSpendingRequest;
|
||||
use nym_gateway_storage::models::PersistedBandwidth;
|
||||
use nym_gateway_storage::traits::BandwidthGatewayStorage;
|
||||
use nym_metrics::{add_histogram_obs, inc, inc_by};
|
||||
use nym_registration_common::{
|
||||
LpDvpnRegistrationRequest, LpMixnetGatewayData, LpMixnetRegistrationRequest,
|
||||
LpRegistrationData, LpRegistrationRequest, LpRegistrationResponse, WireguardConfiguration,
|
||||
use nym_registration_common::dvpn::{
|
||||
LpDvpnRegistrationFinalisation, LpDvpnRegistrationInitialRequest,
|
||||
LpDvpnRegistrationRequestMessage, LpDvpnRegistrationRequestMessageContent,
|
||||
};
|
||||
use nym_wireguard::PeerControlRequest;
|
||||
use nym_registration_common::mixnet::LpMixnetRegistrationRequestMessage;
|
||||
use nym_registration_common::{
|
||||
LpRegistrationRequest, LpRegistrationRequestData, LpRegistrationResponse, RegistrationMode,
|
||||
RegistrationStatus, WireguardConfiguration,
|
||||
};
|
||||
use nym_wireguard::peer_controller::IpPair;
|
||||
use nym_wireguard::WireguardConfig;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use std::collections::HashMap;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::{Mutex, MutexGuard};
|
||||
use tracing::*;
|
||||
|
||||
// Histogram buckets for LP registration duration tracking
|
||||
@@ -56,6 +64,439 @@ const WG_CONTROLLER_LATENCY_BUCKETS: &[f64] = &[
|
||||
2.0, // 2s
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PendingRegistrationState {
|
||||
client_id: i64,
|
||||
peer_key: PeerPublicKey,
|
||||
ticket_type: TicketType,
|
||||
wireguard_config: WireguardConfiguration,
|
||||
}
|
||||
|
||||
impl PendingRegistrationState {
|
||||
pub(crate) fn allocated_ip_pair(&self) -> IpPair {
|
||||
IpPair::new(
|
||||
self.wireguard_config.private_ipv4,
|
||||
self.wireguard_config.private_ipv6,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct RegistrationsInProgress {
|
||||
/// Wrapped in TimestampedState for TTL-based cleanup of stale data.
|
||||
inner: Arc<Mutex<HashMap<ReceiverIndex, TimestampedState<PendingRegistrationState>>>>,
|
||||
}
|
||||
|
||||
impl RegistrationsInProgress {
|
||||
pub async fn lock(
|
||||
&self,
|
||||
) -> MutexGuard<'_, HashMap<ReceiverIndex, TimestampedState<PendingRegistrationState>>> {
|
||||
self.inner.lock().await
|
||||
}
|
||||
}
|
||||
|
||||
impl LpHandlerState {
|
||||
fn upgrade_mode_enabled(&self) -> bool {
|
||||
self.upgrade_mode.enabled()
|
||||
}
|
||||
|
||||
fn keypair(&self) -> &Arc<KeyPair> {
|
||||
self.peer_manager.wireguard_gateway_data.keypair()
|
||||
}
|
||||
|
||||
fn wireguard_config(&self) -> WireguardConfig {
|
||||
self.peer_manager.wireguard_gateway_data.config()
|
||||
}
|
||||
|
||||
fn successful_dvpn_registration(
|
||||
&self,
|
||||
peer_private_ipv4: Ipv4Addr,
|
||||
peer_private_ipv6: Ipv6Addr,
|
||||
bandwidth: i64,
|
||||
) -> LpRegistrationResponse {
|
||||
LpRegistrationResponse::success_dvpn(
|
||||
WireguardConfiguration {
|
||||
public_key: *self.keypair().public_key(),
|
||||
// TODO: according to @SW this is most likely very wrong
|
||||
endpoint: self.wireguard_config().bind_address,
|
||||
private_ipv4: peer_private_ipv4,
|
||||
private_ipv6: peer_private_ipv6,
|
||||
},
|
||||
bandwidth,
|
||||
)
|
||||
}
|
||||
|
||||
/// Check if WG peer already registered, return cached response if so.
|
||||
///
|
||||
/// This enables idempotent registration: if a client retries registration
|
||||
/// with the same WG public key (e.g., after network failure), we return
|
||||
/// the existing registration data instead of re-processing. This prevents
|
||||
/// wasting credentials on network issues.
|
||||
async fn check_existing_dvpn_registration(
|
||||
&self,
|
||||
public_key: PeerPublicKey,
|
||||
) -> Option<LpRegistrationResponse> {
|
||||
// Look up existing peer
|
||||
let Ok(maybe_peer) = self.peer_manager.query_peer(public_key).await else {
|
||||
return Some(LpRegistrationResponse::error(
|
||||
"iternal failure: failed to resolve peer information",
|
||||
RegistrationMode::Dvpn,
|
||||
));
|
||||
};
|
||||
|
||||
let peer = maybe_peer?;
|
||||
|
||||
// Extract IPv4 and IPv6 from allowed_ips
|
||||
let mut private_ipv4 = None;
|
||||
let mut private_ipv6 = None;
|
||||
for ip_mask in &peer.allowed_ips {
|
||||
match ip_mask.address {
|
||||
IpAddr::V4(v4) => private_ipv4 = Some(v4),
|
||||
IpAddr::V6(v6) => private_ipv6 = Some(v6),
|
||||
}
|
||||
if private_ipv4.is_some() && private_ipv6.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Incomplete data, treat as new registration
|
||||
let (Some(private_ipv4), Some(private_ipv6)) = (private_ipv4, private_ipv6) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
// Get current bandwidth
|
||||
let Ok(bandwidth) = self.peer_manager.query_client_bandwidth(public_key).await else {
|
||||
return Some(LpRegistrationResponse::error(
|
||||
"iternal failure: failed to resolve peer bandwidth",
|
||||
RegistrationMode::Dvpn,
|
||||
));
|
||||
};
|
||||
|
||||
Some(self.successful_dvpn_registration(
|
||||
private_ipv4,
|
||||
private_ipv6,
|
||||
bandwidth.available().await,
|
||||
))
|
||||
}
|
||||
|
||||
/// In the case of an already registered WG peer, update its PSK.
|
||||
async fn update_peer_psk(&self, peer: PeerPublicKey, psk: Key) -> Result<(), GatewayError> {
|
||||
let encoded_psk = psk.to_lower_hex();
|
||||
self.storage
|
||||
.update_peer_psk(&peer.to_string(), Some(&encoded_psk))
|
||||
.await?;
|
||||
|
||||
// TODO: do we have to go through a peer manager to also update PSK if a peer is currently active?
|
||||
// seems like an edge case. maybe we should force disconnect here?
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn process_dvpn_initial_registration(
|
||||
&self,
|
||||
sender: ReceiverIndex,
|
||||
request: LpDvpnRegistrationInitialRequest,
|
||||
) -> LpRegistrationResponse {
|
||||
let wg_key_str = request.wg_public_key.to_string();
|
||||
|
||||
// check for an existing registration (same WG key already registered)
|
||||
// This allows clients to retry registration after network failures
|
||||
// or to re-use gateway without spending additional bandwidth
|
||||
if let Some(existing_response) = self
|
||||
.check_existing_dvpn_registration(request.wg_public_key)
|
||||
.await
|
||||
{
|
||||
// if there already exists registration for this client, update the psk and return the peer data
|
||||
if let Err(err) = self
|
||||
.update_peer_psk(request.wg_public_key, Key::new(request.psk))
|
||||
.await
|
||||
{
|
||||
return LpRegistrationResponse::error(
|
||||
format!("WireGuard peer PSK update failed: {err}"),
|
||||
RegistrationMode::Dvpn,
|
||||
);
|
||||
}
|
||||
info!("LP dVPN re-registration for existing peer {wg_key_str} (idempotent)",);
|
||||
inc!("lp_registration_dvpn_idempotent");
|
||||
return existing_response;
|
||||
}
|
||||
|
||||
// TODO: this could be a source of some issue as we pre-allocate ip before validating credentials
|
||||
// (but we do the same in the authenticator anyway...)
|
||||
if let Err(err) = self
|
||||
.register_wg_peer(
|
||||
sender,
|
||||
request.wg_public_key,
|
||||
request.ticket_type,
|
||||
Key::new(request.psk),
|
||||
)
|
||||
.await
|
||||
{
|
||||
return LpRegistrationResponse::error(
|
||||
format!("WireGuard peer IP allocation failed: {err}"),
|
||||
RegistrationMode::Dvpn,
|
||||
);
|
||||
}
|
||||
|
||||
LpRegistrationResponse::request_dvpn_credential()
|
||||
}
|
||||
|
||||
// TODO: dedup
|
||||
async fn handle_final_credential_claim(
|
||||
&self,
|
||||
claim: BandwidthClaim,
|
||||
client_id: i64,
|
||||
) -> Result<i64, GatewayError> {
|
||||
match claim.credential {
|
||||
BandwidthCredential::ZkNym(zk_nym) => {
|
||||
// if we got zk-nym, we just try to verify it
|
||||
let bandwidth =
|
||||
credential_verification(self.ecash_verifier.clone(), *zk_nym, client_id)
|
||||
.await?;
|
||||
Ok(bandwidth)
|
||||
}
|
||||
BandwidthCredential::UpgradeModeJWT { token } => {
|
||||
// TODO: move
|
||||
const UM_BANDWIDTH: i64 = 1024 * 1024 * 1024;
|
||||
|
||||
// if we're already in the upgrade mode, don't bother validating the token
|
||||
if self.upgrade_mode_enabled() {
|
||||
return Ok(UM_BANDWIDTH);
|
||||
}
|
||||
|
||||
self.upgrade_mode.try_enable_via_received_jwt(token).await?;
|
||||
Ok(UM_BANDWIDTH)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_dvpn_registration_finalisation(
|
||||
&self,
|
||||
sender: ReceiverIndex,
|
||||
request: LpDvpnRegistrationFinalisation,
|
||||
) -> LpRegistrationResponse {
|
||||
// see if we still have the pending registration
|
||||
// (e.g. it's illegal for client to request registration and only finalise it,
|
||||
// for example the next day; we can't keep the data forever)
|
||||
let Some(pending) = self
|
||||
.registrations_in_progress
|
||||
.lock()
|
||||
.await
|
||||
.get(&sender)
|
||||
.map(|pending| pending.state)
|
||||
else {
|
||||
return LpRegistrationResponse::error(
|
||||
"no pending registration",
|
||||
RegistrationMode::Dvpn,
|
||||
);
|
||||
};
|
||||
|
||||
if pending.ticket_type != request.credential.kind {
|
||||
return LpRegistrationResponse::error(
|
||||
format!(
|
||||
"inconsistent ticket type. used {} for initial request and {} for finalisation",
|
||||
pending.ticket_type, request.credential.kind
|
||||
),
|
||||
RegistrationMode::Dvpn,
|
||||
);
|
||||
}
|
||||
|
||||
let client_id = pending.client_id;
|
||||
|
||||
let allocated_bandwidth = match self
|
||||
.handle_final_credential_claim(request.credential, client_id)
|
||||
.await
|
||||
{
|
||||
Ok(bandwidth) => bandwidth,
|
||||
Err(err) => {
|
||||
// Credential verification failed, remove the peer
|
||||
warn!("LP credential verification failed for client {client_id}: {err}");
|
||||
inc!("lp_registration_dvpn_failed");
|
||||
if let Err(remove_err) = self
|
||||
.storage
|
||||
.remove_wireguard_peer(&pending.peer_key.to_string())
|
||||
.await
|
||||
{
|
||||
error!(
|
||||
"Failed to remove peer after credential verification failure: {remove_err}"
|
||||
);
|
||||
}
|
||||
self.registrations_in_progress.lock().await.remove(&sender);
|
||||
return LpRegistrationResponse::error(
|
||||
format!("Credential verification failed: {err}"),
|
||||
RegistrationMode::Dvpn,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
info!("LP dVPN registration successful (client_id: {client_id})");
|
||||
inc!("lp_registration_dvpn_success");
|
||||
LpRegistrationResponse::success_dvpn(pending.wireguard_config, allocated_bandwidth)
|
||||
}
|
||||
|
||||
async fn process_dvpn_registration(
|
||||
&self,
|
||||
sender: ReceiverIndex,
|
||||
request: Box<LpDvpnRegistrationRequestMessage>,
|
||||
) -> LpRegistrationResponse {
|
||||
// Track dVPN registration attempts
|
||||
inc!("lp_registration_dvpn_attempts");
|
||||
|
||||
match request.content {
|
||||
LpDvpnRegistrationRequestMessageContent::InitialRequest(req) => {
|
||||
self.process_dvpn_initial_registration(sender, req).await
|
||||
}
|
||||
LpDvpnRegistrationRequestMessageContent::Finalisation(req) => {
|
||||
self.process_dvpn_registration_finalisation(sender, req)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_mixnet_registration(
|
||||
&self,
|
||||
request: LpMixnetRegistrationRequestMessage,
|
||||
) -> LpRegistrationResponse {
|
||||
let _ = request;
|
||||
LpRegistrationResponse::error(
|
||||
"mixnet registration is not yet supported",
|
||||
RegistrationMode::Mixnet,
|
||||
)
|
||||
}
|
||||
|
||||
/// Process an LP registration request
|
||||
pub async fn process_registration(
|
||||
&self,
|
||||
sender: ReceiverIndex,
|
||||
request: LpRegistrationRequest,
|
||||
) -> LpRegistrationResponse {
|
||||
let registration_start = std::time::Instant::now();
|
||||
|
||||
// Track total registration attempts
|
||||
inc!("lp_registration_attempts_total");
|
||||
|
||||
// 1. Validate timestamp for replay protection
|
||||
if !request.validate_timestamp(30) {
|
||||
warn!("LP registration failed: timestamp too old or too far in future");
|
||||
inc!("lp_registration_failed_timestamp");
|
||||
return LpRegistrationResponse::error("invalid timestamp", request.mode());
|
||||
}
|
||||
|
||||
// 2. Process based on mode
|
||||
let result = match request.registration_data {
|
||||
LpRegistrationRequestData::Dvpn { data } => {
|
||||
self.process_dvpn_registration(sender, data).await
|
||||
}
|
||||
LpRegistrationRequestData::Mixnet { data } => {
|
||||
self.process_mixnet_registration(data).await
|
||||
}
|
||||
};
|
||||
|
||||
// Track registration duration
|
||||
let duration = registration_start.elapsed().as_secs_f64();
|
||||
add_histogram_obs!(
|
||||
"lp_registration_duration_seconds",
|
||||
duration,
|
||||
LP_REGISTRATION_DURATION_BUCKETS
|
||||
);
|
||||
|
||||
// Track overall success/failure
|
||||
match result.status {
|
||||
RegistrationStatus::Completed => {
|
||||
inc!("lp_registration_success_total");
|
||||
}
|
||||
RegistrationStatus::Failed => {
|
||||
inc!("lp_registration_failed_total");
|
||||
}
|
||||
RegistrationStatus::PendingMoreData => {
|
||||
inc!("lp_registration_pending_more_data");
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Register a WireGuard peer and return gateway data along with the client_id
|
||||
async fn register_wg_peer(
|
||||
&self,
|
||||
sender: ReceiverIndex,
|
||||
peer_key: PeerPublicKey,
|
||||
ticket_type: nym_credentials_interface::TicketType,
|
||||
psk: Key,
|
||||
) -> Result<(), GatewayError> {
|
||||
// Allocate IPs from centralized pool managed by PeerController
|
||||
let defguard_key = Key::new(peer_key.to_bytes());
|
||||
|
||||
// Request IP allocation from PeerController
|
||||
let ip_pair = self.peer_manager.allocate_peer_ip_pair().await?;
|
||||
|
||||
let client_ipv4 = ip_pair.ipv4;
|
||||
let client_ipv6 = ip_pair.ipv6;
|
||||
|
||||
info!("Allocated IPs for peer {peer_key}: {client_ipv4} / {client_ipv6}");
|
||||
|
||||
// Create WireGuard peer with allocated IPs
|
||||
let mut peer = Peer::new(defguard_key);
|
||||
peer.endpoint = None;
|
||||
peer.allowed_ips = vec![
|
||||
format!("{client_ipv4}/32").parse()?,
|
||||
format!("{client_ipv6}/128").parse()?,
|
||||
];
|
||||
peer.persistent_keepalive_interval = Some(25);
|
||||
peer.preshared_key = Some(psk);
|
||||
|
||||
// Store peer in database FIRST (before adding to controller)
|
||||
// This ensures bandwidth storage exists when controller's generate_bandwidth_manager() is called
|
||||
let client_id = self
|
||||
.storage
|
||||
.insert_wireguard_peer(&peer, ticket_type.into())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Failed to store WireGuard peer in database: {}", e);
|
||||
GatewayError::InternalError(format!("Failed to store peer: {}", e))
|
||||
})?;
|
||||
|
||||
// Create bandwidth entry for the client
|
||||
// This must happen BEFORE AddPeer because generate_bandwidth_manager() expects it to exist
|
||||
credential_storage_preparation(self.ecash_verifier.clone(), client_id).await?;
|
||||
|
||||
// Now send peer to WireGuard controller and track latency
|
||||
let controller_start = std::time::Instant::now();
|
||||
let result = self.peer_manager.add_peer(peer).await;
|
||||
|
||||
// Record peer controller channel latency
|
||||
let latency = controller_start.elapsed().as_secs_f64();
|
||||
add_histogram_obs!(
|
||||
"wg_peer_controller_channel_latency_seconds",
|
||||
latency,
|
||||
WG_CONTROLLER_LATENCY_BUCKETS
|
||||
);
|
||||
|
||||
result?;
|
||||
|
||||
// Get gateway's actual WireGuard public key
|
||||
let gateway_pubkey = *self.keypair().public_key();
|
||||
|
||||
// Get gateway's WireGuard endpoint from config
|
||||
let gateway_endpoint = self.wireguard_config().bind_address;
|
||||
self.registrations_in_progress.lock().await.insert(
|
||||
sender,
|
||||
TimestampedState::new(PendingRegistrationState {
|
||||
client_id,
|
||||
peer_key,
|
||||
ticket_type,
|
||||
wireguard_config: WireguardConfiguration {
|
||||
public_key: gateway_pubkey,
|
||||
endpoint: gateway_endpoint,
|
||||
private_ipv4: client_ipv4,
|
||||
private_ipv6: client_ipv6,
|
||||
},
|
||||
}),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: dedup
|
||||
/// Prepare bandwidth storage for a client
|
||||
async fn credential_storage_preparation(
|
||||
ecash_verifier: Arc<dyn EcashManager + Send + Sync>,
|
||||
@@ -83,6 +524,7 @@ async fn credential_storage_preparation(
|
||||
Ok(bandwidth)
|
||||
}
|
||||
|
||||
// TODO: dedup
|
||||
/// Verify credential and allocate bandwidth using CredentialVerifier
|
||||
async fn credential_verification(
|
||||
ecash_verifier: Arc<dyn EcashManager + Send + Sync>,
|
||||
@@ -131,375 +573,3 @@ async fn credential_verification(
|
||||
|
||||
Ok(allocated)
|
||||
}
|
||||
|
||||
/// Check if WG peer already registered, return cached response if so.
|
||||
///
|
||||
/// This enables idempotent registration: if a client retries registration
|
||||
/// with the same WG public key (e.g., after network failure), we return
|
||||
/// the existing registration data instead of re-processing. This prevents
|
||||
/// wasting credentials on network issues.
|
||||
async fn check_existing_registration(
|
||||
wg_key_str: &str,
|
||||
state: &LpHandlerState,
|
||||
) -> Option<LpRegistrationResponse> {
|
||||
// Need WG data to build GatewayData
|
||||
let wg_data = state.wireguard_data.as_ref()?;
|
||||
|
||||
// Look up existing peer
|
||||
let peer = state.storage.get_wireguard_peer(wg_key_str).await.ok()??;
|
||||
|
||||
// Convert to defguard Peer to access allowed_ips
|
||||
let defguard_peer: Peer = peer.clone().try_into().ok()?;
|
||||
|
||||
// Extract IPv4 and IPv6 from allowed_ips
|
||||
let mut ipv4 = None;
|
||||
let mut ipv6 = None;
|
||||
for ip_mask in &defguard_peer.allowed_ips {
|
||||
match ip_mask.address {
|
||||
std::net::IpAddr::V4(v4) => ipv4 = Some(v4),
|
||||
std::net::IpAddr::V6(v6) => ipv6 = Some(v6),
|
||||
}
|
||||
}
|
||||
|
||||
let (private_ipv4, private_ipv6) = match (ipv4, ipv6) {
|
||||
(Some(v4), Some(v6)) => (v4, v6),
|
||||
_ => return None, // Incomplete data, treat as new registration
|
||||
};
|
||||
|
||||
// Get current bandwidth
|
||||
let bandwidth = state
|
||||
.ecash_verifier
|
||||
.storage()
|
||||
.get_available_bandwidth(peer.client_id)
|
||||
.await
|
||||
.ok()?
|
||||
.map(|b| b.available)
|
||||
.unwrap_or(0);
|
||||
|
||||
// Only return cached response if bandwidth was actually allocated.
|
||||
// If bandwidth is 0, registration was incomplete (peer exists but
|
||||
// credential verification failed or never completed). Let the caller
|
||||
// proceed with normal registration flow which will handle cleanup.
|
||||
if bandwidth == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(LpRegistrationResponse::success(
|
||||
bandwidth,
|
||||
WireguardConfiguration {
|
||||
public_key: *wg_data.keypair().public_key(),
|
||||
endpoint: wg_data.config().bind_address,
|
||||
private_ipv4,
|
||||
private_ipv6,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
/// In the case of an already registered WG peer, update its PSK.
|
||||
async fn update_peer_psk(
|
||||
peer: PeerPublicKey,
|
||||
psk: Key,
|
||||
state: &LpHandlerState,
|
||||
) -> Result<(), GatewayError> {
|
||||
let encoded_psk = psk.to_lower_hex();
|
||||
state
|
||||
.storage
|
||||
.update_peer_psk(&peer.to_string(), Some(&encoded_psk))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn process_dvpn_registration(
|
||||
request: Box<LpDvpnRegistrationRequest>,
|
||||
state: &LpHandlerState,
|
||||
) -> LpRegistrationResponse {
|
||||
// Track dVPN registration attempts
|
||||
inc!("lp_registration_dvpn_attempts");
|
||||
|
||||
// Check for idempotent re-registration (same WG key already registered)
|
||||
// This allows clients to retry registration after network failures
|
||||
// without wasting credentials
|
||||
let wg_key_str = request.wg_public_key.to_string();
|
||||
if let Some(existing_response) = check_existing_registration(&wg_key_str, state).await {
|
||||
// TODO: this flow will be changed in subsequent PRs as it's wasting credentials regardless
|
||||
if let Err(err) = update_peer_psk(request.wg_public_key, Key::new(request.psk), state).await
|
||||
{
|
||||
return LpRegistrationResponse::error(format!(
|
||||
"WireGuard peer PSK update failed: {err}"
|
||||
));
|
||||
}
|
||||
info!("LP dVPN re-registration for existing peer {wg_key_str} (idempotent)",);
|
||||
inc!("lp_registration_dvpn_idempotent");
|
||||
return existing_response;
|
||||
}
|
||||
|
||||
// Register as WireGuard peer first to get client_id
|
||||
let (gateway_data, client_id) = match register_wg_peer(
|
||||
request.wg_public_key.inner().as_ref(),
|
||||
request.ticket_type,
|
||||
Key::new(request.psk),
|
||||
state,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
error!("LP WireGuard peer registration failed: {e}");
|
||||
inc!("lp_registration_dvpn_failed");
|
||||
inc!("lp_errors_wg_peer_registration");
|
||||
return LpRegistrationResponse::error(format!(
|
||||
"WireGuard peer registration failed: {e}",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Verify credential with CredentialVerifier (handles double-spend, storage, etc.)
|
||||
let allocated_bandwidth =
|
||||
match credential_verification(state.ecash_verifier.clone(), request.credential, client_id)
|
||||
.await
|
||||
{
|
||||
Ok(bandwidth) => bandwidth,
|
||||
Err(e) => {
|
||||
// Credential verification failed, remove the peer
|
||||
warn!("LP credential verification failed for client {client_id}: {e}",);
|
||||
inc!("lp_registration_dvpn_failed");
|
||||
if let Err(remove_err) = state
|
||||
.storage
|
||||
.remove_wireguard_peer(&request.wg_public_key.to_string())
|
||||
.await
|
||||
{
|
||||
error!(
|
||||
"Failed to remove peer after credential verification failure: {remove_err}"
|
||||
);
|
||||
}
|
||||
return LpRegistrationResponse::error(format!(
|
||||
"Credential verification failed: {e}",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
info!("LP dVPN registration successful (client_id: {client_id})");
|
||||
inc!("lp_registration_dvpn_success");
|
||||
LpRegistrationResponse::success(allocated_bandwidth, gateway_data)
|
||||
}
|
||||
|
||||
async fn process_mixnet_registration(
|
||||
request: LpMixnetRegistrationRequest,
|
||||
state: &LpHandlerState,
|
||||
) -> LpRegistrationResponse {
|
||||
let session_id = rand::random::<u32>();
|
||||
|
||||
// Track mixnet registration attempts
|
||||
inc!("lp_registration_mixnet_attempts");
|
||||
|
||||
// Derive destination address for ActiveClientsStore lookup
|
||||
let client_identity = request.client_ed25519_pubkey;
|
||||
let client_address = client_identity.derive_destination_address();
|
||||
|
||||
info!("LP Mixnet registration for client {client_identity}, session {session_id}");
|
||||
|
||||
warn!("unimplemented: LP mixnet registration initial bandwidth allocation");
|
||||
// (the old implementation was wrong - it wasn't creating correct db entries)
|
||||
|
||||
// Create channels for client message delivery
|
||||
let (mix_sender, _mix_receiver) = mpsc::unbounded();
|
||||
let (is_active_request_sender, _is_active_request_receiver) =
|
||||
mpsc::unbounded::<oneshot::Sender<IsActive>>();
|
||||
|
||||
// Insert client into ActiveClientsStore for SURB reply delivery
|
||||
if !state.active_clients_store.insert_remote(
|
||||
client_address,
|
||||
mix_sender,
|
||||
is_active_request_sender,
|
||||
OffsetDateTime::now_utc(),
|
||||
) {
|
||||
warn!("LP Mixnet registration failed: client {client_identity} already registered",);
|
||||
inc!("lp_registration_mixnet_failed");
|
||||
return LpRegistrationResponse::error("Client already registered".to_string());
|
||||
}
|
||||
|
||||
// Get gateway identity and derive sphinx key
|
||||
let gateway_identity = *state.local_lp_peer.ed25519().public_key();
|
||||
|
||||
info!("LP Mixnet registration successful (client: {client_identity})",);
|
||||
inc!("lp_registration_mixnet_success");
|
||||
|
||||
LpRegistrationResponse::success_mixnet(0, LpMixnetGatewayData { gateway_identity })
|
||||
}
|
||||
|
||||
/// Process an LP registration request
|
||||
pub async fn process_registration(
|
||||
request: LpRegistrationRequest,
|
||||
state: &LpHandlerState,
|
||||
) -> LpRegistrationResponse {
|
||||
let registration_start = std::time::Instant::now();
|
||||
|
||||
// Track total registration attempts
|
||||
inc!("lp_registration_attempts_total");
|
||||
|
||||
// 1. Validate timestamp for replay protection
|
||||
if !request.validate_timestamp(30) {
|
||||
warn!("LP registration failed: timestamp too old or too far in future");
|
||||
inc!("lp_registration_failed_timestamp");
|
||||
return LpRegistrationResponse::error("Invalid timestamp".to_string());
|
||||
}
|
||||
|
||||
// 2. Process based on mode
|
||||
let result = match request.registration_data {
|
||||
LpRegistrationData::Dvpn { data } => process_dvpn_registration(data, state).await,
|
||||
LpRegistrationData::Mixnet { data } => process_mixnet_registration(data, state).await,
|
||||
};
|
||||
|
||||
// Track registration duration
|
||||
let duration = registration_start.elapsed().as_secs_f64();
|
||||
add_histogram_obs!(
|
||||
"lp_registration_duration_seconds",
|
||||
duration,
|
||||
LP_REGISTRATION_DURATION_BUCKETS
|
||||
);
|
||||
|
||||
// Track overall success/failure
|
||||
if result.success {
|
||||
inc!("lp_registration_success_total");
|
||||
} else {
|
||||
inc!("lp_registration_failed_total");
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Register a WireGuard peer and return gateway data along with the client_id
|
||||
async fn register_wg_peer(
|
||||
public_key_bytes: &[u8],
|
||||
ticket_type: nym_credentials_interface::TicketType,
|
||||
psk: Key,
|
||||
state: &LpHandlerState,
|
||||
) -> Result<(WireguardConfiguration, i64), GatewayError> {
|
||||
let Some(wg_controller) = &state.wg_peer_controller else {
|
||||
return Err(GatewayError::ServiceProviderNotRunning {
|
||||
service: "WireGuard".to_string(),
|
||||
});
|
||||
};
|
||||
|
||||
let Some(wg_data) = &state.wireguard_data else {
|
||||
return Err(GatewayError::ServiceProviderNotRunning {
|
||||
service: "WireGuard".to_string(),
|
||||
});
|
||||
};
|
||||
|
||||
// Convert public key bytes to WireGuard Key
|
||||
let mut key_bytes = [0u8; 32];
|
||||
if public_key_bytes.len() != 32 {
|
||||
return Err(GatewayError::LpProtocolError(
|
||||
"Invalid WireGuard public key length".to_string(),
|
||||
));
|
||||
}
|
||||
key_bytes.copy_from_slice(public_key_bytes);
|
||||
let peer_key = Key::new(key_bytes);
|
||||
|
||||
// Allocate IPs from centralized pool managed by PeerController
|
||||
let registration_data =
|
||||
nym_wireguard::PeerRegistrationData::new(peer_key.clone()).with_preshared_key(psk);
|
||||
|
||||
let psk = registration_data.preshared_key.clone();
|
||||
// Request IP allocation from PeerController
|
||||
let (tx, rx) = oneshot::channel();
|
||||
wg_controller
|
||||
.send(PeerControlRequest::RegisterPeer {
|
||||
registration_data,
|
||||
response_tx: tx,
|
||||
})
|
||||
.await
|
||||
.map_err(|e| {
|
||||
GatewayError::InternalError(format!("Failed to send IP allocation request: {}", e))
|
||||
})?;
|
||||
|
||||
// Wait for IP allocation from pool
|
||||
let ip_pair = rx
|
||||
.await
|
||||
.map_err(|e| {
|
||||
GatewayError::InternalError(format!("Failed to receive IP allocation: {}", e))
|
||||
})?
|
||||
.map_err(|e| {
|
||||
error!("Failed to allocate IPs from pool: {}", e);
|
||||
GatewayError::InternalError(format!("Failed to allocate IPs: {:?}", e))
|
||||
})?;
|
||||
|
||||
let client_ipv4 = ip_pair.ipv4;
|
||||
let client_ipv6 = ip_pair.ipv6;
|
||||
|
||||
info!(
|
||||
"Allocated IPs for peer {}: {} / {}",
|
||||
peer_key, client_ipv4, client_ipv6
|
||||
);
|
||||
|
||||
// Create WireGuard peer with allocated IPs
|
||||
let mut peer = Peer::new(peer_key.clone());
|
||||
peer.endpoint = None;
|
||||
peer.allowed_ips = vec![
|
||||
format!("{client_ipv4}/32").parse()?,
|
||||
format!("{client_ipv6}/128").parse()?,
|
||||
];
|
||||
peer.persistent_keepalive_interval = Some(25);
|
||||
peer.preshared_key = psk;
|
||||
|
||||
// Store peer in database FIRST (before adding to controller)
|
||||
// This ensures bandwidth storage exists when controller's generate_bandwidth_manager() is called
|
||||
let client_id = state
|
||||
.storage
|
||||
.insert_wireguard_peer(&peer, ticket_type.into())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Failed to store WireGuard peer in database: {}", e);
|
||||
GatewayError::InternalError(format!("Failed to store peer: {}", e))
|
||||
})?;
|
||||
|
||||
// Create bandwidth entry for the client
|
||||
// This must happen BEFORE AddPeer because generate_bandwidth_manager() expects it to exist
|
||||
credential_storage_preparation(state.ecash_verifier.clone(), client_id).await?;
|
||||
|
||||
// Now send peer to WireGuard controller and track latency
|
||||
let controller_start = std::time::Instant::now();
|
||||
let (tx, rx) = oneshot::channel();
|
||||
wg_controller
|
||||
.send(PeerControlRequest::AddPeer {
|
||||
peer: peer.clone(),
|
||||
response_tx: tx,
|
||||
})
|
||||
.await
|
||||
.map_err(|e| GatewayError::InternalError(format!("Failed to send peer request: {}", e)))?;
|
||||
|
||||
let result = rx
|
||||
.await
|
||||
.map_err(|e| {
|
||||
GatewayError::InternalError(format!("Failed to receive peer response: {}", e))
|
||||
})?
|
||||
.map_err(|e| GatewayError::InternalError(format!("Failed to add peer: {:?}", e)));
|
||||
|
||||
// Record peer controller channel latency
|
||||
let latency = controller_start.elapsed().as_secs_f64();
|
||||
add_histogram_obs!(
|
||||
"wg_peer_controller_channel_latency_seconds",
|
||||
latency,
|
||||
WG_CONTROLLER_LATENCY_BUCKETS
|
||||
);
|
||||
|
||||
result?;
|
||||
|
||||
// Get gateway's actual WireGuard public key
|
||||
let gateway_pubkey = *wg_data.keypair().public_key();
|
||||
|
||||
// Get gateway's WireGuard endpoint from config
|
||||
let gateway_endpoint = wg_data.config().bind_address;
|
||||
|
||||
// Create GatewayData response (matching authenticator response format)
|
||||
Ok((
|
||||
WireguardConfiguration {
|
||||
public_key: gateway_pubkey,
|
||||
endpoint: gateway_endpoint,
|
||||
private_ipv4: client_ipv4,
|
||||
private_ipv6: client_ipv6,
|
||||
},
|
||||
client_id,
|
||||
))
|
||||
}
|
||||
|
||||
+15
-21
@@ -18,6 +18,7 @@ use nym_credential_verification::upgrade_mode::{
|
||||
};
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_ip_packet_router::IpPacketRouter;
|
||||
use nym_lp::peer::LpLocalPeer;
|
||||
use nym_mixnet_client::forwarder::MixForwardingSender;
|
||||
use nym_network_defaults::NymNetworkDetails;
|
||||
use nym_network_requester::NRServiceProviderBuilder;
|
||||
@@ -37,6 +38,7 @@ use tracing::*;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub use crate::node::upgrade_mode::watcher::UpgradeModeWatcher;
|
||||
use crate::node::wireguard::PeerManager;
|
||||
pub use client_handling::active_clients::ActiveClientsStore;
|
||||
pub use lp_listener::LpConfig;
|
||||
pub use nym_credential_verification::upgrade_mode::UpgradeModeCheckRequestSender;
|
||||
@@ -46,7 +48,6 @@ pub use nym_gateway_storage::{
|
||||
traits::{BandwidthGatewayStorage, InboxGatewayStorage},
|
||||
GatewayStorage,
|
||||
};
|
||||
use nym_lp::peer::LpLocalPeer;
|
||||
pub use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent};
|
||||
|
||||
pub(crate) mod client_handling;
|
||||
@@ -54,6 +55,7 @@ pub(crate) mod internal_service_providers;
|
||||
pub mod lp_listener;
|
||||
mod stale_data_cleaner;
|
||||
pub mod upgrade_mode;
|
||||
pub mod wireguard;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocalNetworkRequesterOpts {
|
||||
@@ -328,13 +330,18 @@ impl GatewayTasksBuilder {
|
||||
|
||||
pub async fn build_lp_listener(
|
||||
&mut self,
|
||||
upgrade_mode_common_state: UpgradeModeDetails,
|
||||
active_clients_store: ActiveClientsStore,
|
||||
) -> Result<lp_listener::LpListener, GatewayError> {
|
||||
// Get WireGuard peer controller if available
|
||||
let wg_peer_controller = self
|
||||
.wireguard_data
|
||||
.as_ref()
|
||||
.map(|wg_data| wg_data.inner.peer_tx().clone());
|
||||
let Some(wireguard_data) = &self.wireguard_data else {
|
||||
return Err(GatewayError::InternalWireguardError(
|
||||
"wireguard not set".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
// TODO: combine this `PeerManager` with the one used within the authenticator
|
||||
let peer_manager = Arc::new(PeerManager::new(wireguard_data.inner.clone()));
|
||||
|
||||
let handler_state = lp_listener::LpHandlerState {
|
||||
ecash_verifier: self.ecash_manager().await?,
|
||||
@@ -346,12 +353,13 @@ impl GatewayTasksBuilder {
|
||||
.with_kem_psq_key(self.kem_psq_keys.clone()),
|
||||
metrics: self.metrics.clone(),
|
||||
active_clients_store,
|
||||
wg_peer_controller,
|
||||
wireguard_data: self.wireguard_data.as_ref().map(|wd| wd.inner.clone()),
|
||||
upgrade_mode: upgrade_mode_common_state,
|
||||
peer_manager,
|
||||
lp_config: self.config.lp,
|
||||
outbound_mix_sender: self.mix_packet_sender.clone(),
|
||||
handshake_states: Arc::new(dashmap::DashMap::new()),
|
||||
session_states: Arc::new(dashmap::DashMap::new()),
|
||||
registrations_in_progress: Default::default(),
|
||||
forward_semaphore: Arc::new(Semaphore::new(
|
||||
self.config.lp.debug.max_concurrent_forwards,
|
||||
)),
|
||||
@@ -485,25 +493,12 @@ impl GatewayTasksBuilder {
|
||||
Ok(peers)
|
||||
}
|
||||
|
||||
async fn get_wireguard_networks(&mut self) -> Result<Vec<IpAddr>, GatewayError> {
|
||||
if let Some(cached) = self.wireguard_networks.take() {
|
||||
return Ok(cached);
|
||||
}
|
||||
|
||||
let (peers, used_private_network_ips) = self.build_wireguard_peers_and_networks().await?;
|
||||
// cache peers for the other task
|
||||
|
||||
self.wireguard_peers = Some(peers);
|
||||
Ok(used_private_network_ips)
|
||||
}
|
||||
|
||||
pub async fn build_wireguard_authenticator(
|
||||
&mut self,
|
||||
upgrade_mode_common: UpgradeModeDetails,
|
||||
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
) -> Result<ServiceProviderBeingBuilt<Authenticator>, GatewayError> {
|
||||
let ecash_manager = self.ecash_manager().await?;
|
||||
let used_private_network_ips = self.get_wireguard_networks().await?;
|
||||
|
||||
let Some(opts) = &self.authenticator_opts else {
|
||||
return Err(GatewayError::UnspecifiedAuthenticatorConfig);
|
||||
@@ -526,7 +521,6 @@ impl GatewayTasksBuilder {
|
||||
opts.config.clone(),
|
||||
upgrade_mode_common,
|
||||
wireguard_data.inner.clone(),
|
||||
used_private_network_ips,
|
||||
ecash_manager,
|
||||
self.shutdown_tracker.clone(),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GatewayWireguardError {
|
||||
#[error("internal error: {0}")]
|
||||
InternalError(String),
|
||||
|
||||
#[error("peers can't be interacted with anymore")]
|
||||
PeerInteractionStopped,
|
||||
}
|
||||
|
||||
impl GatewayWireguardError {
|
||||
pub fn internal(message: impl Into<String>) -> Self {
|
||||
GatewayWireguardError::InternalError(message.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod error;
|
||||
pub mod peer_manager;
|
||||
|
||||
pub use error::GatewayWireguardError;
|
||||
pub use peer_manager::PeerManager;
|
||||
+73
-31
@@ -1,14 +1,17 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::node::internal_service_providers::authenticator::error::AuthenticatorError;
|
||||
use crate::node::wireguard::GatewayWireguardError;
|
||||
use defguard_wireguard_rs::{host::Peer, key::Key};
|
||||
use futures::channel::oneshot;
|
||||
use nym_credential_verification::{ClientBandwidth, TicketVerifier};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_wireguard::peer_controller::IpPair;
|
||||
use nym_wireguard::{peer_controller::PeerControlRequest, WireguardGatewayData};
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use tracing::error;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PeerManager {
|
||||
pub(crate) wireguard_gateway_data: WireguardGatewayData,
|
||||
}
|
||||
@@ -19,26 +22,73 @@ impl PeerManager {
|
||||
wireguard_gateway_data,
|
||||
}
|
||||
}
|
||||
pub async fn add_peer(&self, peer: Peer) -> Result<(), AuthenticatorError> {
|
||||
|
||||
pub async fn allocate_peer_ip_pair(&self) -> Result<IpPair, GatewayWireguardError> {
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
let msg = PeerControlRequest::AllocatePeerIpPair { response_tx };
|
||||
self.wireguard_gateway_data
|
||||
.peer_tx()
|
||||
.send(msg)
|
||||
.await
|
||||
.map_err(|_| GatewayWireguardError::PeerInteractionStopped)?;
|
||||
|
||||
response_rx
|
||||
.await
|
||||
.map_err(|e| {
|
||||
GatewayWireguardError::InternalError(format!(
|
||||
"Failed to receive IP allocation: {e}"
|
||||
))
|
||||
})?
|
||||
.map_err(|e| {
|
||||
error!("Failed to allocate IPs from pool: {e}");
|
||||
GatewayWireguardError::InternalError(format!("Failed to allocate IPs: {e}"))
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn release_ip_pair(&self, ip_pair: IpPair) -> Result<(), GatewayWireguardError> {
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
let msg = PeerControlRequest::ReleaseIpPair {
|
||||
response_tx,
|
||||
ip_pair,
|
||||
};
|
||||
self.wireguard_gateway_data
|
||||
.peer_tx()
|
||||
.send(msg)
|
||||
.await
|
||||
.map_err(|_| GatewayWireguardError::PeerInteractionStopped)?;
|
||||
|
||||
response_rx
|
||||
.await
|
||||
.map_err(|_| GatewayWireguardError::internal("no response for release ip allocation"))?
|
||||
.map_err(|err| {
|
||||
GatewayWireguardError::InternalError(format!(
|
||||
"releasing ip pair not be performed: {err:?}"
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_peer(&self, peer: Peer) -> Result<(), GatewayWireguardError> {
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
let msg = PeerControlRequest::AddPeer { peer, response_tx };
|
||||
self.wireguard_gateway_data
|
||||
.peer_tx()
|
||||
.send(msg)
|
||||
.await
|
||||
.map_err(|_| AuthenticatorError::PeerInteractionStopped)?;
|
||||
.map_err(|_| GatewayWireguardError::PeerInteractionStopped)?;
|
||||
|
||||
response_rx
|
||||
.await
|
||||
.map_err(|_| AuthenticatorError::InternalError("no response for add peer".to_string()))?
|
||||
.map_err(|_| GatewayWireguardError::internal("no response for add peer".to_string()))?
|
||||
.map_err(|err| {
|
||||
AuthenticatorError::InternalError(format!(
|
||||
GatewayWireguardError::internal(format!(
|
||||
"adding peer could not be performed: {err:?}"
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn _remove_peer(&self, pub_key: PeerPublicKey) -> Result<(), AuthenticatorError> {
|
||||
pub async fn _remove_peer(&self, pub_key: PeerPublicKey) -> Result<(), GatewayWireguardError> {
|
||||
let key = Key::new(pub_key.to_bytes());
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
let msg = PeerControlRequest::RemovePeer { key, response_tx };
|
||||
@@ -46,15 +96,13 @@ impl PeerManager {
|
||||
.peer_tx()
|
||||
.send(msg)
|
||||
.await
|
||||
.map_err(|_| AuthenticatorError::PeerInteractionStopped)?;
|
||||
.map_err(|_| GatewayWireguardError::PeerInteractionStopped)?;
|
||||
|
||||
response_rx
|
||||
.await
|
||||
.map_err(|_| {
|
||||
AuthenticatorError::InternalError("no response for remove peer".to_string())
|
||||
})?
|
||||
.map_err(|_| GatewayWireguardError::internal("no response for remove peer"))?
|
||||
.map_err(|err| {
|
||||
AuthenticatorError::InternalError(format!(
|
||||
GatewayWireguardError::InternalError(format!(
|
||||
"removing peer could not be performed: {err:?}"
|
||||
))
|
||||
})
|
||||
@@ -63,7 +111,7 @@ impl PeerManager {
|
||||
pub async fn query_peer(
|
||||
&self,
|
||||
public_key: PeerPublicKey,
|
||||
) -> Result<Option<Peer>, AuthenticatorError> {
|
||||
) -> Result<Option<Peer>, GatewayWireguardError> {
|
||||
let key = Key::new(public_key.to_bytes());
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
let msg = PeerControlRequest::QueryPeer { key, response_tx };
|
||||
@@ -71,15 +119,13 @@ impl PeerManager {
|
||||
.peer_tx()
|
||||
.send(msg)
|
||||
.await
|
||||
.map_err(|_| AuthenticatorError::PeerInteractionStopped)?;
|
||||
.map_err(|_| GatewayWireguardError::PeerInteractionStopped)?;
|
||||
|
||||
response_rx
|
||||
.await
|
||||
.map_err(|_| {
|
||||
AuthenticatorError::InternalError("no response for query peer".to_string())
|
||||
})?
|
||||
.map_err(|_| GatewayWireguardError::internal("no response for query peer".to_string()))?
|
||||
.map_err(|err| {
|
||||
AuthenticatorError::InternalError(format!(
|
||||
GatewayWireguardError::internal(format!(
|
||||
"querying peer could not be performed: {err:?}"
|
||||
))
|
||||
})
|
||||
@@ -88,7 +134,7 @@ impl PeerManager {
|
||||
pub async fn query_bandwidth(
|
||||
&self,
|
||||
public_key: PeerPublicKey,
|
||||
) -> Result<i64, AuthenticatorError> {
|
||||
) -> Result<i64, GatewayWireguardError> {
|
||||
let client_bandwidth = self.query_client_bandwidth(public_key).await?;
|
||||
Ok(client_bandwidth.available().await)
|
||||
}
|
||||
@@ -96,7 +142,7 @@ impl PeerManager {
|
||||
pub async fn query_client_bandwidth(
|
||||
&self,
|
||||
key: PeerPublicKey,
|
||||
) -> Result<ClientBandwidth, AuthenticatorError> {
|
||||
) -> Result<ClientBandwidth, GatewayWireguardError> {
|
||||
let key = Key::new(key.to_bytes());
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
let msg = PeerControlRequest::GetClientBandwidthByKey { key, response_tx };
|
||||
@@ -104,17 +150,13 @@ impl PeerManager {
|
||||
.peer_tx()
|
||||
.send(msg)
|
||||
.await
|
||||
.map_err(|_| AuthenticatorError::PeerInteractionStopped)?;
|
||||
.map_err(|_| GatewayWireguardError::PeerInteractionStopped)?;
|
||||
|
||||
response_rx
|
||||
.await
|
||||
.map_err(|_| {
|
||||
AuthenticatorError::InternalError(
|
||||
"no response for query client bandwidth".to_string(),
|
||||
)
|
||||
})?
|
||||
.map_err(|_| GatewayWireguardError::internal("no response for query client bandwidth"))?
|
||||
.map_err(|err| {
|
||||
AuthenticatorError::InternalError(format!(
|
||||
GatewayWireguardError::internal(format!(
|
||||
"querying client bandwidth could not be performed: {err:?}"
|
||||
))
|
||||
})
|
||||
@@ -124,7 +166,7 @@ impl PeerManager {
|
||||
&self,
|
||||
key: PeerPublicKey,
|
||||
credential: CredentialSpendingData,
|
||||
) -> Result<Box<dyn TicketVerifier + Send + Sync>, AuthenticatorError> {
|
||||
) -> Result<Box<dyn TicketVerifier + Send + Sync>, GatewayWireguardError> {
|
||||
let key = Key::new(key.to_bytes());
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
let msg = PeerControlRequest::GetVerifierByKey {
|
||||
@@ -136,15 +178,15 @@ impl PeerManager {
|
||||
.peer_tx()
|
||||
.send(msg)
|
||||
.await
|
||||
.map_err(|_| AuthenticatorError::PeerInteractionStopped)?;
|
||||
.map_err(|_| GatewayWireguardError::PeerInteractionStopped)?;
|
||||
|
||||
response_rx
|
||||
.await
|
||||
.map_err(|_| {
|
||||
AuthenticatorError::InternalError("no response for query verifier".to_string())
|
||||
GatewayWireguardError::internal("no response for query verifier".to_string())
|
||||
})?
|
||||
.map_err(|err| {
|
||||
AuthenticatorError::InternalError(format!(
|
||||
GatewayWireguardError::internal(format!(
|
||||
"querying verifier could not be performed: {err:?}"
|
||||
))
|
||||
})
|
||||
@@ -26,7 +26,7 @@ nym-lp-transport = { path = "../common/nym-lp-transport", features = ["io-mocks"
|
||||
nym-gateway = { path = "../gateway" }
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite"] }
|
||||
tracing = { workspace = true }
|
||||
|
||||
futures = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
mod tests {
|
||||
use anyhow::Context;
|
||||
use nym_bandwidth_controller::mock::MockBandwidthController;
|
||||
use nym_credential_verification::UpgradeModeState;
|
||||
use nym_credential_verification::ecash::MockEcashManager;
|
||||
use nym_credential_verification::upgrade_mode::{
|
||||
UpgradeModeCheckConfig, UpgradeModeCheckRequestSender, UpgradeModeDetails,
|
||||
};
|
||||
use nym_credentials_interface::TicketType;
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_gateway::GatewayError;
|
||||
@@ -14,6 +18,7 @@ mod tests {
|
||||
LpDebug, LpHandlerState, LpLocalPeer, MixForwardingReceiver, PeerControlRequest,
|
||||
WireguardGatewayData, mix_forwarding_channels,
|
||||
};
|
||||
use nym_gateway::node::wireguard::PeerManager;
|
||||
use nym_gateway::node::{ActiveClientsStore, GatewayStorage, LpConfig};
|
||||
use nym_registration_client::{LpClientError, LpRegistrationClient};
|
||||
use nym_test_utils::helpers::{CryptoRng, RngCore, u64_seeded_rng};
|
||||
@@ -49,6 +54,7 @@ mod tests {
|
||||
peer: LpLocalPeer,
|
||||
x25519_wg_keys: Arc<x25519::KeyPair>,
|
||||
socket_addr: SocketAddr,
|
||||
lp_version: u8,
|
||||
}
|
||||
|
||||
impl Party {
|
||||
@@ -68,6 +74,7 @@ mod tests {
|
||||
.with_kem_psq_key(lp_x25519_keys),
|
||||
x25519_wg_keys,
|
||||
socket_addr: SocketAddr::from((ip, u16::from_le_bytes(port))),
|
||||
lp_version: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,6 +181,17 @@ mod tests {
|
||||
Ok(GatewayStorage::from_connection_pool(conn_pool, 100).await?)
|
||||
}
|
||||
|
||||
const DUMMY_ATTESTER_ED25519_PRIVATE_KEY: [u8; 32] = [
|
||||
108, 49, 193, 21, 126, 161, 249, 85, 242, 207, 74, 195, 238, 6, 64, 149, 201, 140, 248,
|
||||
163, 122, 170, 79, 198, 87, 85, 36, 29, 243, 92, 64, 161,
|
||||
];
|
||||
|
||||
pub(crate) fn dummy_attester_public_key() -> ed25519::PublicKey {
|
||||
let private_key =
|
||||
ed25519::PrivateKey::from_bytes(&Self::DUMMY_ATTESTER_ED25519_PRIVATE_KEY).unwrap();
|
||||
private_key.public_key()
|
||||
}
|
||||
|
||||
async fn mock(rng: &mut (impl RngCore + CryptoRng)) -> anyhow::Result<Self> {
|
||||
let base = Party::generate(rng);
|
||||
|
||||
@@ -199,11 +217,27 @@ mod tests {
|
||||
// create wireguard data
|
||||
let (wireguard_data, peer_request_rx) = Self::wireguard_data(&base);
|
||||
|
||||
let (um_recheck_tx, um_recheck_rx) = futures::channel::mpsc::unbounded();
|
||||
|
||||
// TODO: use it if we ever want to test UM
|
||||
let _ = um_recheck_rx;
|
||||
|
||||
// mock the wg peer controller
|
||||
let (mock_peer_controller, peer_controller_state) =
|
||||
mock_peer_controller(peer_request_rx);
|
||||
|
||||
let upgrade_mode_state = UpgradeModeState::new(Self::dummy_attester_public_key());
|
||||
let upgrade_mode_details = UpgradeModeDetails::new(
|
||||
UpgradeModeCheckConfig {
|
||||
// essentially we never want to trigger this in our tests
|
||||
min_staleness_recheck: Duration::from_nanos(1),
|
||||
},
|
||||
UpgradeModeCheckRequestSender::new(um_recheck_tx),
|
||||
upgrade_mode_state.clone(),
|
||||
);
|
||||
|
||||
// registering particular responses for peer controller is up to given test
|
||||
let peer_manager = Arc::new(PeerManager::new(wireguard_data));
|
||||
|
||||
let lp_state = LpHandlerState {
|
||||
// use mock instance of ecash verifier
|
||||
@@ -220,9 +254,8 @@ mod tests {
|
||||
active_clients_store: ActiveClientsStore::new(),
|
||||
|
||||
// handles required for wg registration
|
||||
wg_peer_controller: Some(wireguard_data.peer_tx().clone()),
|
||||
|
||||
wireguard_data: Some(wireguard_data),
|
||||
upgrade_mode: upgrade_mode_details,
|
||||
peer_manager,
|
||||
|
||||
// use default lp config (with enabled flag)
|
||||
lp_config,
|
||||
@@ -237,6 +270,7 @@ mod tests {
|
||||
session_states: Arc::new(Default::default()),
|
||||
|
||||
// sensible default value for tests
|
||||
registrations_in_progress: Default::default(),
|
||||
forward_semaphore,
|
||||
};
|
||||
|
||||
@@ -376,6 +410,7 @@ mod tests {
|
||||
mod using_lp_registration_client {
|
||||
use super::*;
|
||||
use nym_registration_client::NestedLpSession;
|
||||
use nym_wireguard::DefguardPeer;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_basic_lp_entry_registration() -> anyhow::Result<()> {
|
||||
@@ -392,6 +427,7 @@ mod tests {
|
||||
client_data.base.peer.ed25519().clone(),
|
||||
entry.base.peer.as_remote(),
|
||||
entry.base.socket_addr,
|
||||
entry.base.lp_version,
|
||||
);
|
||||
|
||||
// 1. establish mock connection between client and gateway and retrieve gateway's handle
|
||||
@@ -410,11 +446,10 @@ mod tests {
|
||||
// 1) peer registration - ip pair allocation
|
||||
let ip_pair = entry.allocate_ip_pair().await;
|
||||
let reg_res = Ok::<_, nym_wireguard::Error>(ip_pair);
|
||||
let public_key = client_key.to_wg_key();
|
||||
|
||||
entry
|
||||
.register_peer_controller_response(
|
||||
PeerControlRequestType::RegisterPeer { public_key },
|
||||
PeerControlRequestType::AllocatePeerIpPair {},
|
||||
reg_res,
|
||||
)
|
||||
.await;
|
||||
@@ -430,6 +465,16 @@ mod tests {
|
||||
)
|
||||
.await;
|
||||
|
||||
// 3) peer query - check for prior registrations
|
||||
let query_res = Ok::<_, nym_wireguard::Error>(Option::<DefguardPeer>::None);
|
||||
let key = client_key.to_wg_key();
|
||||
entry
|
||||
.register_peer_controller_response(
|
||||
PeerControlRequestType::QueryPeer { key },
|
||||
query_res,
|
||||
)
|
||||
.await;
|
||||
|
||||
// 4. spawn peer controller to be able to handle dvpn registration requests
|
||||
entry.spawn_peer_controller();
|
||||
|
||||
@@ -440,7 +485,7 @@ mod tests {
|
||||
let wg_keypair = client_data.base.x25519_wg_keys;
|
||||
let gateway_identity = entry.base.peer.ed25519().public_key();
|
||||
let registration_result = client
|
||||
.register(
|
||||
.register_dvpn(
|
||||
&mut client_rng,
|
||||
&wg_keypair,
|
||||
gateway_identity,
|
||||
@@ -454,7 +499,6 @@ mod tests {
|
||||
let peers_guard = entry.mock_peer_controller_state.peers.read().await;
|
||||
let peer = peers_guard.get_by_x25519_key(&client_key).unwrap().clone();
|
||||
drop(peers_guard);
|
||||
assert!(peer.register_success);
|
||||
assert!(peer.add_success);
|
||||
|
||||
assert_eq!(registration_result.private_ipv4, ip_pair.ipv4);
|
||||
@@ -483,6 +527,7 @@ mod tests {
|
||||
client_data.base.peer.ed25519().clone(),
|
||||
entry.base.peer.as_remote(),
|
||||
entry.base.socket_addr,
|
||||
entry.base.lp_version,
|
||||
);
|
||||
|
||||
// 1. establish mock connection between client and gateway and retrieve gateway's handle
|
||||
@@ -506,7 +551,7 @@ mod tests {
|
||||
let wg_keypair = client_data.base.x25519_wg_keys;
|
||||
let gateway_identity = entry.base.peer.ed25519().public_key();
|
||||
let registration_result = client
|
||||
.register(
|
||||
.register_dvpn(
|
||||
&mut client_rng,
|
||||
&wg_keypair,
|
||||
gateway_identity,
|
||||
@@ -520,7 +565,10 @@ mod tests {
|
||||
let LpClientError::Transport(err) = registration_result else {
|
||||
panic!("unexpected error");
|
||||
};
|
||||
assert_eq!(err, "Cannot register: handshake not completed");
|
||||
assert_eq!(
|
||||
err,
|
||||
"State machine not available - has the handshake been completed?"
|
||||
);
|
||||
|
||||
// 5. stop the gateway task and finish the test
|
||||
entry.stop_tasks().await?;
|
||||
@@ -544,6 +592,7 @@ mod tests {
|
||||
client_data.base.peer.ed25519().clone(),
|
||||
entry.base.peer.as_remote(),
|
||||
entry.base.socket_addr,
|
||||
entry.base.lp_version,
|
||||
);
|
||||
|
||||
// START: ENTRY SETUP
|
||||
@@ -570,11 +619,10 @@ mod tests {
|
||||
// 1) peer registration - ip pair allocation
|
||||
let entry_ip_pair = entry.allocate_ip_pair().await;
|
||||
let reg_res = Ok::<_, nym_wireguard::Error>(entry_ip_pair);
|
||||
let public_key = client_key.to_wg_key();
|
||||
|
||||
entry
|
||||
.register_peer_controller_response(
|
||||
PeerControlRequestType::RegisterPeer { public_key },
|
||||
PeerControlRequestType::AllocatePeerIpPair {},
|
||||
reg_res,
|
||||
)
|
||||
.await;
|
||||
@@ -590,6 +638,16 @@ mod tests {
|
||||
)
|
||||
.await;
|
||||
|
||||
// 3) peer query - check for prior registrations
|
||||
let query_res = Ok::<_, nym_wireguard::Error>(Option::<DefguardPeer>::None);
|
||||
let key = client_key.to_wg_key();
|
||||
entry
|
||||
.register_peer_controller_response(
|
||||
PeerControlRequestType::QueryPeer { key },
|
||||
query_res,
|
||||
)
|
||||
.await;
|
||||
|
||||
// 5. spawn peer controller to be able to handle dvpn registration requests
|
||||
entry.spawn_peer_controller();
|
||||
|
||||
@@ -612,10 +670,9 @@ mod tests {
|
||||
// 1) peer registration - ip pair allocation
|
||||
let exit_ip_pair = exit.allocate_ip_pair().await;
|
||||
let reg_res = Ok::<_, nym_wireguard::Error>(exit_ip_pair);
|
||||
let public_key = client_key.to_wg_key();
|
||||
|
||||
exit.register_peer_controller_response(
|
||||
PeerControlRequestType::RegisterPeer { public_key },
|
||||
PeerControlRequestType::AllocatePeerIpPair {},
|
||||
reg_res,
|
||||
)
|
||||
.await;
|
||||
@@ -630,6 +687,15 @@ mod tests {
|
||||
)
|
||||
.await;
|
||||
|
||||
// 3) peer query - check for prior registrations
|
||||
let query_res = Ok::<_, nym_wireguard::Error>(Option::<DefguardPeer>::None);
|
||||
let key = client_key.to_wg_key();
|
||||
exit.register_peer_controller_response(
|
||||
PeerControlRequestType::QueryPeer { key },
|
||||
query_res,
|
||||
)
|
||||
.await;
|
||||
|
||||
// 11. spawn peer controller to be able to handle dvpn registration requests
|
||||
exit.spawn_peer_controller();
|
||||
|
||||
@@ -642,11 +708,12 @@ mod tests {
|
||||
exit.base.socket_addr.to_string(),
|
||||
client_data.base.peer.ed25519().clone(),
|
||||
exit.base.peer.as_remote(),
|
||||
exit.base.lp_version,
|
||||
);
|
||||
|
||||
// 13. Perform handshake and registration with exit gateway (all via entry forwarding)
|
||||
let exit_registration_result = nested_session
|
||||
.handshake_and_register(
|
||||
.handshake_and_register_dvpn(
|
||||
&mut entry_client,
|
||||
&mut client_rng,
|
||||
&client_data.base.x25519_wg_keys,
|
||||
@@ -659,7 +726,7 @@ mod tests {
|
||||
|
||||
// 14. complete registration with the entry
|
||||
let entry_registration_result = entry_client
|
||||
.register(
|
||||
.register_dvpn(
|
||||
&mut client_rng,
|
||||
&client_data.base.x25519_wg_keys,
|
||||
entry.base.peer.ed25519().public_key(),
|
||||
@@ -673,13 +740,11 @@ mod tests {
|
||||
let peers_guard = entry.mock_peer_controller_state.peers.read().await;
|
||||
let entry_peer = peers_guard.get_by_x25519_key(&client_key).unwrap().clone();
|
||||
drop(peers_guard);
|
||||
assert!(entry_peer.register_success);
|
||||
assert!(entry_peer.add_success);
|
||||
|
||||
let peers_guard = exit.mock_peer_controller_state.peers.read().await;
|
||||
let exit_peer = peers_guard.get_by_x25519_key(&client_key).unwrap().clone();
|
||||
drop(peers_guard);
|
||||
assert!(exit_peer.register_success);
|
||||
assert!(exit_peer.add_success);
|
||||
|
||||
assert_eq!(entry_registration_result.private_ipv4, entry_ip_pair.ipv4);
|
||||
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures, serialization_revision as \"serialization_revision: u8\"\n FROM expiration_date_signatures\n WHERE expiration_date = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "epoch_id: u32",
|
||||
"ordinal": 0,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "serialised_signatures",
|
||||
"ordinal": 1,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "serialization_revision: u8",
|
||||
"ordinal": 2,
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "00d857b624e7edab1198114b17cbad1e16988a3f9989d135840500e1143ce5e5"
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT timestamp, reliability as \"reliability: u8\"\n FROM gateway_status\n JOIN gateway_details\n ON gateway_status.gateway_details_id = gateway_details.id\n WHERE gateway_details.node_id=? AND gateway_status.timestamp > ?;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "timestamp",
|
||||
"ordinal": 0,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "reliability: u8",
|
||||
"ordinal": 1,
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "1d4535b58abdefaaca96bc7312fe14f63ccb56fa62976f7ce3d3b4f6eca8b711"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n DELETE FROM emergency_credential\n WHERE id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2f3c12cb0c48084b569e12ecb0315212a6f526f78258e173c96ec177988696ef"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n DELETE FROM emergency_credential\n WHERE type = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4a4f3b32b313f7fbc6eb579659e7cec1442967e53764b83ba0a66cd9a72494f9"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT serialised_signatures, serialization_revision as \"serialization_revision: u8\"\n FROM global_expiration_date_signatures\n WHERE expiration_date = ? AND epoch_id = ?\n ",
|
||||
"query": "\n SELECT serialised_signatures, serialization_revision as \"serialization_revision: u8\"\n FROM expiration_date_signatures\n WHERE expiration_date = ? AND epoch_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -22,5 +22,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "3cc446220668fb3e02f0578104291d2a2af57656b405212af414d765b2263347"
|
||||
"hash": "5d3b8ad051ab6f46c702308c2fc751a5ca340ac9c6dd86da1a5e9a3e65ea589f"
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n d.node_id as \"node_id: NodeId\",\n CASE WHEN count(*) > 3 THEN AVG(reliability) ELSE 100 END as \"value: f32\"\n FROM\n gateway_details d\n JOIN\n gateway_status s on d.id = s.gateway_details_id\n WHERE\n timestamp >= ? AND\n timestamp <= ?\n GROUP BY 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "node_id: NodeId",
|
||||
"ordinal": 0,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "value: f32",
|
||||
"ordinal": 1,
|
||||
"type_info": "Null"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "676299beb2004ab89f7b38cf21ffb84ab5e7d7435297573523e2532560c2e302"
|
||||
}
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures\n FROM global_expiration_date_signatures\n WHERE expiration_date = ?\n ",
|
||||
"query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures\n FROM global_expiration_date_signatures\n WHERE expiration_date = ? AND epoch_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -15,12 +15,12 @@
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "5eb13bfbee53b50641f69d4d6b62383c7f43864bffe98642bb8d1cf7c259d7be"
|
||||
"hash": "697e6d738aecf115a3608139579b2d1d937dd4cc4778dfb42709adf08c1497f2"
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT timestamp, reliability as \"reliability: u8\"\n FROM mixnode_status\n JOIN mixnode_details\n ON mixnode_status.mixnode_details_id = mixnode_details.id\n WHERE mixnode_details.mix_id=? AND mixnode_status.timestamp > ?;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "timestamp",
|
||||
"ordinal": 0,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "reliability: u8",
|
||||
"ordinal": 1,
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "73ca856950a0157acfd3e2ed07b11aca3d875f67c77e2e7c75653c3f337d594e"
|
||||
}
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures\n FROM partial_expiration_date_signatures\n WHERE expiration_date = ?\n ",
|
||||
"query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures\n FROM partial_expiration_date_signatures\n WHERE expiration_date = ? AND epoch_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -15,12 +15,12 @@
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "1f72d6f538a3655a031a3a8706794559c4c0df6defdfd179c84d02d3b8a6c055"
|
||||
"hash": "7e1829a17b81d7ee3dd8134b7a2be38ffdcaa672afbae44993365ca6910f68f6"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO emergency_credential\n (type, content, expiration)\n VALUES (?, ?, ?)\n ON CONFLICT(type, content) DO NOTHING;\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "805ad4f26e0234d7f482a263e186156311713d2e9f69d39c868cd16296b56326"
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT identity FROM gateway_details WHERE node_id = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "identity",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "924f8eb10c6cbb7f35da6c1bb77e1025442a594dcb5c6401b3dfac7df9c25073"
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n d.mix_id as \"mix_id: NodeId\",\n AVG(s.reliability) as \"value: f32\"\n FROM\n mixnode_details d\n JOIN\n mixnode_status s on d.id = s.mixnode_details_id\n WHERE\n timestamp >= ? AND\n timestamp <= ?\n GROUP BY 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "mix_id: NodeId",
|
||||
"ordinal": 0,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "value: f32",
|
||||
"ordinal": 1,
|
||||
"type_info": "Null"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "c19e1b3768bf2929407599e6e8783ead09f4d7319b7997fa2a9bb628f9404166"
|
||||
}
|
||||
@@ -16,6 +16,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::net::IpAddr;
|
||||
use strum_macros::{Display, EnumString};
|
||||
use thiserror::Error;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
// to whoever is thinking of modifying this struct.
|
||||
@@ -211,17 +212,57 @@ pub struct LewesProtocolDetailsV1 {
|
||||
|
||||
/// Digests of the KEM keys available to this node alongside hashing algorithms used
|
||||
/// for their computation.
|
||||
pub kem_keys: HashMap<LPKEM, HashMap<LPHashFunction, Vec<u8>>>,
|
||||
/// note: digests are hex encoded
|
||||
pub kem_keys: HashMap<LPKEM, HashMap<LPHashFunction, String>>,
|
||||
|
||||
/// Digests of the signing keys available to this node alongside hashing algorithms used
|
||||
/// for their computation.
|
||||
pub signing_keys: HashMap<LPSignatureScheme, HashMap<LPHashFunction, Vec<u8>>>,
|
||||
/// note: digests are hex encoded
|
||||
pub signing_keys: HashMap<LPSignatureScheme, HashMap<LPHashFunction, String>>,
|
||||
}
|
||||
|
||||
impl LewesProtocolDetailsV1 {
|
||||
fn decode_digests(
|
||||
digests: &HashMap<LPHashFunction, String>,
|
||||
) -> Result<HashMap<HashFunction, Vec<u8>>, MalformedLPData> {
|
||||
let mut kem_digests = HashMap::new();
|
||||
for (hash_function, digest) in digests {
|
||||
let digest = hex::decode(digest).map_err(|source| MalformedLPData::MalformedHash {
|
||||
value: digest.to_string(),
|
||||
source,
|
||||
})?;
|
||||
kem_digests.insert((*hash_function).try_into()?, digest);
|
||||
}
|
||||
Ok(kem_digests)
|
||||
}
|
||||
|
||||
pub fn kem_keys(
|
||||
&self,
|
||||
) -> Result<HashMap<KEM, HashMap<HashFunction, Vec<u8>>>, MalformedLPData> {
|
||||
let mut kem_keys = HashMap::new();
|
||||
for (kem, digests) in &self.kem_keys {
|
||||
let kem_digests = Self::decode_digests(digests)?;
|
||||
kem_keys.insert((*kem).try_into()?, kem_digests);
|
||||
}
|
||||
Ok(kem_keys)
|
||||
}
|
||||
|
||||
pub fn signing_keys(
|
||||
&self,
|
||||
) -> Result<HashMap<SignatureScheme, HashMap<HashFunction, Vec<u8>>>, MalformedLPData> {
|
||||
let mut signing_keys = HashMap::new();
|
||||
for (signature_scheme, digests) in &self.signing_keys {
|
||||
let kem_digests = Self::decode_digests(digests)?;
|
||||
signing_keys.insert((*signature_scheme).try_into()?, kem_digests);
|
||||
}
|
||||
Ok(signing_keys)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert map of digests from `nym_node_requests` types into `nym-api-requests` types
|
||||
fn translate_digests(
|
||||
digests: HashMap<nym_node_requests::api::v1::lewes_protocol::models::LPHashFunction, Vec<u8>>,
|
||||
) -> HashMap<LPHashFunction, Vec<u8>> {
|
||||
digests: HashMap<nym_node_requests::api::v1::lewes_protocol::models::LPHashFunction, String>,
|
||||
) -> HashMap<LPHashFunction, String> {
|
||||
digests
|
||||
.into_iter()
|
||||
.map(|(hash_fn, digest)| (hash_fn.into(), digest))
|
||||
@@ -244,6 +285,7 @@ fn translate_digests(
|
||||
ToSchema,
|
||||
)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
#[non_exhaustive]
|
||||
pub enum LPKEM {
|
||||
MlKem768,
|
||||
XWing,
|
||||
@@ -267,6 +309,7 @@ pub enum LPKEM {
|
||||
ToSchema,
|
||||
)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
#[non_exhaustive]
|
||||
pub enum LPHashFunction {
|
||||
Blake3,
|
||||
Shake128,
|
||||
@@ -290,6 +333,7 @@ pub enum LPHashFunction {
|
||||
ToSchema,
|
||||
)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
#[non_exhaustive]
|
||||
pub enum LPSignatureScheme {
|
||||
Ed25519,
|
||||
}
|
||||
@@ -469,32 +513,59 @@ impl From<nym_node_requests::api::v1::lewes_protocol::models::LPSignatureScheme>
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LPKEM> for KEM {
|
||||
fn from(value: LPKEM) -> Self {
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MalformedLPData {
|
||||
#[error("{value} does not correspond to any valid LP KEM")]
|
||||
UnknownLpKEM { value: LPKEM },
|
||||
|
||||
#[error("{value} does not correspond to any valid LP Signature Scheme")]
|
||||
UnknownLpSignatureScheme { value: LPSignatureScheme },
|
||||
|
||||
#[error("{value} does not correspond to any valid LP Hash Function")]
|
||||
UnknownLpHashFunction { value: LPHashFunction },
|
||||
|
||||
#[error("{value} is not a valid hex encoded hash: {source}")]
|
||||
MalformedHash {
|
||||
value: String,
|
||||
source: hex::FromHexError,
|
||||
},
|
||||
}
|
||||
|
||||
impl TryFrom<LPKEM> for KEM {
|
||||
type Error = MalformedLPData;
|
||||
fn try_from(value: LPKEM) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
LPKEM::MlKem768 => KEM::MlKem768,
|
||||
LPKEM::XWing => KEM::XWing,
|
||||
LPKEM::X25519 => KEM::X25519,
|
||||
LPKEM::McEliece => KEM::McEliece,
|
||||
LPKEM::MlKem768 => Ok(KEM::MlKem768),
|
||||
LPKEM::XWing => Ok(KEM::XWing),
|
||||
LPKEM::X25519 => Ok(KEM::X25519),
|
||||
LPKEM::McEliece => Ok(KEM::McEliece),
|
||||
// TODO: for backwards compatibility once variants within the LP crate change
|
||||
// other => Err(MalformedLPData::UnknownLpKEM { value: other }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LPHashFunction> for HashFunction {
|
||||
fn from(value: LPHashFunction) -> Self {
|
||||
impl TryFrom<LPHashFunction> for HashFunction {
|
||||
type Error = MalformedLPData;
|
||||
fn try_from(value: LPHashFunction) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
LPHashFunction::Blake3 => HashFunction::Blake3,
|
||||
LPHashFunction::Shake128 => HashFunction::Shake128,
|
||||
LPHashFunction::Shake256 => HashFunction::Shake256,
|
||||
LPHashFunction::Sha256 => HashFunction::SHA256,
|
||||
LPHashFunction::Blake3 => Ok(HashFunction::Blake3),
|
||||
LPHashFunction::Shake128 => Ok(HashFunction::Shake128),
|
||||
LPHashFunction::Shake256 => Ok(HashFunction::Shake256),
|
||||
LPHashFunction::Sha256 => Ok(HashFunction::SHA256),
|
||||
// TODO: for backwards compatibility once variants within the LP crate change
|
||||
// other => Err(MalformedLPData::UnknownLpHashFunction { value: other }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LPSignatureScheme> for SignatureScheme {
|
||||
fn from(value: LPSignatureScheme) -> Self {
|
||||
impl TryFrom<LPSignatureScheme> for SignatureScheme {
|
||||
type Error = MalformedLPData;
|
||||
fn try_from(value: LPSignatureScheme) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
LPSignatureScheme::Ed25519 => SignatureScheme::Ed25519,
|
||||
LPSignatureScheme::Ed25519 => Ok(SignatureScheme::Ed25519),
|
||||
// TODO: for backwards compatibility once variants within the LP crate change
|
||||
// other => Err(MalformedLPData::UnknownLpSignatureScheme { value: other }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,10 +262,16 @@ pub fn mock_nym_node_description(seed: u64) -> NymNodeDescriptionV2 {
|
||||
let mut kem_hashes = std::collections::HashMap::new();
|
||||
let mut signing_keys_hashes = std::collections::HashMap::new();
|
||||
|
||||
kem_hashes.insert(LPHashFunction::Sha256, vec![(seed % 256) as u8; 32]);
|
||||
kem_hashes.insert(
|
||||
LPHashFunction::Sha256,
|
||||
hex::encode([(seed % 256) as u8; 32]),
|
||||
);
|
||||
kem_hashes_wrapper.insert(LPKEM::X25519, kem_hashes);
|
||||
|
||||
signing_keys_hashes.insert(LPHashFunction::Sha256, vec![(seed % 256) as u8; 32]);
|
||||
signing_keys_hashes.insert(
|
||||
LPHashFunction::Sha256,
|
||||
hex::encode([(seed % 256) as u8; 32]),
|
||||
);
|
||||
signing_keys_hashes_wrapper.insert(LPSignatureScheme::Ed25519, signing_keys_hashes);
|
||||
|
||||
NymNodeDescriptionV2 {
|
||||
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO transaction\n (hash, height, index, success, messages, memo, signatures, signer_infos, fee, gas_wanted, gas_used, raw_log, logs, events)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)\n ON CONFLICT (hash) DO UPDATE\n SET height = excluded.height,\n index = excluded.index,\n success = excluded.success,\n messages = excluded.messages,\n memo = excluded.memo,\n signatures = excluded.signatures,\n signer_infos = excluded.signer_infos,\n fee = excluded.fee,\n gas_wanted = excluded.gas_wanted,\n gas_used = excluded.gas_used,\n raw_log = excluded.raw_log,\n logs = excluded.logs,\n events = excluded.events\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int8",
|
||||
"Int4",
|
||||
"Bool",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"TextArray",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Text",
|
||||
"Jsonb",
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "08f4e54ac24fccd54f4208797b3749e457f8cd4ba3d7d906a7ab3bf5b4e7dc9c"
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO validator (consensus_address, consensus_pubkey)\n VALUES ($1, $2)\n ON CONFLICT DO NOTHING\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0d3709efacf763b06bf14803bb803b5ee5b27879b0026bb0480b3f2722318a75"
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM pre_commit WHERE height < $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1c2fb0e9ffceca21ef8dbea19b116422b1f723d0a316314b50c43c8b29f8891d"
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT height\n FROM block\n ORDER BY height ASC\n LIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "height",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "2561fb016951ea4cd29e43fb9a4a93e944b0d44ed1f7c1036f306e34372da11c"
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE metadata SET last_processed_height = GREATEST(last_processed_height, $1)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2679cdf11fa66c7920678cde860c57402119ec7c3aae731b0da831327301466f"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user