Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6598d677da | |||
| e736a01ecc | |||
| a708fa2d4a | |||
| a512217382 | |||
| 086611c7ac | |||
| 05d6652177 | |||
| 9c514fe3b7 | |||
| 924160b3e7 | |||
| 23d14b60de | |||
| a4b47ef3a5 | |||
| 47303bcf48 | |||
| 60917ec9e7 | |||
| f616b3c15a |
@@ -1,13 +1,6 @@
|
||||
name: ci-nym-wallet-rust
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'nym-wallet/**'
|
||||
- 'common/**'
|
||||
- 'contracts/vesting/**'
|
||||
- 'nym-api/nym-api-requests/**'
|
||||
- '.github/workflows/ci-nym-wallet-rust.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'nym-wallet/**'
|
||||
|
||||
@@ -20,11 +20,6 @@ jobs:
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- run: |
|
||||
cargo install --list
|
||||
rustup show
|
||||
which cargo
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
@@ -49,16 +44,11 @@ jobs:
|
||||
- name: Install wasm-bindgen-cli
|
||||
run: cargo install wasm-bindgen-cli
|
||||
|
||||
- run: |
|
||||
cargo install --list
|
||||
rustup show
|
||||
which cargo
|
||||
- name: "Build"
|
||||
run: make sdk-wasm-build
|
||||
|
||||
# - name: "Build"
|
||||
# run: make sdk-wasm-build
|
||||
- name: "Test"
|
||||
run: make sdk-wasm-test
|
||||
|
||||
# - name: "Test"
|
||||
# run: make sdk-wasm-test
|
||||
|
||||
# - name: "Lint"
|
||||
# run: make sdk-wasm-lint
|
||||
- name: "Lint"
|
||||
run: make sdk-wasm-lint
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Build and upload Network monitor container to harbor.nymte.ch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
WORKING_DIRECTORY: "."
|
||||
CONTAINER_NAME: "network-monitor"
|
||||
|
||||
jobs:
|
||||
build-container:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
steps:
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: harbor.nymte.ch
|
||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||
password: ${{ secrets.HARBOR_ROBOT_SECRET }}
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure git identity
|
||||
run: |
|
||||
git config --global user.email "lawrence@nymtech.net"
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from package.json
|
||||
uses: sergeysova/jq-action@v2
|
||||
id: get_version
|
||||
with:
|
||||
cmd: jq -r '.version' ${{ env.WORKING_DIRECTORY }}/package.json
|
||||
|
||||
- name: Check if tag exists
|
||||
run: |
|
||||
if git rev-parse ${{ steps.get_version.outputs.value }} >/dev/null 2>&1; then
|
||||
echo "Tag ${{ steps.get_version.outputs.value }} already exists"
|
||||
fi
|
||||
|
||||
- name: Remove existing tag if exists
|
||||
run: |
|
||||
if git rev-parse ${{ steps.get_version.outputs.value }} >/dev/null 2>&1; then
|
||||
git push --delete origin ${{ steps.get_version.outputs.value }}
|
||||
git tag -d ${{ steps.get_version.outputs.value }}
|
||||
fi
|
||||
|
||||
- name: Create tag
|
||||
run: |
|
||||
git tag -a ${{ steps.get_version.outputs.value }} -m "Version ${{ steps.get_version.outputs.value }}"
|
||||
git push origin ${{ steps.get_version.outputs.value }}
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build -f nym-network-monitor.dockerfile ${{ env.WORKING_DIRECTORY }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.value }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
Generated
+5
-24
@@ -4910,7 +4910,7 @@ dependencies = [
|
||||
"nym-network-defaults",
|
||||
"rand",
|
||||
"serde",
|
||||
"strum 0.25.0",
|
||||
"strum 0.26.3",
|
||||
"thiserror",
|
||||
"time",
|
||||
]
|
||||
@@ -6186,7 +6186,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.8",
|
||||
"strum 0.25.0",
|
||||
"strum 0.26.3",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"ts-rs",
|
||||
@@ -8576,20 +8576,14 @@ version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
||||
dependencies = [
|
||||
"strum_macros 0.25.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
dependencies = [
|
||||
"strum_macros 0.26.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
@@ -8617,19 +8611,6 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.25.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.26.4"
|
||||
|
||||
+1
-1
@@ -288,7 +288,7 @@ sha2 = "0.10.8"
|
||||
si-scale = "0.2.3"
|
||||
sphinx-packet = "0.1.1"
|
||||
sqlx = "0.6.3"
|
||||
strum = "0.25"
|
||||
strum = "0.26"
|
||||
subtle-encoding = "0.5"
|
||||
syn = "1"
|
||||
sysinfo = "0.30.12"
|
||||
|
||||
@@ -293,7 +293,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
// as we need to be able to write the request and read the subsequent response
|
||||
async fn send_websocket_message(
|
||||
&mut self,
|
||||
msg: Message,
|
||||
msg: impl Into<Message>,
|
||||
) -> Result<ServerResponse, GatewayClientError> {
|
||||
let should_restart_mixnet_listener = if self.connection.is_partially_delegated() {
|
||||
self.recover_socket_connection().await?;
|
||||
@@ -307,7 +307,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
SocketState::NotConnected => return Err(GatewayClientError::ConnectionNotEstablished),
|
||||
_ => return Err(GatewayClientError::ConnectionInInvalidState),
|
||||
};
|
||||
conn.send(msg).await?;
|
||||
conn.send(msg.into()).await?;
|
||||
let response = self.read_control_response().await;
|
||||
|
||||
if should_restart_mixnet_listener {
|
||||
@@ -481,8 +481,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
encrypted_address,
|
||||
iv,
|
||||
self.cfg.bandwidth.require_tickets,
|
||||
)
|
||||
.into();
|
||||
);
|
||||
|
||||
match self.send_websocket_message(msg).await? {
|
||||
ServerResponse::Authenticate {
|
||||
@@ -532,6 +531,21 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_gateway_protocol(&mut self) -> Result<u8, GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
|
||||
match self
|
||||
.send_websocket_message(ClientControlRequest::SupportedProtocol {})
|
||||
.await?
|
||||
{
|
||||
ServerResponse::SupportedProtocol { version } => Ok(version),
|
||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||
_ => Err(GatewayClientError::UnexpectedResponse),
|
||||
}
|
||||
}
|
||||
|
||||
async fn claim_ecash_bandwidth(
|
||||
&mut self,
|
||||
credential: CredentialSpendingData,
|
||||
@@ -543,8 +557,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
credential,
|
||||
self.shared_key.as_ref().unwrap(),
|
||||
iv,
|
||||
)
|
||||
.into();
|
||||
);
|
||||
let bandwidth_remaining = match self.send_websocket_message(msg).await? {
|
||||
ServerResponse::Bandwidth { available_total } => Ok(available_total),
|
||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||
@@ -562,7 +575,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
async fn try_claim_testnet_bandwidth(&mut self) -> Result<(), GatewayClientError> {
|
||||
let msg = ClientControlRequest::ClaimFreeTestnetBandwidth.into();
|
||||
let msg = ClientControlRequest::ClaimFreeTestnetBandwidth;
|
||||
let bandwidth_remaining = match self.send_websocket_message(msg).await? {
|
||||
ServerResponse::Bandwidth { available_total } => Ok(available_total),
|
||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||
|
||||
@@ -20,4 +20,4 @@ thiserror = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "=8.3.1", features = ["build", "git", "gitcl", "rustc", "cargo"] }
|
||||
vergen = { workspace = true, features = ["build", "git", "gitcl", "rustc", "cargo"] }
|
||||
|
||||
@@ -4,11 +4,9 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cosmwasm-schema = { workspace = true }
|
||||
cw4 = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
cw4 = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
serde = { version = "1.0.210", default-features = false, features = ["derive"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
@@ -9,7 +9,7 @@ license = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
bs58 = "0.5.1"
|
||||
bs58 = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmwasm-schema = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
|
||||
@@ -4,15 +4,13 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cosmwasm-schema = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cw-utils = { workspace = true }
|
||||
cw3 = { workspace = true }
|
||||
cw4 = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cosmwasm-schema = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
serde = { version = "1.0.210", default-features = false, features = ["derive"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
@@ -21,7 +21,7 @@ x25519-dalek = { workspace = true, features = ["static_secrets"], optional = tru
|
||||
ed25519-dalek = { workspace = true, features = ["rand_core"], optional = true }
|
||||
rand = { workspace = true, optional = true }
|
||||
serde_bytes = { workspace = true, optional = true }
|
||||
serde = { version = "1.0", optional = true, default-features = false, features = ["derive"] }
|
||||
serde = { workspace = true, features = ["derive"], optional = true }
|
||||
subtle-encoding = { workspace = true, features = ["bech32-preview"] }
|
||||
thiserror = { workspace = true }
|
||||
zeroize = { workspace = true, optional = true, features = ["zeroize_derive"] }
|
||||
|
||||
@@ -190,6 +190,7 @@ pub enum ClientControlRequest {
|
||||
iv: Vec<u8>,
|
||||
},
|
||||
ClaimFreeTestnetBandwidth,
|
||||
SupportedProtocol {},
|
||||
}
|
||||
|
||||
impl ClientControlRequest {
|
||||
@@ -229,6 +230,7 @@ impl ClientControlRequest {
|
||||
ClientControlRequest::ClaimFreeTestnetBandwidth => {
|
||||
"ClaimFreeTestnetBandwidth".to_string()
|
||||
}
|
||||
ClientControlRequest::SupportedProtocol { .. } => "SupportedProtocol".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +274,15 @@ impl TryFrom<String> for ClientControlRequest {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(msg: String) -> Result<Self, Self::Error> {
|
||||
serde_json::from_str(&msg)
|
||||
msg.parse()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ClientControlRequest {
|
||||
type Err = serde_json::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
serde_json::from_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,6 +314,9 @@ pub enum ServerResponse {
|
||||
Send {
|
||||
remaining_bandwidth: i64,
|
||||
},
|
||||
SupportedProtocol {
|
||||
version: u8,
|
||||
},
|
||||
// Generic error
|
||||
Error {
|
||||
message: String,
|
||||
@@ -324,6 +337,7 @@ impl ServerResponse {
|
||||
ServerResponse::Send { .. } => "Send".to_string(),
|
||||
ServerResponse::Error { .. } => "Error".to_string(),
|
||||
ServerResponse::TypedError { .. } => "TypedError".to_string(),
|
||||
ServerResponse::SupportedProtocol { .. } => "SupportedProtocol".to_string(),
|
||||
}
|
||||
}
|
||||
pub fn new_error<S: Into<String>>(msg: S) -> Self {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
CREATE TABLE clients (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
client_type TEXT NOT NULL CHECK(client_type IN ('entry_mixnet', 'exit_mixnet', 'entry_wireguard', 'exit_wireguard'))
|
||||
);
|
||||
|
||||
INSERT INTO clients (id, client_type)
|
||||
SELECT id, 'entry_mixnet'
|
||||
FROM shared_keys;
|
||||
|
||||
CREATE TABLE shared_keys_tmp (
|
||||
client_id INTEGER NOT NULL PRIMARY KEY REFERENCES clients(id),
|
||||
client_address_bs58 TEXT NOT NULL UNIQUE,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58 TEXT NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO shared_keys_tmp (client_id, client_address_bs58, derived_aes128_ctr_blake3_hmac_keys_bs58)
|
||||
SELECT id as client_id, client_address_bs58, derived_aes128_ctr_blake3_hmac_keys_bs58 FROM shared_keys;
|
||||
|
||||
CREATE TABLE available_bandwidth_tmp (
|
||||
client_id INTEGER NOT NULL PRIMARY KEY REFERENCES clients(id),
|
||||
available INTEGER NOT NULL,
|
||||
expiration TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
INSERT INTO available_bandwidth_tmp
|
||||
SELECT * FROM available_bandwidth;
|
||||
|
||||
DROP TABLE available_bandwidth;
|
||||
ALTER TABLE available_bandwidth_tmp RENAME TO available_bandwidth;
|
||||
|
||||
CREATE TABLE received_ticket_tmp (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
client_id INTEGER NOT NULL REFERENCES clients(id),
|
||||
received_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
rejected BOOLEAN
|
||||
);
|
||||
|
||||
INSERT INTO received_ticket_tmp
|
||||
SELECT * FROM received_ticket;
|
||||
|
||||
DROP INDEX received_ticket_index;
|
||||
CREATE INDEX received_ticket_index ON received_ticket_tmp (received_at);
|
||||
|
||||
-- received tickets that are in the process of verifying
|
||||
CREATE TABLE ticket_data_tmp (
|
||||
ticket_id INTEGER NOT NULL PRIMARY KEY REFERENCES received_ticket_tmp(id),
|
||||
|
||||
-- serial_number, alongside the entire row, will get purged after redemption is complete
|
||||
serial_number BLOB NOT NULL UNIQUE,
|
||||
|
||||
-- data will get purged after 80% of signers verifies it
|
||||
data BLOB
|
||||
);
|
||||
|
||||
INSERT INTO ticket_data_tmp
|
||||
SELECT * FROM ticket_data;
|
||||
|
||||
DROP TABLE ticket_data;
|
||||
ALTER TABLE ticket_data_tmp RENAME TO ticket_data;
|
||||
|
||||
-- result of a verification from a single signer (API)
|
||||
CREATE TABLE ticket_verification_tmp (
|
||||
ticket_id INTEGER NOT NULL REFERENCES received_ticket_tmp(id),
|
||||
signer_id INTEGER NOT NULL,
|
||||
verified_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
accepted BOOLEAN NOT NULL,
|
||||
|
||||
PRIMARY KEY (ticket_id, signer_id)
|
||||
);
|
||||
|
||||
DROP INDEX ticket_verification_index;
|
||||
CREATE INDEX ticket_verification_index ON ticket_verification_tmp (ticket_id);
|
||||
|
||||
DROP TABLE ticket_verification;
|
||||
ALTER TABLE ticket_verification_tmp RENAME TO ticket_verification;
|
||||
|
||||
-- verified tickets that are yet to be redeemed
|
||||
CREATE TABLE verified_tickets_tmp (
|
||||
ticket_id INTEGER NOT NULL PRIMARY KEY REFERENCES received_ticket_tmp(id),
|
||||
proposal_id INTEGER REFERENCES redemption_proposals(proposal_id)
|
||||
);
|
||||
|
||||
DROP INDEX verified_tickets_index;
|
||||
CREATE INDEX verified_tickets_index ON verified_tickets_tmp (proposal_id);
|
||||
|
||||
DROP TABLE verified_tickets;
|
||||
ALTER TABLE verified_tickets_tmp RENAME TO verified_tickets;
|
||||
|
||||
DROP TABLE received_ticket;
|
||||
ALTER TABLE received_ticket_tmp RENAME TO received_ticket;
|
||||
|
||||
DROP TABLE shared_keys;
|
||||
ALTER TABLE shared_keys_tmp RENAME TO shared_keys;
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::models::Client;
|
||||
|
||||
#[derive(Debug, PartialEq, sqlx::Type)]
|
||||
#[sqlx(type_name = "TEXT")] // SQLite TEXT type
|
||||
#[sqlx(rename_all = "snake_case")]
|
||||
pub enum ClientType {
|
||||
EntryMixnet,
|
||||
ExitMixnet,
|
||||
EntryWireguard,
|
||||
ExitWireguard,
|
||||
}
|
||||
|
||||
impl FromStr for ClientType {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"entry_mixnet" => Ok(ClientType::EntryMixnet),
|
||||
"exit_mixnet" => Ok(ClientType::ExitMixnet),
|
||||
"entry_wireguard" => Ok(ClientType::EntryWireguard),
|
||||
"exit_wireguard" => Ok(ClientType::ExitWireguard),
|
||||
_ => Err("Invalid client type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ClientType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let s = match self {
|
||||
ClientType::EntryMixnet => "entry_mixnet",
|
||||
ClientType::ExitMixnet => "exit_mixnet",
|
||||
ClientType::EntryWireguard => "entry_wireguard",
|
||||
ClientType::ExitWireguard => "exit_wireguard",
|
||||
};
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ClientManager {
|
||||
connection_pool: sqlx::SqlitePool,
|
||||
}
|
||||
|
||||
impl ClientManager {
|
||||
/// Creates new instance of the `ClientManager` with the provided sqlite connection pool.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `connection_pool`: database connection pool to use.
|
||||
pub(crate) fn new(connection_pool: sqlx::SqlitePool) -> Self {
|
||||
ClientManager { connection_pool }
|
||||
}
|
||||
|
||||
/// Inserts new client to the storage, specifying its type.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_type`: Type of the client that gets inserted
|
||||
pub(crate) async fn insert_client(&self, client_type: ClientType) -> Result<i64, sqlx::Error> {
|
||||
let client_id = sqlx::query!("INSERT INTO clients(client_type) VALUES (?)", client_type)
|
||||
.execute(&self.connection_pool)
|
||||
.await?
|
||||
.last_insert_rowid();
|
||||
Ok(client_id)
|
||||
}
|
||||
|
||||
/// Tries to retrieve a particular client.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `id`: The client id
|
||||
pub(crate) async fn get_client(&self, id: i64) -> Result<Option<Client>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
Client,
|
||||
r#"
|
||||
SELECT id, client_type as "client_type: ClientType"
|
||||
FROM clients
|
||||
WHERE id = ?
|
||||
"#,
|
||||
id
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,12 @@
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bandwidth::BandwidthManager;
|
||||
use clients::{ClientManager, ClientType};
|
||||
use error::StorageError;
|
||||
use inboxes::InboxManager;
|
||||
use models::{
|
||||
PersistedBandwidth, PersistedSharedKeys, RedemptionProposal, StoredMessage, VerifiedTicket,
|
||||
WireguardPeer,
|
||||
Client, PersistedBandwidth, PersistedSharedKeys, RedemptionProposal, StoredMessage,
|
||||
VerifiedTicket, WireguardPeer,
|
||||
};
|
||||
use nym_credentials_interface::ClientTicket;
|
||||
use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
@@ -20,6 +21,7 @@ use time::OffsetDateTime;
|
||||
use tracing::{debug, error};
|
||||
|
||||
pub mod bandwidth;
|
||||
mod clients;
|
||||
pub mod error;
|
||||
mod inboxes;
|
||||
pub mod models;
|
||||
@@ -29,7 +31,7 @@ mod wireguard_peers;
|
||||
|
||||
#[async_trait]
|
||||
pub trait Storage: Send + Sync {
|
||||
async fn get_client_id(
|
||||
async fn get_mixnet_client_id(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<i64, StorageError>;
|
||||
@@ -39,7 +41,7 @@ pub trait Storage: Send + Sync {
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_address`: address of the client
|
||||
/// * `client_address`: base58-encoded address of the client
|
||||
/// * `shared_keys`: shared encryption (AES128CTR) and mac (hmac-blake3) derived shared keys to store.
|
||||
async fn insert_shared_keys(
|
||||
&self,
|
||||
@@ -70,6 +72,14 @@ pub trait Storage: Send + Sync {
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<(), StorageError>;
|
||||
|
||||
/// Tries to retrieve a particular client.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_id`: id of the client
|
||||
#[allow(dead_code)]
|
||||
async fn get_client(&self, client_id: i64) -> Result<Option<Client>, StorageError>;
|
||||
|
||||
/// Inserts new message to the storage for an offline client for future retrieval.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -246,6 +256,7 @@ pub trait Storage: Send + Sync {
|
||||
// note that clone here is fine as upon cloning the same underlying pool will be used
|
||||
#[derive(Clone)]
|
||||
pub struct PersistentStorage {
|
||||
client_manager: ClientManager,
|
||||
shared_key_manager: SharedKeysManager,
|
||||
inbox_manager: InboxManager,
|
||||
bandwidth_manager: BandwidthManager,
|
||||
@@ -294,6 +305,7 @@ impl PersistentStorage {
|
||||
|
||||
// the cloning here are cheap as connection pool is stored behind an Arc
|
||||
Ok(PersistentStorage {
|
||||
client_manager: clients::ClientManager::new(connection_pool.clone()),
|
||||
wireguard_peer_manager: wireguard_peers::WgPeerManager::new(connection_pool.clone()),
|
||||
shared_key_manager: SharedKeysManager::new(connection_pool.clone()),
|
||||
inbox_manager: InboxManager::new(connection_pool.clone(), message_retrieval_limit),
|
||||
@@ -305,7 +317,7 @@ impl PersistentStorage {
|
||||
|
||||
#[async_trait]
|
||||
impl Storage for PersistentStorage {
|
||||
async fn get_client_id(
|
||||
async fn get_mixnet_client_id(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<i64, StorageError> {
|
||||
@@ -321,8 +333,12 @@ impl Storage for PersistentStorage {
|
||||
shared_keys: &SharedKeys,
|
||||
) -> Result<i64, StorageError> {
|
||||
let client_id = self
|
||||
.shared_key_manager
|
||||
.client_manager
|
||||
.insert_client(ClientType::EntryMixnet)
|
||||
.await?;
|
||||
self.shared_key_manager
|
||||
.insert_shared_keys(
|
||||
client_id,
|
||||
client_address.as_base58_string(),
|
||||
shared_keys.to_base58_string(),
|
||||
)
|
||||
@@ -352,6 +368,11 @@ impl Storage for PersistentStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_client(&self, client_id: i64) -> Result<Option<Client>, StorageError> {
|
||||
let client = self.client_manager.get_client(client_id).await?;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
async fn store_message(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
|
||||
@@ -6,9 +6,14 @@ use nym_credentials_interface::{AvailableBandwidth, ClientTicket, CredentialSpen
|
||||
use sqlx::FromRow;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub struct Client {
|
||||
pub id: i64,
|
||||
pub client_type: crate::clients::ClientType,
|
||||
}
|
||||
|
||||
pub struct PersistedSharedKeys {
|
||||
#[allow(dead_code)]
|
||||
pub id: i64,
|
||||
pub client_id: i64,
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub client_address_bs58: String,
|
||||
|
||||
@@ -20,12 +20,12 @@ impl SharedKeysManager {
|
||||
|
||||
pub(crate) async fn client_id(&self, client_address_bs58: &str) -> Result<i64, sqlx::Error> {
|
||||
let client_id = sqlx::query!(
|
||||
"SELECT id FROM shared_keys WHERE client_address_bs58 = ?",
|
||||
"SELECT client_id FROM shared_keys WHERE client_address_bs58 = ?",
|
||||
client_address_bs58
|
||||
)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await?
|
||||
.id;
|
||||
.client_id;
|
||||
Ok(client_id)
|
||||
}
|
||||
|
||||
@@ -34,26 +34,30 @@ impl SharedKeysManager {
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `shared_keys`: shared encryption (AES128CTR) and mac (hmac-blake3) derived shared keys to store.
|
||||
/// * `client_id`: The client id for which the shared keys are stored
|
||||
/// * `client_address_bs58`: base58-encoded address of the client
|
||||
/// * `derived_aes128_ctr_blake3_hmac_keys_bs58`: shared encryption (AES128CTR) and mac (hmac-blake3) derived shared keys to store.
|
||||
pub(crate) async fn insert_shared_keys(
|
||||
&self,
|
||||
client_id: i64,
|
||||
client_address_bs58: String,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58: String,
|
||||
) -> Result<i64, sqlx::Error> {
|
||||
) -> Result<(), sqlx::Error> {
|
||||
// https://stackoverflow.com/a/20310838
|
||||
// we don't want to be using `INSERT OR REPLACE INTO` due to the foreign key on `available_bandwidth` if the entry already exists
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT OR IGNORE INTO shared_keys(client_address_bs58, derived_aes128_ctr_blake3_hmac_keys_bs58) VALUES (?, ?);
|
||||
INSERT OR IGNORE INTO shared_keys(client_id, client_address_bs58, derived_aes128_ctr_blake3_hmac_keys_bs58) VALUES (?, ?, ?);
|
||||
UPDATE shared_keys SET derived_aes128_ctr_blake3_hmac_keys_bs58 = ? WHERE client_address_bs58 = ?
|
||||
"#,
|
||||
client_id,
|
||||
client_address_bs58,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
client_address_bs58,
|
||||
).execute(&self.connection_pool).await?;
|
||||
|
||||
self.client_id(&client_address_bs58).await
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tries to retrieve shared keys stored for the particular client.
|
||||
|
||||
@@ -9,7 +9,7 @@ repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
rand = { workspace = true }
|
||||
serde = { version = "1.0", optional = true, default-features = false, features = ["derive"] }
|
||||
serde = { workspace = true, features = ["derive"], optional = true }
|
||||
generic-array = { workspace = true, optional = true, features = ["serde"] }
|
||||
thiserror = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
use strum::{Display, EnumString, EnumVariantNames};
|
||||
use strum::{Display, EnumString, VariantNames};
|
||||
|
||||
#[cfg(feature = "generate-ts")]
|
||||
use ts_rs::{Dependency, TS};
|
||||
@@ -28,7 +28,7 @@ use ts_rs::{Dependency, TS};
|
||||
Clone,
|
||||
Debug,
|
||||
EnumString,
|
||||
EnumVariantNames,
|
||||
VariantNames,
|
||||
PartialEq,
|
||||
Eq,
|
||||
JsonSchema,
|
||||
|
||||
@@ -2,6 +2,31 @@
|
||||
|
||||
This page displays a full list of all the changes during our release cycle from [`v2024.3-eclipse`](https://github.com/nymtech/nym/blob/nym-binaries-v2024.3-eclipse/CHANGELOG.md) onwards. Operators can find here the newest updates together with links to relevant documentation. The list is sorted so that the newest changes appear first.
|
||||
|
||||
## `nym-node` patch from `release/2024.10-caramello`
|
||||
|
||||
- [Patch release binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2024.10-caramello-patch)
|
||||
|
||||
```sh
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2024-09-16T15:00:41.019107021Z
|
||||
Build Version: 1.1.7
|
||||
Commit SHA: 65c8982cab0ff3a1154966e7d61956cb42a065fc
|
||||
Commit Date: 2024-09-16T15:59:34.000000000+02:00
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.81.0
|
||||
rustc Channel: stable
|
||||
cargo Profile: release
|
||||
```
|
||||
|
||||
This patch fixes [`v202410-caramello`](#v202410-caramello) release [bug](#known-bugs--undone-features) where one of the used dependencies - [`DefGuard`](https://github.com/DefGuard/defguard/issues/619), was failing.
|
||||
|
||||
Updating to this patched version and running `nym-node --mode exit-gateway` with `--wireguard-enabled true` should result in a smooth node start without the `defguard_wireguard` error, occuring to some operators before:
|
||||
```sh
|
||||
/home/ubuntu/.cargo/registry/src/index.crates.io-6f17d22bba15001f/defguard_wireguard_rs-0.4.2/src/netlink.rs:155: Serialized netlink packet (23240 bytes) larger than maximum size 12288: NetlinkMessage.
|
||||
```
|
||||
|
||||
This release is a patch only, there are no additional features, everything else stays the same like in the latest release [`v202410-caramello`](#v202410-caramello).
|
||||
|
||||
## `v2024.10-caramello`
|
||||
|
||||
- [Release binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2024.10-caramello)
|
||||
@@ -106,7 +131,7 @@ The client was removed from the peer list after 3 days of inactivity. Upon re-co
|
||||
|
||||
- [Feature/merge back](https://github.com/nymtech/nym/pull/4710): Merge back from the release branch the changes that fix the `nym-node` upgrades
|
||||
|
||||
- [Removed mixnode/gateway config migration code and disabled cli without explicit flag](https://github.com/nymtech/nym/pull/4706): `nym-gateway` and `nym-mixnode` commands now won't do anything without explicit `--force-run` to bypass the deprecation. The next step, in say a month or so, is to completely remove all `cli` related things.
|
||||
- [Removed mixnode/gateway config migration code and disabled cli without explicit flag](https://github.com/nymtech/nym/pull/4706): Commands for archived / legacy binaries `nym-gateway` and `nym-mixnode` won't do anything without explicit `--force-run` to bypass the deprecation. The next step, in say a month or so, is to completely remove all `cli` related things.
|
||||
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
- Verify that the `nym-gateway` binary and `nym-mixnode` binary commands return the _error message_ stating to update to nym-node
|
||||
@@ -118,12 +143,12 @@ The client was removed from the peer list after 3 days of inactivity. Upon re-co
|
||||
- [Handle clients with different versions in IPR](https://github.com/nymtech/nym/pull/4723): Allow the IPR to handle clients connecting both using `v6` and `v7`, independently. The motivation is that we want to be able to roll out a API version change gradually for VPN clients without breaking backwards compatibility. The main feature on the new `v7` format that is not yet used, is that it adds signatures for connect/disconnect.
|
||||
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
Run the same command (using same gateways deployed from this PR) on different versions of the `nym-vpn-cli`.
|
||||
Run the same command (using same gateways deployed from this PR) on different versions of the `nym-vpn-cli`.
|
||||
|
||||
Example:
|
||||
Example:
|
||||
```sh
|
||||
~/nym-vpn-core-v0.1.10_macos_universal ❯ sudo -E ./nym-vpn-cli -c ../qa.env run --entry-gateway-id $entry_gateway --exit-gateway-id $exit_gateway --enable-two-hop
|
||||
|
||||
|
||||
~/nym-vpn-core-v0.1.11-dev_macos_universal ❯ sudo -E ./nym-vpn-cli -c ../qa.env run --entry-gateway-id $entry_gateway --exit-gateway-id $exit_gateway --enable-two-hop
|
||||
```
|
||||
~~~
|
||||
@@ -164,11 +189,11 @@ Example:
|
||||
|
||||
- [Feature/vesting purge plus ranged cost params](https://github.com/nymtech/nym/pull/4716): Combines [\#4715](https://github.com/nymtech/nym/pull/4715) and [\#4711](https://github.com/nymtech/nym/pull/4711) into one.
|
||||
- Disables all non-essential operations on the vesting contract => you can no longer bond mixnodes/make delegations/etc. (you can still, however, withdraw your vested tokens and so on)
|
||||
- Introduces admin-controlled minimum (and maximum) profit margin and interval operating costs.
|
||||
- Introduces admin-controlled minimum (and maximum) profit margin and interval operating costs.
|
||||
- both contracts have to be migrated **at the same time**. ideally within the same transaction
|
||||
- mixnet contract migration is not allowed (and will fail) if there are any pending actions involving vesting tokens, like delegating, increasing pledge, etc
|
||||
|
||||
- [Bump braces from `3.0.2` to `3.0.3` in `/nym-wallet/webdriver`](https://github.com/nymtech/nym/pull/4709): Bumps [braces](https://github.com/micromatch/braces) from `3.0.2` to `3.0.3`.
|
||||
- [Bump braces from `3.0.2` to `3.0.3` in `/nym-wallet/webdriver`](https://github.com/nymtech/nym/pull/4709): Bumps [braces](https://github.com/micromatch/braces) from `3.0.2` to `3.0.3`.
|
||||
|
||||
### Bugfix
|
||||
|
||||
@@ -176,29 +201,29 @@ Example:
|
||||
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
- Building all binaries is ok
|
||||
- Running `cargo fmt` returns no issues
|
||||
- Running `cargo fmt` returns no issues
|
||||
~~~
|
||||
|
||||
- [Fix version 1 not having template correspondent initially](https://github.com/nymtech/nym/pull/4733)
|
||||
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
Tested updating an old `nym-node` version and ensuring it did not throw any errors.
|
||||
Tested updating an old `nym-node` version and ensuring it did not throw any errors.
|
||||
~~~
|
||||
|
||||
- [Bugfix/client registration vol2](https://github.com/nymtech/nym/pull/4856)
|
||||
|
||||
- [Fix tokio error in `1.39`](https://github.com/nymtech/nym/pull/4730):
|
||||
- Bump tokio to `1.39.2`, skipping the issue with `1.39.1`
|
||||
|
||||
|
||||
|
||||
- [Fix (some) feature unification build failures](https://github.com/nymtech/nym/pull/4681): Running a script in the root workspace that builds each crate independently
|
||||
|
||||
~~~admonish example collapsible=true title='The script'
|
||||
```sh
|
||||
#!/bin/bash
|
||||
|
||||
|
||||
packages=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[].name')
|
||||
|
||||
|
||||
# Loop through each package and build
|
||||
for package in $packages; do
|
||||
echo "Building $package"
|
||||
@@ -211,16 +236,16 @@ Tested updating an old `nym-node` version and ensuring it did not throw any erro
|
||||
done
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
- [bugfix: make sure DKG parses data out of events if logs are empty](https://github.com/nymtech/nym/pull/4764): This will be the case on post `0.50` chains
|
||||
|
||||
- [Fix clippy on rustc beta toolchain](https://github.com/nymtech/nym/pull/4746): Fix clippy warnings for rust beta toolchain
|
||||
|
||||
|
||||
- [Fix clippy for beta toolchain](https://github.com/nymtech/nym/pull/4742): Fix beta toolchain clippy by removing unused module
|
||||
- Add `nym-` prefix to `serde-common` crate
|
||||
- Remove ignored `default-features = false` attribute for workspace dependency
|
||||
|
||||
### Crypto
|
||||
### Crypto
|
||||
|
||||
- [Feature Compact Ecash : The One PR](https://github.com/nymtech/nym/pull/4623)
|
||||
|
||||
@@ -234,7 +259,7 @@ Tested updating an old `nym-node` version and ensuring it did not throw any erro
|
||||
|
||||
- Wireguard peers stay connected for longer time, re-connections are also faster
|
||||
|
||||
- Profit margin and operating cost values will be set to the agreed values, the values can be changed in the future through [Nym Operators governance process](https://forum.nymtech.net/t/poll-proposal-for-on-chain-minimum-profit-margin-for-all-nym-nodes/253)
|
||||
- Profit margin and operating cost values are set to the values agreed by operators off-chain vote, the values can be changed in the future through [Nym Operators governance process](https://forum.nymtech.net/t/poll-proposal-for-on-chain-minimum-profit-margin-for-all-nym-nodes/253)
|
||||
```admonish success title=""
|
||||
- Minimum profit margin = 20%
|
||||
- Maximum profit margin = 50%
|
||||
@@ -249,9 +274,10 @@ Tested updating an old `nym-node` version and ensuring it did not throw any erro
|
||||
- Wireguard registration and complete handshake test, to configure see [tasklist below](#operators-tasks)
|
||||
- DNS resolution check, to configure see [tasklist below](#operators-tasks)
|
||||
- Wireguard perfomance > 0.75, to configure see [tasklist below](#operators-tasks)
|
||||
|
||||
- New wallet coming out soon!
|
||||
|
||||
- New [Nym Wallet](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.2.14) is out!
|
||||
- Vesting contract functionalities have been purged, users can only remove tokens from vesting
|
||||
- Migrating from `mixnode` or `gateway` smart contracts to a new unifying `nym-node` smart contract will be available soon using Nym desktop wallet, just like you are used to for bonding and node settings. After this migration all `nym-nodes` will be able to receive delegation and rewards. We will share a step by step guide once this migration will be deployed. No action needed now.
|
||||
|
||||
- [Nym API Check CLI](testing/node-api-check.md) is upgraded according to the latest API endpoints, output is cleaner and more concise.
|
||||
|
||||
@@ -272,7 +298,6 @@ Every `nym-node` should be upgraded to the latest version! Operators can test us
|
||||
- Note: On some VPS this setup may not be enough to get the correct results as some ISPs have their own security groups setup below the individual VPS. In that case a ticket to ISP will have to be issued to open the needed settings. We are working on a template for such ticket.
|
||||
- Setup [reverse proxy and WSS](nodes/proxy-configuration.md) on `nym-node` (Gateways only for the time being)
|
||||
- Don't forget to restart your node - or (preferably using [systemd automation](nodes/configuration.md#systemd)) reload daemon and restart the service
|
||||
- Migrating from `mixnet` or `gateway` smart contracts to a new `nym-node` smart contract will be available soon with an upcoming version of Nym desktop wallet. After this migration all `nym-nodes` will be able to receive delegation. The operators will have to confirm the migration once it's deployed.
|
||||
- Optional: Use [`nym-gateway-probe`](testing/gateway-probe.html) and [NymVPN CLI](https://nymtech.net/developers/nymvpn/cli.html) to test your own Gateway
|
||||
- Optional: Run the script below to measure ping speed of your Gateway and share your results in [Nym Operators channel](https://matrix.to/#/#operators:nymtech.chat)
|
||||
|
||||
@@ -352,6 +377,20 @@ THANK YOU!
|
||||
|
||||
- New `nym-nodes` without a performance 24h history above 50% don't show routing properly on `nym-gateway-probe`, on Nym Harbourmaster the page may appear blank - we are working on a fix.
|
||||
- Wireguard works on IPv4 only for the time being, we are working on IPv6 implementation.
|
||||
- Harbourmaster *Role* column shows `nym-node --mode exit-gateway` as `EntryGateway`, we are working to fix it.
|
||||
- In rare occassions Harbourmaster shows only *"panda"* without the *"smiley"* badge even for nodes, which have T&C's accepted. We are working to fix it.
|
||||
- Sometimes `nym-node` running with `--wireguard-enabled true` gives this error on restart: `Serialized netlink packet .. larger than maximum size ..`
|
||||
```sh
|
||||
/home/ubuntu/.cargo/registry/src/index.crates.io-6f17d22bba15001f/defguard_wireguard_rs-0.4.2/src/netlink.rs:155: Serialized netlink packet (23240 bytes) larger than maximum size 12288: NetlinkMessage.
|
||||
```
|
||||
|
||||
From what we found out it seems that one of our [dependencies - `DefGuard` - is failing](https://github.com/DefGuard/defguard/issues/619). Based on the reading on their fix, it seems that when node operators try to re-create a wireguard interface with too many previous peers (like on Gateway restart, with restoring from storage), there's an overflow. So their fix is to just add them one by one. To be sure that bumping the dependency version fixes the problem there's still two things we'd need to check - and your feedback would help us a lot:
|
||||
|
||||
1. Did operators only encounter this error after a `nym-node` (Gateway) restart?
|
||||
2. Reprouce this error ourselves and see if it actually fixes our problem.
|
||||
|
||||
**Please share your experience with us to help faster fix of this issue.**
|
||||
|
||||
---
|
||||
|
||||
## `v2024.9-topdeck`
|
||||
@@ -405,42 +444,42 @@ THANK YOU!
|
||||
**Notes:**
|
||||
Bandwidth reset to 1GB for all clients at midnight. Logs and reports correctly showed the reset.
|
||||
~~~
|
||||
|
||||
|
||||
~~~admonish example collapsible=true title='Testing steps performed - Scenario 3: Bandwidth Reset at a Different Time (e.g., Midday)'
|
||||
1. Configure the system to reset bandwidth at midday.
|
||||
2. Use the client and monitored bandwidth until midday.
|
||||
3. Keep the client connected during the reset time.
|
||||
4. Verify that bandwidth reset to 1GB live at midday.
|
||||
|
||||
|
||||
**Notes:**
|
||||
Bandwidth reset to 1GB at midday while the client was connected. Logs and reports correctly reflected the reset.
|
||||
~~~
|
||||
|
||||
* [Handle clients with different versions in IPR](https://github.com/nymtech/nym/pull/4723): Allow the IPR to handle clients connecting both using `v6` and `v7`, independently. The motivation is that we want to be able to roll out an API version change gradually for NymVPN clients without breaking backwards compatibility. The main feature on the new `v7` format that is not yet used, is that it adds signatures for connect/disconnect.
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
Run the same command (using same gateways deployed from this PR) on different versions of the `nym-vpn-cli`.
|
||||
Run the same command (using same gateways deployed from this PR) on different versions of the `nym-vpn-cli`.
|
||||
|
||||
Example:
|
||||
Example:
|
||||
```sh
|
||||
sudo -E ./nym-vpn-cli -c ../qa.env run --entry-gateway-id $entry_gateway --exit-gateway-id $exit_gateway --enable-two-hop
|
||||
|
||||
|
||||
sudo -E ./nym-vpn-cli -c ../qa.env run --entry-gateway-id $entry_gateway --exit-gateway-id $exit_gateway --enable-two-hop
|
||||
```
|
||||
~~~
|
||||
|
||||
### Bugfix
|
||||
|
||||
* [Feature/merge back](https://github.com/nymtech/nym/pull/4710): Merge back from the release branch the changes that fix the `nym-node` upgrades.
|
||||
* [Feature/merge back](https://github.com/nymtech/nym/pull/4710): Merge back from the release branch the changes that fix the `nym-node` upgrades.
|
||||
|
||||
* [Fix version `1.x.x` not having template correspondent initially](https://github.com/nymtech/nym/pull/4733): This should fix the problem of config deserialisation when operators upgrade nodes and skip over multiple versions.
|
||||
* [Fix version `1.x.x` not having template correspondent initially](https://github.com/nymtech/nym/pull/4733): This should fix the problem of config deserialisation when operators upgrade nodes and skip over multiple versions.
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
- Tested updating an old nym-node version and ensuring it did not throw any errors.
|
||||
~~~
|
||||
|
||||
* [chore: fix 1.80 lint issues](https://github.com/nymtech/nym/pull/4731):
|
||||
* [chore: fix 1.80 lint issues](https://github.com/nymtech/nym/pull/4731):
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
- Building all binaries is ok
|
||||
- Running `cargo fmt` returns no issues
|
||||
- Running `cargo fmt` returns no issues
|
||||
~~~
|
||||
|
||||
### Operators Guide updates
|
||||
@@ -482,7 +521,7 @@ ufw allow 1789/tcp # Nym specific
|
||||
ufw allow 1790/tcp # Nym specific
|
||||
ufw allow 8080/tcp # Nym specific - nym-node-api
|
||||
ufw allow 9000/tcp # Nym Specific - clients port
|
||||
ufw allow 9001/tcp # Nym specific - wss port
|
||||
ufw allow 9001/tcp # Nym specific - wss port
|
||||
ufw allow 51822/udp # WireGuard
|
||||
```
|
||||
|
||||
@@ -578,7 +617,7 @@ For Entry Gateway:
|
||||
```
|
||||
~~~
|
||||
|
||||
* [Update Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt): Based on the survey, AMA and following discussions we added several ports to Nym exit policy. The ports voted upon in the [forum governance](https://forum.nymtech.net/t/poll-a-new-nym-exit-policy-for-exit-gateways-and-the-nym-mixnet-is-inbound/464) have not been added yet due to the concerns raised. These ports were unrestricted:
|
||||
* [Update Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt): Based on the survey, AMA and following discussions we added several ports to Nym exit policy. The ports voted upon in the [forum governance](https://forum.nymtech.net/t/poll-a-new-nym-exit-policy-for-exit-gateways-and-the-nym-mixnet-is-inbound/464) have not been added yet due to the concerns raised. These ports were unrestricted:
|
||||
|
||||
~~~admonish example collapsible=true title='Newly opened ports in Nym exit policy'
|
||||
```
|
||||
@@ -611,7 +650,7 @@ For Entry Gateway:
|
||||
|
||||
* [Create a NymConnect archive page](https://nymtech.net/developers/archive/nym-connect.html), PR [\#4750](https://github.com/nymtech/nym/commit/5096c1e60e203dcf8be934823946e24fda16a9a3): Archive deprecated NymConnect for backward compatibility, show PEApps examples for both NC and maintained `nym-socks5-client`.
|
||||
|
||||
* Fix broken URLs and correct redirection. PRs: [\#4745](https://github.com/nymtech/nym/commit/7e36595d8fa7706876880b42df1c998a4b8c1478), [\#4752](https://github.com/nymtech/nym/commit/1db61f800c6884e284c5ab21e7abce3bc6d91d99) [\#4755](https://github.com/nymtech/nym/commit/aaf3dca5b999ad7f19d2ff170078b43c9c4476c2), [\#4737](https://github.com/nymtech/nym/commit/6f669866e92e637772726ad05caa5c5501a830f3)
|
||||
* Fix broken URLs and correct redirection. PRs: [\#4745](https://github.com/nymtech/nym/commit/7e36595d8fa7706876880b42df1c998a4b8c1478), [\#4752](https://github.com/nymtech/nym/commit/1db61f800c6884e284c5ab21e7abce3bc6d91d99) [\#4755](https://github.com/nymtech/nym/commit/aaf3dca5b999ad7f19d2ff170078b43c9c4476c2), [\#4737](https://github.com/nymtech/nym/commit/6f669866e92e637772726ad05caa5c5501a830f3)
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
- Use [deadlinkchecker.com](https://www.deadlinkchecker.com/website-dead-link-checker.asp) to go over `nymtech.net` and correct all docs URLs
|
||||
- Go over search engines and old medium articles and check that all dead URLs re-directing correctly
|
||||
@@ -692,8 +731,8 @@ bind_address = '0.0.0.0:9000'
|
||||
3. Checked the implementation of the `default_node_role` function to ensure it returns `NodeRole::Inactive`.
|
||||
4. Ran the updated code in the sandbox environment.
|
||||
5. Monitored the sandbox environment for any issues or errors related to the changes.
|
||||
|
||||
|
||||
|
||||
|
||||
**Notes (if any):**
|
||||
The test was successful. No issues were flagged during the testing in the sandbox environment. The new default value for `NodeRole` ensures backward compatibility without causing disruptions.
|
||||
~~~
|
||||
@@ -741,7 +780,7 @@ bind_address = '0.0.0.0:9000'
|
||||
6. Validated that no leftover code from the old statistics service provider caused any issues.
|
||||
~~~
|
||||
|
||||
* [Remove `UserAgent` constructor since it's weakly typed](https://github.com/nymtech/nym/pull/4689):
|
||||
* [Remove `UserAgent` constructor since it's weakly typed](https://github.com/nymtech/nym/pull/4689):
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
1. Reviewed the changes in `common/http-api-client/src/user_agent.rs` file.
|
||||
2. Verified the removal of the `UserAgent` constructor and ensured that all instances of `UserAgent::new` are updated accordingly.
|
||||
@@ -749,14 +788,14 @@ bind_address = '0.0.0.0:9000'
|
||||
4. Deployed the updated code across different environments (QA, sandbox, and canary).
|
||||
5. Ran tests to ensure that the `UserAgent` struct functions correctly without the constructor.
|
||||
~~~
|
||||
|
||||
|
||||
* [Add mixnodes to self describing api cache](https://github.com/nymtech/nym/pull/4684):
|
||||
- Abstracts getting the self describing info a bit
|
||||
- Adds mixnodes to the cache refresher as well
|
||||
- Adds `role` field to the `NodeDescription` struct, to be able to distinguish between mixnodes and gateways
|
||||
- Switched to using `NodeStatusCache` instead of `ContractCache`
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
Called the new `/mixnodes/described` endpoint as well as the existing `/gateways/described` endpoint and verified that the data returned for each was correct based on the settings that different nodes have when they are setup.
|
||||
Called the new `/mixnodes/described` endpoint as well as the existing `/gateways/described` endpoint and verified that the data returned for each was correct based on the settings that different nodes have when they are setup.
|
||||
|
||||
For gateway endpoint, the “role” for now does not differentiate between entry and exit gateways, this will be implemented in the future.
|
||||
~~~
|
||||
@@ -792,7 +831,7 @@ For gateway endpoint, the “role” for now does not differentiate between entr
|
||||
5. Ran all tests to ensure the removal did not affect the functionality of the project.
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
* [Remove code that refers to removed `nym-network-statistics`](https://github.com/nymtech/nym/pull/4679): Follow up to [\#4678](https://github.com/nymtech/nym/pull/4678) where all code interacting with it is removed.
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
1. Reviewed the project to identify all references to `nym-network-statistics`.
|
||||
@@ -809,7 +848,7 @@ For gateway endpoint, the “role” for now does not differentiate between entr
|
||||
- `nym-client`
|
||||
- `nym-socks5-client`
|
||||
- Standalone `ip-packet-router`
|
||||
|
||||
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
Used the nym-vpn-cli to test this, and we can visibly see the `UserAgent`, no issues with the comments mentioned above.
|
||||
|
||||
@@ -817,7 +856,7 @@ Example of the user agent sent:
|
||||
`nym-client/1.1.36/x86_64-unknown-linux-gnu/e18bb70`
|
||||
|
||||
<img width="1435" alt="image" src="https://github.com/nymtech/nym/assets/60836166/5d4cc76f-84e6-45cb-9102-adc2b58a25d9">
|
||||
|
||||
|
||||
Connected with no problems
|
||||
~~~
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Reversed Proxy & Web Secure Socket
|
||||
# Reverse Proxy & Web Secure Socket
|
||||
|
||||
It's useful to put your Nym Node behind a reversed proxy and have it accessible via `https` domain, where you can host a [landing page](../legal/landing-pages.md). The guide is right [below](#reversed-proxy).
|
||||
This section will guide you in setting up a reverse proxy for serving `nym-node` HTTP requests and to set up a custom [landing page](../legal/landing-pages.md) for your node.
|
||||
|
||||
More advanced and secure solution is to have your node behind Web Secure Socket (WSS). Follow this [this guide](#web-secure-socket-setup) for installation.
|
||||
In later sections, you will be setting up secure websocket (wss) to add additional security and encrypt connections coming to your node. Follow [this guide](#web-secure-socket-setup) for installation.
|
||||
|
||||
```admonish info
|
||||
For both of these configurations you will need to register a DNS domain and configure a record to your VPS.
|
||||
Since SSL certificates can only be issued for a domain name and not an IP address, it is essential for you to register a new domain name and configure a domain record pointing to your node's IP address
|
||||
```
|
||||
|
||||
## Variables Explanation
|
||||
@@ -13,19 +13,19 @@ For both of these configurations you will need to register a DNS domain and conf
|
||||
This guide contains several variables. Substitute them with your own value, without `<>` brackets. Here is a list of variables we used below.
|
||||
|
||||
| Variable | Description | Syntax example |
|
||||
| :--- | :--- | :--- |
|
||||
| :-------------------- | :------------------------------------------------------------------------------------------ | :-------------------------------------------------------- |
|
||||
| `<HOSTNAME>` | Your registered DNS domain, asigned to the VPS with `nym-node` | exit-gateway1.squad.nsl |
|
||||
| `<WSS_PORT>` | Port listening to WSS, default is `9001` | 9001 |
|
||||
| `<YOUR_WELCOME_TEXT>` | Any text you want to show on the landing page | Welcome to Nym Node, operator contact is example@email.me |
|
||||
| `<LANDING_PAGE_PATH>` | A sub-directory located at `/var/www/<HOSTNAME>` containing html configuration files | `/var/www/exit-gateway1.squad.nsl` |
|
||||
| `<NODE_ID>` | A local only `nym-node` identifier, specified by flag `--id`, default is `default-nym-node` | alice_super_node |
|
||||
| `<PATH_TO>` | Specify a full path to the given file, directory or binary behind this variable | `/root/src/nym/target/release/` |
|
||||
| `<ID>` | A local only `nym-node` identifier, specified by flag `--id`, default is `default-nym-node` | alice_super_node |
|
||||
| `<PATH_TO>` | Specify a full path to the given file, directory or binary behind this variable | `/root/src/nym/target/release/` |
|
||||
|
||||
```admonish warning title=""
|
||||
The commands in this setup need to be run with root permission. Either add a prefix `sudo` or execute them from a root shell.
|
||||
```
|
||||
|
||||
## Reversed Proxy Setup
|
||||
## Reverse Proxy Setup
|
||||
|
||||
```admonish info
|
||||
This guide was created by a Nym node operator, [Avril 14th](https://avril14th.org) as a part of [Nym Operators Community Counsel](../legal/community-counsel.md), edited by Nym.
|
||||
@@ -309,7 +309,7 @@ Now your html page is configured.
|
||||
|
||||
### `nym-node` Configuration
|
||||
|
||||
When done with the customization, you'll need to make sure your `nym-node` uploads the file and reference to it. This is done by opening your node configuration file located at `~/.nym/nym-nodes/<NODE_ID>/config/config.toml` and changing the value of the line `landing_page_assets_path` on the `[http]` section:
|
||||
When done with the customization, you'll need to make sure your `nym-node` uploads the file and reference to it. This is done by opening your node configuration file located at `~/.nym/nym-nodes/<ID>/config/config.toml` and changing the value of the line `landing_page_assets_path` on the `[http]` section:
|
||||
```
|
||||
landing_page_assets_path = '<LANDING_PAGE_PATH>'
|
||||
```
|
||||
@@ -318,9 +318,6 @@ landing_page_assets_path = '<LANDING_PAGE_PATH>'
|
||||
|
||||
You may set up a [reverse proxy](https://www.nginx.com/resources/glossary/reverse-proxy-server/) in order to serve this landing page with proper SSL and DNS management, i.e. to resolve it to https://<HOSTNAME>.
|
||||
|
||||
The following assumes that you're owning a domain and that you've already set the Let's Encrypt certificates on your hosting, and you've copied those on your Gateway, i.e. copy the two Let's Encript pem files on your Gateway's home folder.
|
||||
Else you may obtain a Let's Encrypt certificate using a [`--certonly` procedure](https://eff-certbot.readthedocs.io/en/latest/using.html#getting-certificates-and-choosing-plugins).
|
||||
|
||||
**Configure Nginx**
|
||||
|
||||
1. Install `nginx`:
|
||||
@@ -328,7 +325,7 @@ Else you may obtain a Let's Encrypt certificate using a [`--certonly` procedure]
|
||||
sudo apt install nginx
|
||||
```
|
||||
|
||||
2. Setup firewall with `ufw`. `ufw` has three profile pre-configured for `nginx`, we will need the first one for `nym-node`:
|
||||
1. Setup firewall with `ufw`. `ufw` has three profile pre-configured for `nginx`, we will need the first one for `nym-node`:
|
||||
|
||||
- `Nginx Full`: This profile opens both port 80 (normal, unencrypted web traffic) and port 443 (TLS/SSL encrypted traffic)
|
||||
- `Nginx HTTP`: This profile opens only port 80 (normal, unencrypted web traffic)
|
||||
@@ -345,6 +342,7 @@ ufw reload
|
||||
```
|
||||
|
||||
3. Disable the default Nginx landing page
|
||||
|
||||
```
|
||||
systemctl status nginx
|
||||
unlink /etc/nginx/sites-enabled/default
|
||||
@@ -353,80 +351,65 @@ systemctl restart nginx
|
||||
|
||||
4. Add your endpoint configuration to Nginx by creating file:
|
||||
|
||||
|
||||
```sh
|
||||
nano /etc/nginx/sites-available/<HOSTNAME>
|
||||
```
|
||||
- and changing `<HOSTNAME>` occurrences below with your domain name:
|
||||
|
||||
```
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
|
||||
server_name nym-exit.<HOSTNAME>;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/<HOSTNAME>/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/<HOSTNAME>/privkey.pem;
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
if ($host = <HOSTNAME>) {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
# Replace <HOSTNAME> with your domain name
|
||||
server_name <HOSTNAME>;
|
||||
|
||||
server_name <HOSTNAME> www.<HOSTNAME>;
|
||||
|
||||
return 301 https://<HOSTNAME>$request_uri;
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
```
|
||||
This guide assumes that the HTTP port used by you is `8080`. Adjust the configuration accordingly if you have defined
|
||||
a custom port for your `nym-node` HTTP connections
|
||||
|
||||
5. Activate the configuration by creating a symlink to `/etc/nginx/sites-enabled`:
|
||||
|
||||
5. Activate the configuration by creating a simlink to `/etc/nginx/sites-enabled`:
|
||||
```sh
|
||||
ln -s /etc/nginx/sites-available/<HOSTNAME> /etc/nginx/sites-enabled
|
||||
```
|
||||
|
||||
6. Test your configuration syntax:
|
||||
|
||||
```sh
|
||||
nginx -t
|
||||
```
|
||||
|
||||
Nginx must report that the config is "ok" and the test was successful.
|
||||
|
||||
7. Restart `nginx`:
|
||||
|
||||
```sh
|
||||
systemctl daemon-reload && systemctl restart nginx
|
||||
systemctl restart nginx
|
||||
```
|
||||
|
||||
8. Get an `SSL` certificate using certbot:
|
||||
|
||||
```sh
|
||||
apt install certbot python3-certbot-nginx
|
||||
certbot renew --dry-run
|
||||
certbot --nginx -d <HOSTNAME>
|
||||
certbot --nginx --non-interactive --agree-tos --redirect -m <YOUR_EMAIL_ADDRESS> -d <HOSTNAME>
|
||||
```
|
||||
|
||||
9. Restart your `nym-node` or if you're running your `nym-node` as a [`systemd` service](configuration.md#systemd), restart your service:
|
||||
```sh
|
||||
systemctl daemon-reload
|
||||
service nym-node restart
|
||||
systemctl daemon-reload && systemctl restart nym-node
|
||||
```
|
||||
|
||||
10. Check for the page being served reading the service logs
|
||||
9. Check for the page being served reading the service logs
|
||||
```sh
|
||||
journalctl -u nym-gateway.service | grep 8080
|
||||
journalctl -u nym-node.service | grep 8080
|
||||
|
||||
# where you should see
|
||||
... Started NymNodeHTTPServer on 0.0.0.0:8080
|
||||
@@ -437,27 +420,19 @@ Now your `nginx` should be configured, up and running. Test it by inserting your
|
||||
|
||||
## Web Secure Socket Setup
|
||||
|
||||
For better security of transfered data, we recommend node operators to run their nodes through Secure Socket instead of be out in open. You can read more about *Secure Socket Layer* (SSL) in [here](https://www.geeksforgeeks.org/secure-socket-layer-ssl/).
|
||||
This section assumes that you have already configured a reverse proxy and have set it up to work over https. If not, head over to [the reverse proxy section](#reverse-proxy-configuration) to configure it.
|
||||
|
||||
Before you start, don't forget to register a DNS and configure a record for your VPS with `nym-node`.
|
||||
We strongly recommend node operators to configure secure web sockets on their nodes. This will provide clients a more secure way to connect to your node.
|
||||
|
||||
You can read more about *Secure Socket Layer* (SSL) in [here](https://www.geeksforgeeks.org/secure-socket-layer-ssl/).
|
||||
|
||||
|
||||
If you haven't configure reversed proxy before, start with [*Preliminary steps* chapter](#preliminary-steps) below and only then move to WSS setup. If you have reversed proxy already running and your `nym-node` can be reached via https, you can skip *Preliminary steps* and begin to setup WSS directly. Remember that there may be some unique variables and customization depending on the way your reversed proxy is done which you may have to adjust when installing WSS in order to make it work.
|
||||
Remember that there may be some unique variables and customization depending on the way your reverse proxy is setup which you may have to adjust when configuring WSS to ensure correct functionality
|
||||
|
||||
```admonish tip
|
||||
To see description of used variables (noted in `<>` brackets), scroll to the top of this page, chapter [*Variables Explanation*](#variables-explanation).
|
||||
```
|
||||
|
||||
We documented two options for node operators to setup WSS for `nym-node`:
|
||||
|
||||
1. [Using a script](#using-a-script)
|
||||
2. [Step by step](#step-by-step)
|
||||
|
||||
|
||||
### Preliminary Steps
|
||||
|
||||
Whether you choose to setup WSS manually or using the script, the preliminary steps are mandatory to begin with before you continue with the installation.
|
||||
|
||||
#### Firewall configuration
|
||||
|
||||
Make sure to open all [needed ports](vps-setup.md#configure-your-firewall), adding your `<WSS_PORT>`:
|
||||
@@ -469,293 +444,18 @@ ufw allow <WSS_PORT>/tcp
|
||||
# ufw allow 9001/tcp
|
||||
```
|
||||
|
||||
#### Landing page configuration
|
||||
#### WSS configuration
|
||||
|
||||
1. Create server block directory for your https site:
|
||||
```sh
|
||||
sudo mkdir -p /var/www/<HOSTNAME>
|
||||
```
|
||||
This section assumes that you have already configured a reverse proxy and have set it up to work over https. If not, head over to [the reverse proxy section](#reverse-proxy) to configure it.
|
||||
|
||||
2. Assign ownership using `$USER` environmental variable:
|
||||
```sh
|
||||
sudo chown -R $USER:$USER /var/www/<HOSTNAME>
|
||||
```
|
||||
1. Create a new Nginx configuration file called `/etc/nginx/sites-available/wss-config-nym` and paste the block below. Don't forget to insert your correct values.
|
||||
|
||||
3. Create a landing page in `/var/www/<HOSTNAME>`. Either configure your own page (basic [syntax example](https://www.freecodecamp.org/news/introduction-to-html-basics/) or use our [template](#html-file-customization). Alternatively you can just make a simple welcome text using this command:
|
||||
```sh
|
||||
echo "<h1><YOUR_WELCOME_TEXT></h1>" | sudo tee /var/www/<HOSTNAME>/index.html
|
||||
```
|
||||
|
||||
4. When done with the customization, you'll need to make sure your `nym-node` uploads the file and reference to it. This is done by opening your node configuration file located at `~/.nym/nym-nodes/<NODE_ID>/config/config.toml` and changing the value of the line `landing_page_assets_path` on the `[http]` section:
|
||||
```sh
|
||||
landing_page_assets_path = '<LANDING_PAGE_PATH>'
|
||||
|
||||
# for example
|
||||
# landing_page_assets_path = '/var/www/exit-gateway1.squad.nsl'
|
||||
```
|
||||
|
||||
Now you are ready to set up WSS, ether using a [script](#using-a-script) or [step-by-step](#step-by-step) tutorial.
|
||||
|
||||
### Using a Script
|
||||
|
||||
Using a script is a more convenient option but it takes away some customization possibilities. If you like to have your setup fully in your hands, use [*Step by step guide*](#step-by-step-guide). Before you move on, make sure you went through [*Preliminary steps*](#preliminary-steps).
|
||||
|
||||
1. Create a script by copying the block below and save it on your VPS as `wss_nginx_setup.sh`.
|
||||
|
||||
~~~admonish example collapsible=true title="Script `wss_nginx_setup.sh`"
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo "usage: sudo ./wss_nginx_setup.sh <host_name> <port_to_run_wss>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
host_name=$1
|
||||
port_value=$2
|
||||
|
||||
# preliminary checks
|
||||
config_file_path="${HOME}/.nym/nym-nodes/*/config/config.toml"
|
||||
|
||||
# check if the configuration file exists
|
||||
if [ ! -f $config_file_path ]; then
|
||||
echo "configuration file not found at $config_file_path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# extract hostname and wss port
|
||||
hostname=$(grep "hostname" $config_file_path | awk -F" = " '{print $2}' | tr -d "'")
|
||||
wss_port=$(grep "announce_wss_port" $config_file_path | awk -F" = " '{print $2}' | tr -d ' ')
|
||||
|
||||
# check if hostname is empty
|
||||
if [ -z "$hostname" ]; then
|
||||
echo "hostname is empty, updating it to ${host_name}"
|
||||
sed -i "s|hostname = ''|hostname = '${host_name}'|" $config_file_path
|
||||
else
|
||||
echo "current hostname: $hostname"
|
||||
fi
|
||||
|
||||
# check if wss port is set to 0 and update it
|
||||
if [ "$wss_port" -eq 0 ]; then
|
||||
echo "wss port is 0, updating it to ${port_value}"
|
||||
sed -i "s/announce_wss_port *= *0/announce_wss_port = ${port_value}/" $config_file_path
|
||||
else
|
||||
echo "current wss port: $wss_port"
|
||||
fi
|
||||
|
||||
# install nginx
|
||||
apt update
|
||||
apt install -y nginx
|
||||
|
||||
# install certbot and the nginx plugin
|
||||
apt install -y certbot python3-certbot-nginx
|
||||
|
||||
# enable nginx service
|
||||
systemctl enable nginx.service
|
||||
|
||||
# create a consolidated nginx configuration file
|
||||
nginx_config_file="/etc/nginx/sites-available/${host_name}"
|
||||
cat <<EOF > $nginx_config_file
|
||||
# Reversed proxy configuration for landing page
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
|
||||
server_name ${host_name};
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/${host_name}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/${host_name}/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
|
||||
# http configuration
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
server_name ${host_name} www.${host_name};
|
||||
|
||||
return 301 https://${host_name}\$request_uri;
|
||||
}
|
||||
|
||||
# WSS configuration
|
||||
server {
|
||||
listen ${port_value} ssl http2;
|
||||
listen [::]:${port_value} ssl http2;
|
||||
|
||||
server_name ${host_name};
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/${host_name}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/${host_name}/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
location / {
|
||||
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Credentials' 'true';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, HEAD';
|
||||
add_header 'Access-Control-Allow-Headers' '*';
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header X-Forwarded-For \$remote_addr;
|
||||
proxy_pass http://localhost:9000;
|
||||
proxy_intercept_errors on; # Enable intercepting errors from the proxy
|
||||
}
|
||||
|
||||
}
|
||||
EOF
|
||||
|
||||
# create a symbolic link in sites-enabled
|
||||
ln -s /etc/nginx/sites-available/${host_name} /etc/nginx/sites-enabled/
|
||||
|
||||
# test nginx configuration
|
||||
if ! nginx -t; then
|
||||
echo "nginx configuration test failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# reload nginx service
|
||||
systemctl reload nginx.service
|
||||
|
||||
# obtain ssl certificates using certbot
|
||||
if ! certbot --nginx -d ${host_name} --non-interactive --agree-tos -m your-email@example.com; then
|
||||
echo "certbot failed to obtain certificates"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# test nginx configuration again
|
||||
if ! nginx -t; then
|
||||
echo "nginx configuration test failed after obtaining ssl certificates"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# reload nginx service to apply the new configuration
|
||||
systemctl reload nginx.service
|
||||
|
||||
echo "script completed successfully!"
|
||||
echo "have a nice day!"
|
||||
exit 0
|
||||
```
|
||||
~~~
|
||||
|
||||
2. Make the script executable:
|
||||
```sh
|
||||
chmod u+x wss_nginx_setup.sh
|
||||
```
|
||||
|
||||
3. Run the script as root (with `sudo` or from the root shell):
|
||||
```sh
|
||||
./wss_nginx_setup.sh <HOSTNAME> <WSS_PORT>
|
||||
# hostname is your domain
|
||||
# wss default port is 9001
|
||||
```
|
||||
|
||||
|
||||
4. Restart your `nym-node` or if you're running your `nym-node` as a [`systemd` service](configuration.md#systemd), restart your service:
|
||||
```sh
|
||||
systemctl daemon-reload
|
||||
service nym-node restart
|
||||
```
|
||||
|
||||
Your `nym-node` should be configured to run over WSS now. Test it using the steps in the chapter [below](#test-wss-setup)
|
||||
|
||||
|
||||
### Step by Step Guide
|
||||
|
||||
Step by step guide is more advanced than using a [script](#using-a-script), but it allows for more customisation. Before you move on, make sure you finished [*Preliminary steps*](#preliminary-steps*).
|
||||
|
||||
|
||||
#### Nginx Configuration
|
||||
|
||||
|
||||
1. Install `nginx`:
|
||||
```sh
|
||||
apt install nginx
|
||||
```
|
||||
|
||||
2. Setup firewall with `ufw`. `ufw` has three profile pre-configured for `nginx`, we will need the first one for `nym-node`:
|
||||
|
||||
- `Nginx Full`: This profile opens both port 80 (normal, unencrypted web traffic) and port 443 (TLS/SSL encrypted traffic)
|
||||
- `Nginx HTTP`: This profile opens only port 80 (normal, unencrypted web traffic)
|
||||
- `Nginx HTTPS`: This profile opens only port 443 (TLS/SSL encrypted traffic)
|
||||
|
||||
```sh
|
||||
ufw allow 'Nginx Full'
|
||||
|
||||
# you can verify by
|
||||
ufw status
|
||||
```
|
||||
|
||||
#### WSS & Landing page configuration
|
||||
|
||||
We made the landing page customization directory in [*Preliminary steps*](#preliminary-steps), next steps will configure that with Nginx.
|
||||
|
||||
3. Configure your site to work with `nginx`. Open a new text file `/etc/nginx/sites-available/<HOSTNAME>` and paste the block below. Don't forget to insert your correct values.
|
||||
~~~admonish example collapsible=true title="Site configuration `/etc/nginx/sites-available/<HOSTNAME>`"
|
||||
~~~admonish example collapsible=true title="Site configuration `/etc/nginx/sites-available/wss-config-nym`"
|
||||
```bash
|
||||
#####################################################
|
||||
# EXCHANGE ALL <HOSTNAME> & <WSS_PORT> VARIABLES ! #
|
||||
####################################################
|
||||
|
||||
# Reversed proxy configuration for landing page
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
|
||||
server_name <HOSTNAME>;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/<HOSTNAME>/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/<HOSTNAME>/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# http configuration
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
if ($host = <HOSTNAME>) {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server_name <HOSTNAME> www.<HOSTNAME>;
|
||||
|
||||
return 301 https://<HOSTNAME>$request_uri;
|
||||
}
|
||||
|
||||
|
||||
# WSS configuration
|
||||
server {
|
||||
listen <WSS_PORT> ssl http2;
|
||||
listen [::]:<WSS_PORT> ssl http2;
|
||||
@@ -770,29 +470,35 @@ server {
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
# Ignore favicon requests
|
||||
location /favicon.ico {
|
||||
return 204;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
|
||||
location / {
|
||||
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Credentials' 'true';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, HEAD';
|
||||
add_header 'Access-Control-Allow-Headers' '*';
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
|
||||
proxy_pass http://localhost:9000;
|
||||
proxy_intercept_errors on; # Enable intercepting errors from the proxy
|
||||
proxy_intercept_errors on;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
~~~
|
||||
|
||||
4. Activate the configuration by creating a simlink to `/etc/nginx/sites-enabled`:
|
||||
```sh
|
||||
ln -s /etc/nginx/sites-available/<HOSTNAME> /etc/nginx/sites-enabled
|
||||
ln -s /etc/nginx/sites-available/wss-config-nym /etc/nginx/sites-enabled
|
||||
```
|
||||
|
||||
5. Test your configuration syntax:
|
||||
@@ -802,23 +508,22 @@ nginx -t
|
||||
|
||||
6. Restart `nginx`:
|
||||
```sh
|
||||
systemctl daemon-reload && systemctl restart nginx
|
||||
systemctl restart nginx
|
||||
|
||||
```
|
||||
|
||||
#### SSL Setup using certbot
|
||||
7. Finally, configure your `nym-node` to announce the port you have setup. This is done by opening your node configuration file located at `~/.nym/nym-nodes/<ID>/config/config.toml` and changing the value of the line `announce_wss_port` in the `[entry_gateway]` section:
|
||||
|
||||
7. Get an `SSL` certificate using certbot:
|
||||
```
|
||||
announce_wss_port = <WSS_PORT>
|
||||
|
||||
```sh
|
||||
apt install certbot python3-certbot-nginx
|
||||
certbot renew --dry-run
|
||||
certbot --nginx -d <HOSTNAME>
|
||||
# example
|
||||
# announce_wss_port = 9001
|
||||
```
|
||||
|
||||
8. Restart your `nym-node` or if you're running your `nym-node` as a [`systemd` service](configuration.md#systemd), restart your service:
|
||||
8. Restart your `nym-node` :
|
||||
```sh
|
||||
systemctl daemon-reload
|
||||
service nym-node restart
|
||||
systemctl restart nym-node
|
||||
```
|
||||
|
||||
Your `nym-node` should be configured to run over WSS now. Test it using the steps in the chapter [below](#test-wss-setup).
|
||||
@@ -833,9 +538,7 @@ You can do a few quick checks to test that your installation worked out and your
|
||||
sudo apt install node-ws
|
||||
|
||||
# run
|
||||
wscat -c ws://<HOSTNAME>:<WSS_PORT>
|
||||
wscat -c wss://<HOSTNAME>:<WSS_PORT>
|
||||
```
|
||||
|
||||
- Browse your `<HOSTNAME>` as URL and see your landing page.
|
||||
|
||||
- Check Swagger API of your node using the hostname: `https://<HOSTNAME>/api/v1/swagger/#/`
|
||||
|
||||
@@ -115,6 +115,12 @@ impl IntoWSMessage for Result<ServerResponse, RequestHandlingError> {
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoWSMessage for ServerResponse {
|
||||
fn into_ws_message(self) -> Message {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct AuthenticatedHandler<R, S, St> {
|
||||
inner: FreshHandler<R, S, St>,
|
||||
bandwidth_storage_manager: BandwidthStorageManager<St>,
|
||||
@@ -322,13 +328,28 @@ where
|
||||
.await
|
||||
.map_err(|e| e.into())
|
||||
.into_ws_message(),
|
||||
other => RequestHandlingError::IllegalRequest {
|
||||
additional_context: format!(
|
||||
"received illegal message of type {} in an authenticated client",
|
||||
other.name()
|
||||
),
|
||||
ClientControlRequest::SupportedProtocol { .. } => self
|
||||
.inner
|
||||
.handle_supported_protocol_request()
|
||||
.into_ws_message(),
|
||||
other @ ClientControlRequest::Authenticate { .. } => {
|
||||
RequestHandlingError::IllegalRequest {
|
||||
additional_context: format!(
|
||||
"received illegal message of type {} in an authenticated client",
|
||||
other.name()
|
||||
),
|
||||
}
|
||||
.into_error_message()
|
||||
}
|
||||
other @ ClientControlRequest::RegisterHandshakeInitRequest { .. } => {
|
||||
RequestHandlingError::IllegalRequest {
|
||||
additional_context: format!(
|
||||
"received illegal message of type {} in an authenticated client",
|
||||
other.name()
|
||||
),
|
||||
}
|
||||
.into_error_message()
|
||||
}
|
||||
.into_error_message(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::RequestHandlingError;
|
||||
use crate::node::client_handling::websocket::common_state::CommonHandlerState;
|
||||
use crate::node::client_handling::websocket::connection_handler::INITIAL_MESSAGE_TIMEOUT;
|
||||
use crate::node::client_handling::{
|
||||
active_clients::ActiveClientsStore,
|
||||
websocket::{
|
||||
connection_handler::{
|
||||
AuthenticatedHandler, ClientDetails, InitialAuthResult, SocketStream,
|
||||
},
|
||||
message_receiver::{IsActive, IsActiveRequestSender},
|
||||
},
|
||||
};
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
SinkExt, StreamExt,
|
||||
@@ -18,6 +28,7 @@ use nym_gateway_requests::{
|
||||
types::{ClientControlRequest, ServerResponse},
|
||||
BinaryResponse, CURRENT_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_gateway_storage::{error::StorageError, Storage};
|
||||
use nym_mixnet_client::forwarder::MixForwardingSender;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use nym_task::TaskClient;
|
||||
@@ -26,21 +37,10 @@ use std::net::SocketAddr;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::time::timeout;
|
||||
use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError};
|
||||
use tracing::*;
|
||||
|
||||
use crate::node::client_handling::websocket::common_state::CommonHandlerState;
|
||||
use crate::node::client_handling::{
|
||||
active_clients::ActiveClientsStore,
|
||||
websocket::{
|
||||
connection_handler::{
|
||||
AuthenticatedHandler, ClientDetails, InitialAuthResult, SocketStream,
|
||||
},
|
||||
message_receiver::{IsActive, IsActiveRequestSender},
|
||||
},
|
||||
};
|
||||
use nym_gateway_storage::{error::StorageError, Storage};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum InitialAuthenticationError {
|
||||
#[error("Internal gateway storage error")]
|
||||
@@ -80,12 +80,6 @@ pub(crate) enum InitialAuthenticationError {
|
||||
#[error("Attempted to negotiate connection with client using incompatible protocol version. Ours is {current} and the client reports {client:?}")]
|
||||
IncompatibleProtocol { client: Option<u8>, current: u8 },
|
||||
|
||||
#[error("failed to send authentication error response: {source}")]
|
||||
ErrorResponseSendFailure {
|
||||
#[source]
|
||||
source: WsError,
|
||||
},
|
||||
|
||||
#[error("failed to send authentication response: {source}")]
|
||||
ResponseSendFailure {
|
||||
#[source]
|
||||
@@ -95,9 +89,6 @@ pub(crate) enum InitialAuthenticationError {
|
||||
#[error("possibly received a sphinx packet without prior authentication. Request is going to be ignored")]
|
||||
BinaryRequestWithoutAuthentication,
|
||||
|
||||
#[error("received a connection close message")]
|
||||
CloseMessage,
|
||||
|
||||
#[error("the connection has unexpectedly closed")]
|
||||
ClosedConnection,
|
||||
|
||||
@@ -107,21 +98,11 @@ pub(crate) enum InitialAuthenticationError {
|
||||
source: WsError,
|
||||
},
|
||||
|
||||
#[error("timed out while waiting for initial data")]
|
||||
Timeout,
|
||||
|
||||
#[error("could not establish client details")]
|
||||
EmptyClientDetails,
|
||||
|
||||
#[error("failed to upgrade the client handler: {source}")]
|
||||
HandlerUpgradeFailure { source: RequestHandlingError },
|
||||
|
||||
#[error("received shutdown")]
|
||||
ReceivedShutdown,
|
||||
}
|
||||
|
||||
impl InitialAuthenticationError {
|
||||
/// Converts this Error into an appropriate websocket Message.
|
||||
fn to_error_message(&self) -> Message {
|
||||
ServerResponse::new_error(self.to_string()).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct FreshHandler<R, S, St> {
|
||||
@@ -232,7 +213,10 @@ where
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `msg`: WebSocket message to write back to the client.
|
||||
pub(crate) async fn send_websocket_message(&mut self, msg: Message) -> Result<(), WsError>
|
||||
pub(crate) async fn send_websocket_message(
|
||||
&mut self,
|
||||
msg: impl Into<Message>,
|
||||
) -> Result<(), WsError>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
@@ -240,11 +224,31 @@ where
|
||||
// TODO: more closely investigate difference between `Sink::send` and `Sink::send_all`
|
||||
// it got something to do with batching and flushing - it might be important if it
|
||||
// turns out somehow we've got a bottleneck here
|
||||
SocketStream::UpgradedWebSocket(ref mut ws_stream) => ws_stream.send(msg).await,
|
||||
SocketStream::UpgradedWebSocket(ref mut ws_stream) => ws_stream.send(msg.into()).await,
|
||||
_ => panic!("impossible state - websocket handshake was somehow reverted"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn send_error_response(
|
||||
&mut self,
|
||||
err: impl std::error::Error,
|
||||
) -> Result<(), WsError>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin + Send,
|
||||
{
|
||||
self.send_websocket_message(ServerResponse::new_error(err.to_string()))
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn send_and_forget_error_response(&mut self, err: impl std::error::Error)
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin + Send,
|
||||
{
|
||||
if let Err(err) = self.send_error_response(err).await {
|
||||
debug!("failed to send error response: {err}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends unwrapped sphinx packets (payloads) back to the client. Note that each message is encrypted and tagged with
|
||||
/// the previously derived shared keys.
|
||||
///
|
||||
@@ -570,7 +574,11 @@ where
|
||||
return Ok(InitialAuthResult::new_failed(Some(negotiated_protocol)));
|
||||
};
|
||||
|
||||
let client_id = self.shared_state.storage.get_client_id(address).await?;
|
||||
let client_id = self
|
||||
.shared_state
|
||||
.storage
|
||||
.get_mixnet_client_id(address)
|
||||
.await?;
|
||||
|
||||
let available_bandwidth: AvailableBandwidth = self
|
||||
.shared_state
|
||||
@@ -687,167 +695,177 @@ where
|
||||
))
|
||||
}
|
||||
|
||||
/// Handles data that resembles request to either start registration handshake or perform
|
||||
/// authentication.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `raw_request`: raw text request received from the websocket.
|
||||
async fn handle_initial_authentication_request(
|
||||
&mut self,
|
||||
raw_request: String,
|
||||
) -> Result<InitialAuthResult, InitialAuthenticationError>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin + Send,
|
||||
{
|
||||
if let Ok(request) = ClientControlRequest::try_from(raw_request) {
|
||||
match request {
|
||||
ClientControlRequest::Authenticate {
|
||||
protocol_version,
|
||||
address,
|
||||
enc_address,
|
||||
iv,
|
||||
} => {
|
||||
self.handle_authenticate(protocol_version, address, enc_address, iv)
|
||||
.await
|
||||
}
|
||||
ClientControlRequest::RegisterHandshakeInitRequest {
|
||||
protocol_version,
|
||||
data,
|
||||
} => self.handle_register(protocol_version, data).await,
|
||||
// won't accept anything else (like bandwidth) without prior authentication
|
||||
_ => Err(InitialAuthenticationError::InvalidRequest),
|
||||
}
|
||||
} else {
|
||||
Err(InitialAuthenticationError::InvalidRequest)
|
||||
pub(crate) fn handle_supported_protocol_request(&self) -> ServerResponse {
|
||||
debug!("returning gateway protocol version");
|
||||
ServerResponse::SupportedProtocol {
|
||||
version: CURRENT_PROTOCOL_VERSION,
|
||||
}
|
||||
}
|
||||
|
||||
/// Listens for only a subset of possible client requests, i.e. for those that can either
|
||||
/// result in client getting registered or authenticated. All other requests, such as forwarding
|
||||
/// sphinx packets considered an error and terminate the connection.
|
||||
// TODO: somehow cleanup this method
|
||||
pub(crate) async fn perform_initial_authentication(
|
||||
mut self,
|
||||
) -> Result<AuthenticatedHandler<R, S, St>, InitialAuthenticationError>
|
||||
async fn handle_reply_supported_protocol_request(&mut self)
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin + Send,
|
||||
{
|
||||
trace!("Started waiting for authenticate/register request...");
|
||||
if let Err(err) = self
|
||||
.send_websocket_message(self.handle_supported_protocol_request())
|
||||
.await
|
||||
{
|
||||
debug!("failed to reply with protocol version: {err}")
|
||||
}
|
||||
}
|
||||
|
||||
let mut shutdown = self.shutdown.clone();
|
||||
loop {
|
||||
tokio::select! {
|
||||
pub(crate) async fn handle_initial_client_request(
|
||||
&mut self,
|
||||
request: ClientControlRequest,
|
||||
) -> Result<Option<ClientDetails>, InitialAuthenticationError>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin + Send,
|
||||
{
|
||||
// we can handle stateless client requests without prior authentication, like `ClientControlRequest::SupportedProtocol`
|
||||
let auth_result = match request {
|
||||
ClientControlRequest::Authenticate {
|
||||
protocol_version,
|
||||
address,
|
||||
enc_address,
|
||||
iv,
|
||||
} => {
|
||||
self.handle_authenticate(protocol_version, address, enc_address, iv)
|
||||
.await
|
||||
}
|
||||
ClientControlRequest::RegisterHandshakeInitRequest {
|
||||
protocol_version,
|
||||
data,
|
||||
} => self.handle_register(protocol_version, data).await,
|
||||
ClientControlRequest::SupportedProtocol { .. } => {
|
||||
self.handle_reply_supported_protocol_request().await;
|
||||
return Ok(None);
|
||||
}
|
||||
_ => {
|
||||
debug!("received an invalid client request");
|
||||
return Err(InitialAuthenticationError::InvalidRequest);
|
||||
}
|
||||
};
|
||||
|
||||
let auth_result = match auth_result {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
match &err {
|
||||
InitialAuthenticationError::StorageError(inner_storage) => {
|
||||
debug!("authentication failure due to storage issue: {inner_storage}")
|
||||
}
|
||||
other => debug!("authentication failure: {other}"),
|
||||
}
|
||||
|
||||
self.send_and_forget_error_response(&err).await;
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
|
||||
// try to send auth response back to the client
|
||||
if let Err(source) = self
|
||||
.send_websocket_message(auth_result.server_response)
|
||||
.await
|
||||
{
|
||||
debug!("failed to send authentication response: {source}");
|
||||
return Err(InitialAuthenticationError::ResponseSendFailure { source });
|
||||
}
|
||||
|
||||
let Some(client_details) = auth_result.client_details else {
|
||||
// honestly, it's been so long I don't remember under what conditions its possible (if at all)
|
||||
// to have empty client details
|
||||
warn!("could not establish client details");
|
||||
return Err(InitialAuthenticationError::EmptyClientDetails);
|
||||
};
|
||||
Ok(Some(client_details))
|
||||
}
|
||||
|
||||
pub(crate) async fn handle_until_authenticated_or_failure(
|
||||
mut self,
|
||||
shutdown: &mut TaskClient,
|
||||
) -> Option<AuthenticatedHandler<R, S, St>>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin + Send,
|
||||
{
|
||||
while !shutdown.is_shutdown() {
|
||||
let req = tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
trace!("received shutdown signal while performing initial authentication");
|
||||
return Err(InitialAuthenticationError::ReceivedShutdown);
|
||||
return None
|
||||
},
|
||||
msg = self.read_websocket_message() => {
|
||||
let Some(msg) = msg else {
|
||||
break;
|
||||
};
|
||||
req = self.wait_for_initial_message() => req,
|
||||
};
|
||||
|
||||
let msg = match msg {
|
||||
Ok(msg) => msg,
|
||||
Err(source) => {
|
||||
debug!("failed to obtain message from websocket stream! stopping connection handler: {source}");
|
||||
return Err(InitialAuthenticationError::FailedToReadMessage { source });
|
||||
}
|
||||
};
|
||||
|
||||
if msg.is_close() {
|
||||
return Err(InitialAuthenticationError::CloseMessage);
|
||||
}
|
||||
|
||||
// ONLY handle 'Authenticate' or 'Register' requests, ignore everything else
|
||||
match msg {
|
||||
// we have explicitly checked for close message
|
||||
Message::Close(_) => unreachable!(),
|
||||
Message::Text(text_msg) => {
|
||||
let (mix_sender, mix_receiver) = mpsc::unbounded();
|
||||
return match self.handle_initial_authentication_request(text_msg).await {
|
||||
Err(err) => {
|
||||
debug!("authentication failure: {err}");
|
||||
|
||||
// try to send error to the client
|
||||
if let Err(source) =
|
||||
self.send_websocket_message(err.to_error_message()).await
|
||||
{
|
||||
debug!("Failed to send authentication error response: {source}");
|
||||
return Err(InitialAuthenticationError::ErrorResponseSendFailure {
|
||||
source,
|
||||
});
|
||||
}
|
||||
// return the underlying error
|
||||
Err(err)
|
||||
}
|
||||
Ok(auth_result) => {
|
||||
// try to send auth response back to the client
|
||||
if let Err(source) = self
|
||||
.send_websocket_message(auth_result.server_response.into())
|
||||
.await
|
||||
{
|
||||
debug!("Failed to send authentication response: {source}");
|
||||
return Err(InitialAuthenticationError::ResponseSendFailure {
|
||||
source,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(client_details) = auth_result.client_details {
|
||||
// Channel for handlers to ask other handlers if they are still active.
|
||||
let (is_active_request_sender, is_active_request_receiver) =
|
||||
mpsc::unbounded();
|
||||
self.active_clients_store.insert_remote(
|
||||
client_details.address,
|
||||
mix_sender,
|
||||
is_active_request_sender,
|
||||
);
|
||||
AuthenticatedHandler::upgrade(
|
||||
self,
|
||||
client_details,
|
||||
mix_receiver,
|
||||
is_active_request_receiver,
|
||||
)
|
||||
.await
|
||||
.map_err(|source| {
|
||||
InitialAuthenticationError::HandlerUpgradeFailure { source }
|
||||
})
|
||||
} else {
|
||||
// honestly, it's been so long I don't remember under what conditions its possible (if at all)
|
||||
// to have empty client details
|
||||
Err(InitialAuthenticationError::EmptyClientDetails)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Message::Binary(_) => {
|
||||
// perhaps logging level should be reduced here, let's leave it for now and see what happens
|
||||
// if client is working correctly, this should have never happened
|
||||
debug!("possibly received a sphinx packet without prior authentication. Request is going to be ignored");
|
||||
if let Err(source) = self
|
||||
.send_websocket_message(
|
||||
ServerResponse::new_error(
|
||||
"binary request without prior authentication",
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
return Err(InitialAuthenticationError::ErrorResponseSendFailure {
|
||||
source,
|
||||
});
|
||||
}
|
||||
return Err(InitialAuthenticationError::BinaryRequestWithoutAuthentication);
|
||||
}
|
||||
|
||||
_ => continue,
|
||||
};
|
||||
let initial_request = match req {
|
||||
Ok(req) => req,
|
||||
Err(err) => {
|
||||
self.send_and_forget_error_response(err).await;
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// see if we managed to register the client through this request
|
||||
let maybe_auth_res = match self.handle_initial_client_request(initial_request).await {
|
||||
Ok(maybe_auth_res) => maybe_auth_res,
|
||||
Err(err) => {
|
||||
debug!("initial client request handling error: {err}");
|
||||
self.send_and_forget_error_response(err).await;
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(registration_details) = maybe_auth_res {
|
||||
let (mix_sender, mix_receiver) = mpsc::unbounded();
|
||||
// Channel for handlers to ask other handlers if they are still active.
|
||||
let (is_active_request_sender, is_active_request_receiver) = mpsc::unbounded();
|
||||
self.active_clients_store.insert_remote(
|
||||
registration_details.address,
|
||||
mix_sender,
|
||||
is_active_request_sender,
|
||||
);
|
||||
|
||||
return AuthenticatedHandler::upgrade(
|
||||
self,
|
||||
registration_details,
|
||||
mix_receiver,
|
||||
is_active_request_receiver,
|
||||
)
|
||||
.await
|
||||
.inspect_err(|err| error!("failed to upgrade client handler: {err}"))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
Err(InitialAuthenticationError::ClosedConnection)
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) async fn wait_for_initial_message(
|
||||
&mut self,
|
||||
) -> Result<ClientControlRequest, InitialAuthenticationError>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin + Send,
|
||||
{
|
||||
let msg = match timeout(INITIAL_MESSAGE_TIMEOUT, self.read_websocket_message()).await {
|
||||
Ok(Some(Ok(msg))) => msg,
|
||||
Ok(Some(Err(source))) => {
|
||||
debug!("failed to obtain message from websocket stream! stopping connection handler: {source}");
|
||||
return Err(InitialAuthenticationError::FailedToReadMessage { source });
|
||||
}
|
||||
Ok(None) => return Err(InitialAuthenticationError::ClosedConnection),
|
||||
Err(_timeout) => return Err(InitialAuthenticationError::Timeout),
|
||||
};
|
||||
|
||||
let text = match msg {
|
||||
Message::Text(text) => text,
|
||||
Message::Binary(_) => {
|
||||
return Err(InitialAuthenticationError::BinaryRequestWithoutAuthentication);
|
||||
}
|
||||
_ => unreachable!(
|
||||
"the underlying tunsgenite stream should be handling other message types"
|
||||
),
|
||||
};
|
||||
|
||||
text.parse()
|
||||
.map_err(|_| InitialAuthenticationError::InvalidRequest)
|
||||
}
|
||||
|
||||
pub(crate) async fn start_handling(self)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::Config;
|
||||
use fresh::InitialAuthenticationError;
|
||||
use nym_credential_verification::BandwidthFlushingBehaviourConfig;
|
||||
use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
use nym_gateway_requests::ServerResponse;
|
||||
@@ -12,7 +11,7 @@ use rand::{CryptoRng, Rng};
|
||||
use std::time::Duration;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio_tungstenite::WebSocketStream;
|
||||
use tracing::{instrument, trace, warn};
|
||||
use tracing::{debug, instrument, trace, warn};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
pub(crate) use self::authenticated::AuthenticatedHandler;
|
||||
@@ -22,6 +21,7 @@ pub(crate) mod authenticated;
|
||||
mod fresh;
|
||||
|
||||
const WEBSOCKET_HANDSHAKE_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
const INITIAL_MESSAGE_TIMEOUT: Duration = Duration::from_millis(10_000);
|
||||
|
||||
// TODO: note for my future self to consider the following idea:
|
||||
// split the socket connection into sink and stream
|
||||
@@ -92,6 +92,12 @@ where
|
||||
S: AsyncRead + AsyncWrite + Unpin + Send,
|
||||
St: Storage + Clone + 'static,
|
||||
{
|
||||
// don't accept any new requests if we have already received shutdown
|
||||
if handle.shutdown.is_shutdown() {
|
||||
debug!("stopping the handle as we have received a shutdown");
|
||||
return;
|
||||
}
|
||||
|
||||
// If the connection handler abruptly stops, we shouldn't signal global shutdown
|
||||
handle.shutdown.disarm();
|
||||
|
||||
@@ -101,35 +107,29 @@ where
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(timeout_err) => {
|
||||
warn!("websocket handshake timedout: {timeout_err}");
|
||||
Err(_elapsed) => {
|
||||
warn!("websocket handshake timeout");
|
||||
return;
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
warn!("Failed to complete WebSocket handshake: {err}. Stopping the handler");
|
||||
debug!("failed to complete WebSocket handshake: {err}. Stopping the handler");
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
trace!("Managed to perform websocket handshake!");
|
||||
trace!("managed to perform websocket handshake!");
|
||||
|
||||
let shutdown = handle.shutdown.clone();
|
||||
match handle.perform_initial_authentication().await {
|
||||
// For storage error, we want to print the extended storage error, but without
|
||||
// including it in the error that's returned to the clients
|
||||
Err(InitialAuthenticationError::StorageError(err)) => {
|
||||
warn!("authentication has failed: {err}");
|
||||
return;
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("authentication has failed: {err}");
|
||||
return;
|
||||
}
|
||||
Ok(auth_handle) => auth_handle.listen_for_requests(shutdown).await,
|
||||
let mut shutdown = handle.shutdown.clone();
|
||||
|
||||
if let Some(auth_handle) = handle
|
||||
.handle_until_authenticated_or_failure(&mut shutdown)
|
||||
.await
|
||||
{
|
||||
auth_handle.listen_for_requests(shutdown).await
|
||||
}
|
||||
|
||||
trace!("The handler is done!");
|
||||
trace!("the handler is done!");
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Config> for BandwidthFlushingBehaviourConfig {
|
||||
|
||||
@@ -9,6 +9,6 @@ EXPOSE 8089
|
||||
COPY --from=builder /usr/src/nym/target/release/nym-network-monitor /bin/nym-network-monitor
|
||||
COPY --from=builder /usr/src/nym/nym-network-monitor/locustfile.py locustfile.py
|
||||
COPY --from=builder /usr/src/nym/nym-network-monitor/entrypoint.sh entrypoint.sh
|
||||
COPY --from=builder /usr/src/nym/envs/mainnet.env mainnet.env
|
||||
COPY --from=builder /usr/src/nym/envs envs
|
||||
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
|
||||
@@ -4,15 +4,17 @@
|
||||
|
||||
set -ex
|
||||
|
||||
users=${2:-10}
|
||||
timeout=${1:-600}
|
||||
private_key=${1}
|
||||
network=${2:-mainnet}
|
||||
timeout=${3:-600}
|
||||
users=${4:-10}
|
||||
|
||||
RUST_LOG=info nym-network-monitor --env mainnet.env --host 127.0.0.1 --port 8080 &
|
||||
RUST_LOG=info nym-network-monitor --env envs/"${network}".env --host 127.0.0.1 --port 8080 --private-key "${private_key}" &
|
||||
nnm_pid=$!
|
||||
|
||||
sleep 10
|
||||
|
||||
python -m locust -H http://127.0.0.1:8080 --processes 4 --autostart --autoquit 60 -u "$users" -t "$timeout"s &
|
||||
python -m locust -H http://127.0.0.1:8080 --processes 4 --autostart --autoquit 60 -u "${users}" -t "${timeout}"s &
|
||||
locust_pid=$!
|
||||
|
||||
wait $locust_pid
|
||||
|
||||
Generated
+14
-8
@@ -2209,6 +2209,12 @@ version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
@@ -3195,7 +3201,7 @@ dependencies = [
|
||||
"nym-network-defaults",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"strum 0.25.0",
|
||||
"strum 0.26.3",
|
||||
"thiserror",
|
||||
"time",
|
||||
]
|
||||
@@ -3403,7 +3409,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.8",
|
||||
"strum 0.25.0",
|
||||
"strum 0.26.3",
|
||||
"thiserror",
|
||||
"ts-rs",
|
||||
"url",
|
||||
@@ -5188,11 +5194,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.25.0"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
dependencies = [
|
||||
"strum_macros 0.25.3",
|
||||
"strum_macros 0.26.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5210,11 +5216,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.25.3"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
|
||||
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
|
||||
Reference in New Issue
Block a user