diff --git a/sn_networking/src/lib.rs b/sn_networking/src/lib.rs index 31a6d4523f..6db462b938 100644 --- a/sn_networking/src/lib.rs +++ b/sn_networking/src/lib.rs @@ -26,6 +26,7 @@ mod record_store; mod record_store_api; mod replication_fetcher; mod spends; +mod sybil; pub mod target_arch; mod transfers; mod transport; @@ -42,7 +43,7 @@ pub use self::{ transfers::{get_raw_signed_spends_from_record, get_signed_spend_from_record}, }; -use self::{cmd::SwarmCmd, error::Result}; +use self::{cmd::SwarmCmd, error::Result, sybil::check_for_sybil_attack}; use backoff::{Error as BackoffError, ExponentialBackoff}; use futures::future::select_all; use libp2p::{ @@ -55,7 +56,7 @@ use rand::Rng; use sn_protocol::{ error::Error as ProtocolError, messages::{ChunkProof, Cmd, Nonce, Query, QueryResponse, Request, Response}, - storage::{RecordType, RetryStrategy}, + storage::{ChunkAddress, RecordType, RetryStrategy}, NetworkAddress, PrettyPrintKBucketKey, PrettyPrintRecordKey, }; use sn_transfers::{MainPubkey, NanoTokens, PaymentQuote, QuotingMetrics}; @@ -64,13 +65,15 @@ use std::{ path::PathBuf, sync::Arc, }; -use tokio::sync::{ - mpsc::{self, Sender}, - oneshot, +use tokio::{ + sync::{ + mpsc::{self, Sender}, + oneshot, + }, + time::Duration, }; - -use tokio::time::Duration; use tracing::trace; +use xor_name::XorName; /// The type of quote for a selected payee. pub type PayeeQuote = (PeerId, MainPubkey, PaymentQuote); @@ -812,6 +815,23 @@ impl Network { Ok(closest_peers.into_iter().cloned().collect()) } + /// Using a random address, check if there is a sybil attack around it + pub async fn perform_sybil_attack_check(&self) { + let random_addr = { + let mut rng = rand::thread_rng(); + let chunk_addr = ChunkAddress::new(XorName::random(&mut rng)); + NetworkAddress::from_chunk_address(chunk_addr) + }; + + match self.get_closest_peers(&random_addr, true).await { + Ok(closest_peers) => match check_for_sybil_attack(&closest_peers).await { + Ok(is_attack) => debug!("Sybil attack detection result: {is_attack}"), + Err(err) => error!("Failed to check for sybil attack: {err:?}"), + }, + Err(err) => error!("Failed to get closes peer to check for sybil attack: {err:?}"), + } + } + /// Send a `Request` to the provided set of peers and wait for their responses concurrently. /// If `get_all_responses` is true, we wait for the responses from all the peers. /// NB TODO: Will return an error if the request timeouts. diff --git a/sn_networking/src/sybil.rs b/sn_networking/src/sybil.rs new file mode 100644 index 0000000000..cc8e8cfc70 --- /dev/null +++ b/sn_networking/src/sybil.rs @@ -0,0 +1,47 @@ +// Copyright 2024 MaidSafe.net limited. +// +// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3. +// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed +// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. Please review the Licences for the specific language governing +// permissions and limitations relating to use of the SAFE Network Software. + +use crate::Result; + +use libp2p::PeerId; + +// Threshold to determine if there is an attack using Kullback-Liebler (KL) divergence +// between model peer ids distribution vs. actual distribution around any point in the address space. +const KL_DIVERGENCE_THRESHOLD: usize = 10; + +pub(super) async fn check_for_sybil_attack(peers: &[PeerId]) -> Result { + let q = num_peers_per_cpl(peers)? / 20; + let n = get_net_size_estimate()?; + let p = compute_model_distribution(n)?; + let kl_divergence = compute_kl_divergence(p, q)?; + + let is_attack = kl_divergence > KL_DIVERGENCE_THRESHOLD; + Ok(is_attack) +} + +fn num_peers_per_cpl(peers: &[PeerId]) -> Result { + // TODO! + Ok(0usize) +} + +fn get_net_size_estimate() -> Result { + Ok(0usize) + // TODO! +} + +fn compute_model_distribution(net_size: usize) -> Result { + // TODO! + let model_dist = net_size; + Ok(model_dist) +} + +fn compute_kl_divergence(model_dist: usize, peers_per_cpl: usize) -> Result { + // TODO! + let kl = model_dist * peers_per_cpl; + Ok(kl) +} diff --git a/sn_node/src/node.rs b/sn_node/src/node.rs index 8dd1314cfe..19d6ffe8cd 100644 --- a/sn_node/src/node.rs +++ b/sn_node/src/node.rs @@ -251,14 +251,21 @@ impl Node { _ = bad_nodes_check_interval.tick() => { let start = std::time::Instant::now(); trace!("Periodic bad_nodes check triggered"); - let network = self.network.clone(); self.record_metrics(Marker::IntervalBadNodesCheckTriggered); + let network = self.network.clone(); let _handle = spawn(async move { Self::try_bad_nodes_check(network, rolling_index).await; trace!("Periodic bad_nodes check took {:?}", start.elapsed()); }); + // we also spawn a task to check for sybil peers + let network = self.network.clone(); + let _handle = spawn(async move { + network.perform_sybil_attack_check().await; + trace!("Checking for sybil peers took {:?}", start.elapsed()); + }); + if rolling_index == 511 { rolling_index = 0; } else {