Compare commits

...

8 Commits

Author SHA1 Message Date
Jędrzej Stuczyński 87bb3b4b82 chore: nym-node version bump 2026-06-05 18:56:12 +01:00
Jędrzej Stuczyński d78ad2ef15 update handle_update_peer_psk_request 2026-06-05 18:56:09 +01:00
Bogdan-Ștefan Neacşu b614534dc2 Fix unit test 2026-06-05 18:56:09 +01:00
Bogdan-Ștefan Neacşu 3de9c8419a Keep peer in wg table when updating psk 2026-06-05 18:56:09 +01:00
Jędrzej Stuczyński 7a9e846d89 feat: disable Nagle's algorithm for LP between nym-nodes 2026-06-05 16:37:26 +01:00
benedettadavico 761f970912 backport ci to waterloo 2026-05-29 14:40:25 +02:00
benedettadavico 0cdefc5881 Merge remote-tracking branch 'origin/release/2026.10-waterloo' into release/2026.10-waterloo 2026-05-27 16:53:10 +02:00
benedettadavico 85454dc431 fix crates bump 2026-05-27 16:53:00 +02:00
12 changed files with 229 additions and 24 deletions
@@ -0,0 +1,63 @@
name: ci-build-upload-network-monitor-agent
on:
workflow_dispatch:
jobs:
build-and-upload:
strategy:
fail-fast: false
matrix:
platform: [arc-ubuntu-22.04]
runs-on: ${{ matrix.platform }}
env:
CARGO_TERM_COLOR: always
RUSTUP_PERMIT_COPY_RENAME: 1
steps:
- uses: actions/checkout@v6
- name: Prepare build output directory
shell: bash
env:
OUTPUT_DIR: ci-builds/${{ github.ref_name }}
run: |
rm -rf ci-builds || true
mkdir -p "$OUTPUT_DIR"
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libudev-dev
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ vars.REQUIRED_RUSTC_VERSION }}
- name: Build nym-network-monitor-agent
shell: bash
run: cargo build -p nym-network-monitor-agent --release
- name: Upload artifact
uses: actions/upload-artifact@v6
with:
name: nym-network-monitor-agent
path: target/release/nym-network-monitor-agent
retention-days: 30
- name: Prepare build output
shell: bash
env:
OUTPUT_DIR: ci-builds/${{ github.ref_name }}
run: cp target/release/nym-network-monitor-agent "$OUTPUT_DIR"
- name: Deploy to CI www
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-avzr"
SOURCE: "ci-builds/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/
EXCLUDE: "/dist/, /node_modules/"
+16 -6
View File
@@ -19,6 +19,7 @@ jobs:
RUSTUP_PERMIT_COPY_RENAME: 1
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repo
uses: actions/checkout@v6
@@ -66,11 +67,20 @@ jobs:
--no-git-commit \
--yes
- name: Commit and push version bump
run: |
git add -A
git commit -m "crates release: bump version to ${{ inputs.version }}"
git push
- name: Create pull request
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: "chore/bump-version-${{ inputs.version }}"
base: ${{ github.ref_name }}
commit-message: "crates release: bump version to ${{ inputs.version }}"
title: "chore: bump crate versions to ${{ inputs.version }}"
body: |
Automated version bump from `${{ steps.current_version.outputs.version }}` → `${{ inputs.version }}`.
Triggered by @${{ github.actor }} via workflow dispatch.
labels: "automated, crates-version-bump"
delete-branch: true
- name: Show package versions
run: cargo workspaces list --long
run: cargo workspaces list --long
Generated
+1 -1
View File
@@ -7608,7 +7608,7 @@ dependencies = [
[[package]]
name = "nym-node"
version = "1.32.0"
version = "1.32.1"
dependencies = [
"anyhow",
"arc-swap",
@@ -14,6 +14,7 @@ use tokio::sync::mpsc::Receiver;
#[derive(Hash, PartialOrd, PartialEq, Clone, Debug, Eq, Copy)]
pub enum PeerControlRequestTypeV2 {
AddPeer,
UpdatePeerPsk,
RemovePeer,
QueryPeer,
GetClientBandwidthByKey,
@@ -26,6 +27,7 @@ impl From<&PeerControlRequest> for PeerControlRequestTypeV2 {
fn from(req: &PeerControlRequest) -> Self {
match req {
PeerControlRequest::AddPeer { .. } => PeerControlRequestTypeV2::AddPeer,
PeerControlRequest::UpdatePeerPsk { .. } => PeerControlRequestTypeV2::UpdatePeerPsk,
PeerControlRequest::PreAllocateIpPair { .. } => PeerControlRequestTypeV2::AddPeer,
PeerControlRequest::RemovePeer { .. } => PeerControlRequestTypeV2::RemovePeer,
PeerControlRequest::QueryPeer { .. } => PeerControlRequestTypeV2::QueryPeer,
@@ -115,6 +117,15 @@ impl MockPeerControllerV2 {
)
.unwrap();
}
PeerControlRequest::UpdatePeerPsk { response_tx, .. } => {
response_tx
.send(
*response
.downcast()
.expect("registered response has mismatched type"),
)
.unwrap();
}
PeerControlRequest::PreAllocateIpPair { response_tx, .. } => {
response_tx
.send(
@@ -71,6 +71,7 @@ impl From<&Key> for KeyWrapper {
#[derive(Hash, PartialOrd, PartialEq, Clone, Debug, Eq)]
pub enum PeerControlRequestType {
AddPeer { public_key: KeyWrapper },
UpdatePeerPsk { peer_key: KeyWrapper },
AllocatePeerIpPair {},
ReleaseIpPair { ip_pair: IpPair },
RemovePeer { key: KeyWrapper },
@@ -86,6 +87,7 @@ impl PeerControlRequestType {
pub fn peer_key(&self) -> Option<KeyWrapper> {
match self {
PeerControlRequestType::AddPeer { public_key } => Some(public_key.clone()),
PeerControlRequestType::UpdatePeerPsk { peer_key } => Some(peer_key.clone()),
PeerControlRequestType::AllocatePeerIpPair {} => None,
PeerControlRequestType::ReleaseIpPair { .. } => None,
PeerControlRequestType::RemovePeer { key } => Some(key.clone()),
@@ -109,6 +111,11 @@ impl From<&PeerControlRequest> for PeerControlRequestType {
PeerControlRequest::AddPeer { peer, .. } => PeerControlRequestType::AddPeer {
public_key: (&peer.public_key).into(),
},
PeerControlRequest::UpdatePeerPsk { peer_key, .. } => {
PeerControlRequestType::UpdatePeerPsk {
peer_key: peer_key.into(),
}
}
PeerControlRequest::PreAllocateIpPair { .. } => {
PeerControlRequestType::AllocatePeerIpPair {}
}
@@ -271,6 +278,9 @@ impl MockPeerController {
}
response_tx.send_downcasted(response.content)
}
PeerControlRequest::UpdatePeerPsk { response_tx, .. } => {
response_tx.send_downcasted(response.content)
}
PeerControlRequest::PreAllocateIpPair { response_tx, .. } => {
response_tx.send_downcasted(response.content)
}
@@ -76,6 +76,12 @@ pub enum PeerControlRequest {
peer: Peer,
response_tx: oneshot::Sender<AddPeerControlResponse>,
},
/// Update PSK for an existing peer, without changing its IP allocation
UpdatePeerPsk {
peer_key: Key,
psk: Key,
response_tx: oneshot::Sender<UpdatePeerPskControlResponse>,
},
/// Attempt to allocate an IP pair from the pool
PreAllocateIpPair {
response_tx: oneshot::Sender<AllocatePeerControlResponse>,
@@ -118,6 +124,7 @@ pub enum PeerControlRequest {
}
pub type AddPeerControlResponse = Result<()>;
pub type UpdatePeerPskControlResponse = Result<()>;
pub type AllocatePeerControlResponse = Result<IpPair>;
pub type ReleaseIpPairControlResponse = Result<()>;
pub type RemovePeerControlResponse = Result<()>;
@@ -317,6 +324,50 @@ impl PeerController {
Ok(())
}
async fn handle_update_peer_psk_request(&mut self, peer_key: &Key, psk: Key) -> Result<()> {
// observation will get automatically added once dropped
let _metric_timer =
PROMETHEUS_METRICS.start_timer(PrometheusMetric::WireguardDefguardPeerPskUpdate);
nym_metrics::inc!("wg_peer_update_psk_attempts");
let Ok(Some(mut peer)) = self.handle_query_peer_by_key(peer_key).await else {
return Ok(());
};
let encoded_psk = psk.to_lower_hex();
peer.preshared_key = Some(psk);
// Account for bandwidth used so far *before* reconfiguring: `configure_peer`
// isn't guaranteed to preserve the kernel rx/tx counters, so fold the
// accrued bytes into the metrics first to avoid losing them on a reset.
if let Ok(host) = self.wg_api.read_interface_data() {
self.update_metrics(&host).await;
*self.host_information.write().await = host;
}
// Try to update WireGuard peer
if let Err(e) = self.wg_api.configure_peer(&peer) {
nym_metrics::inc!("wg_peer_update_psk_failed");
nym_metrics::inc!("wg_config_errors_total");
return Err(e.into());
};
// Persist the new PSK to disk so it survives a restart. Kernel-first: a
// failure here leaves the live session working, only risking drift on restart.
self.ecash_verifier
.storage()
.update_peer_psk(&peer_key.to_string(), Some(&encoded_psk))
.await?;
// Refresh again so the cached host information reflects the post-update state
if let Ok(host) = self.wg_api.read_interface_data() {
*self.host_information.write().await = host;
}
nym_metrics::inc!("wg_peer_update_psk_success");
Ok(())
}
/// Allocate IP pair from pool for a new peer registration
///
/// This only allocates IPs - the caller must handle database storage and
@@ -513,6 +564,15 @@ impl PeerController {
PeerControlRequest::AddPeer { peer, response_tx } => {
response_tx.send(self.handle_add_request(&peer).await).ok();
}
PeerControlRequest::UpdatePeerPsk {
peer_key,
psk,
response_tx,
} => {
response_tx
.send(self.handle_update_peer_psk_request(&peer_key, psk).await)
.ok();
}
PeerControlRequest::PreAllocateIpPair { response_tx } => {
response_tx.send(self.handle_ip_allocation_request()).ok();
}
@@ -15,25 +15,14 @@ use std::time::Instant;
impl PeerRegistrator {
/// In the case of an already registered WG peer, update its PSK.
///
/// The peer controller keeps the active config and the on-disk PSK in sync.
pub(super) async fn update_peer_psk(
&self,
peer: PeerPublicKey,
psk: Key,
) -> Result<(), GatewayWireguardError> {
// 1. check if the peer is currently being handled
if self.peer_manager.check_active_peer(peer).await? {
// 2. if so, force disconnect it (as we're handling new request from the same peer)
self.peer_manager.remove_peer(peer).await?;
}
// 3. update the on-disk PSK
let encoded_psk = psk.to_lower_hex();
self.ecash_verifier
.storage()
.update_peer_psk(&peer.to_string(), Some(&encoded_psk))
.await?;
Ok(())
self.peer_manager.update_peer_psk(peer, psk).await
}
fn lp_peer_to_final_response(
@@ -125,6 +125,44 @@ impl PeerManager {
res
}
pub async fn update_peer_psk(
&self,
pub_key: PeerPublicKey,
psk: Key,
) -> Result<(), GatewayWireguardError> {
let controller_start = Instant::now();
let peer_key = Key::new(pub_key.to_bytes());
let (response_tx, response_rx) = oneshot::channel();
let msg = PeerControlRequest::UpdatePeerPsk {
peer_key,
psk,
response_tx,
};
self.wireguard_gateway_data
.peer_tx()
.send(msg)
.await
.map_err(|_| GatewayWireguardError::PeerInteractionStopped)?;
let res = response_rx
.await
.map_err(|_| GatewayWireguardError::internal("no response for update peer psk"))?
.map_err(|err| {
GatewayWireguardError::InternalError(format!(
"updating peer psk could not be performed: {err:?}"
))
});
let latency = controller_start.elapsed().as_secs_f64();
add_histogram_obs!(
"wg_peer_controller_channel_latency_seconds",
latency,
WG_CONTROLLER_LATENCY_BUCKETS
);
res
}
pub async fn remove_peer(&self, pub_key: PeerPublicKey) -> Result<(), GatewayWireguardError> {
let controller_start = Instant::now();
let key = Key::new(pub_key.to_bytes());
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-node"
version = "1.32.0"
version = "1.32.1"
authors.workspace = true
edition.workspace = true
license = "GPL-3.0"
@@ -157,6 +157,9 @@ pub enum PrometheusMetric {
#[strum(props(help = "The distribution of defguard peer creation time"))]
WireguardDefguardPeerCreation,
#[strum(props(help = "The distribution of defguard peer psk update time"))]
WireguardDefguardPeerPskUpdate,
#[strum(props(
help = "The distribution of time it takes to verify a credential during peer registration"
))]
@@ -320,6 +323,9 @@ impl PrometheusMetric {
PrometheusMetric::WireguardDefguardPeerCreation => {
Metric::new_histogram(&name, help, Some(REG_LATENCY_BUCKETS))
}
PrometheusMetric::WireguardDefguardPeerPskUpdate => {
Metric::new_histogram(&name, help, Some(REG_LATENCY_BUCKETS))
}
PrometheusMetric::DvpnAuthenticatorClientRegistrationMsg1 => {
Metric::new_histogram(&name, help, Some(REG_LATENCY_BUCKETS))
}
@@ -452,7 +458,7 @@ mod tests {
// a sanity check for anyone adding new metrics. if this test fails,
// make sure any methods on `PrometheusMetric` enum don't need updating
// or require custom Display impl
assert_eq!(46, PrometheusMetric::COUNT)
assert_eq!(47, PrometheusMetric::COUNT)
}
#[test]
@@ -402,7 +402,7 @@ where
};
// Connect to target gateway with timeout
let stream = match timeout(Duration::from_secs(5), S::connect(target_addr)).await {
let mut stream = match timeout(Duration::from_secs(5), S::connect(target_addr)).await {
Ok(Ok(stream)) => stream,
Ok(Err(e)) => {
inc!("lp_forward_failed");
@@ -420,6 +420,16 @@ where
}
};
// Disable Nagle's algorithm: the forward stream carries small request/response
// handshake packets, so we want them sent immediately rather than coalesced.
if let Err(e) = stream.set_no_delay(true) {
inc!("lp_forward_failed");
return Err(LpHandlerError::ConnectionFailure {
egress: target_addr,
reason: format!("failed to set TCP_NODELAY: {e}"),
});
}
debug!("Opened persistent exit connection to {target_addr} for forwarding");
self.exit_stream = Some((stream, target_addr));
@@ -171,6 +171,14 @@ impl LpControlListener {
}
fn handle_connection(&self, stream: tokio::net::TcpStream, remote_addr: SocketAddr) {
// Disable Nagle's algorithm on the accepted socket so our responses are flushed
// immediately rather than coalesced. This is the write side of every reply we send,
// including handshake replies forwarded back to an entry gateway. Non-fatal: a valid
// connection should still be served if the option can't be set.
if let Err(e) = stream.set_nodelay(true) {
warn!("failed to set TCP_NODELAY on accepted LP connection from {remote_addr}: {e}");
}
if let Some(initiator_details) = self
.nodes_handler_state
.nodes