From cdd9b5e1dabdefefdfa792e1ff82d00432a71d66 Mon Sep 17 00:00:00 2001 From: bochaco Date: Mon, 8 Apr 2024 15:38:16 -0300 Subject: [PATCH] draft: node-mgr and node args to run as sybil nodes eclipsing a specified xorname --- Cargo.lock | 2 ++ sn_networking/src/cmd.rs | 6 ++++++ sn_networking/src/driver.rs | 10 ++++++++++ sn_node/src/bin/safenode/main.rs | 19 +++++++++++++++++++ sn_node/src/node.rs | 6 ++++++ sn_node_manager/Cargo.toml | 2 ++ sn_node_manager/src/bin/cli/main.rs | 10 ++++++++++ sn_node_manager/src/cmd/local.rs | 20 +++++++++++++++++--- sn_node_manager/src/local.rs | 16 ++++++++++++++-- 9 files changed, 86 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee788c5261..c85086974b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5177,6 +5177,7 @@ dependencies = [ "color-eyre", "colored", "dirs-next", + "hex", "indicatif", "libp2p", "libp2p-identity", @@ -5200,6 +5201,7 @@ dependencies = [ "tracing", "users", "uuid", + "xor_name", ] [[package]] diff --git a/sn_networking/src/cmd.rs b/sn_networking/src/cmd.rs index 355d3d276d..2a1d08fb85 100644 --- a/sn_networking/src/cmd.rs +++ b/sn_networking/src/cmd.rs @@ -364,6 +364,8 @@ impl SwarmDriver { .payment_received(); } SwarmCmd::GetLocalRecord { key, sender } => { + // TODO: eclipse content if sybil was set, if sybil xorname set is close to the key ....? + cmd_string = "GetLocalRecord"; let record = self .swarm @@ -379,6 +381,8 @@ impl SwarmDriver { sender, quorum, } => { + // TODO: eclipse content if sybil was set, if sybil xorname set is close to the key ....? + cmd_string = "PutRecord"; let record_key = PrettyPrintRecordKey::from(&record.key).into_owned(); trace!( @@ -431,6 +435,8 @@ impl SwarmDriver { } } SwarmCmd::PutLocalRecord { record } => { + // TODO: eclipse content if sybil was set, if sybil xorname set is close to the key ....? + cmd_string = "PutLocalRecord"; let key = record.key.clone(); let record_key = PrettyPrintRecordKey::from(&key); diff --git a/sn_networking/src/driver.rs b/sn_networking/src/driver.rs index b91ddfa112..e9bb628bfb 100644 --- a/sn_networking/src/driver.rs +++ b/sn_networking/src/driver.rs @@ -65,6 +65,7 @@ use std::{ use tokio::sync::{mpsc, oneshot}; use tokio::time::Duration; use tracing::warn; +use xor_name::XorName; /// Interval over which we check for the farthest record we _should_ be holding /// based upon our knowledge of the CLOSE_GROUP @@ -185,6 +186,7 @@ pub struct NetworkBuilder { metrics_registry: Option, #[cfg(feature = "open-metrics")] metrics_server_port: u16, + sybil: Option, } impl NetworkBuilder { @@ -200,6 +202,7 @@ impl NetworkBuilder { metrics_registry: None, #[cfg(feature = "open-metrics")] metrics_server_port: 0, + sybil: None, } } @@ -225,6 +228,10 @@ impl NetworkBuilder { self.metrics_server_port = port; } + pub fn set_sybil_mode(&mut self, sybil: Option) { + self.sybil = sybil; + } + /// Creates a new `SwarmDriver` instance, along with a `Network` handle /// for sending commands and an `mpsc::Receiver` for receiving /// network events. It initializes the swarm, sets up the transport, and @@ -488,6 +495,7 @@ impl NetworkBuilder { replication_fetcher, #[cfg(feature = "open-metrics")] network_metrics, + sybil: self.sybil, cmd_receiver: swarm_cmd_receiver, event_sender: network_event_sender, pending_get_closest_peers: Default::default(), @@ -536,6 +544,8 @@ pub struct SwarmDriver { #[allow(unused)] pub(crate) network_metrics: NetworkMetrics, + sybil: Option, + cmd_receiver: mpsc::Receiver, event_sender: mpsc::Sender, // Use `self.send_event()` to send a NetworkEvent. diff --git a/sn_node/src/bin/safenode/main.rs b/sn_node/src/bin/safenode/main.rs index 7659f51a93..9fc3ad3de4 100644 --- a/sn_node/src/bin/safenode/main.rs +++ b/sn_node/src/bin/safenode/main.rs @@ -143,6 +143,15 @@ struct Opt { #[clap(long)] local: bool, + /// Set to have the node to act as sybil node eclipsing a specified CID/address. + /// + /// A hex-encoded xorname shall be provided to eclipse the content at such address + /// by dropping any provider record, as well as queries, targeting such address. + /// This can be used for testing sybil defense and detection using an address which belongs to + /// only test content so real users content is not affected. + #[clap(long, name = "CID's xorname")] + sybil: Option, + #[cfg(feature = "open-metrics")] /// Specify the port for the OpenMetrics server. /// @@ -173,6 +182,15 @@ fn main() -> Result<()> { info!("Node started with initial_peers {bootstrap_peers:?}"); + let sybil = opt.sybil.map(|xorname_str| { + let bytes = hex::decode(xorname_str).unwrap(); + let mut arr = [0u8; xor_name::XOR_NAME_LEN]; + arr.copy_from_slice(&bytes); + let xorname = xor_name::XorName(arr); + info!("Running as sybil node to eclipse XorName: {xorname}"); + xorname + }); + // Create a tokio runtime per `run_node` attempt, this ensures // any spawned tasks are closed before we would attempt to run // another process with these args. @@ -185,6 +203,7 @@ fn main() -> Result<()> { bootstrap_peers, opt.local, root_dir, + sybil, ); #[cfg(feature = "open-metrics")] let mut node_builder = node_builder; diff --git a/sn_node/src/node.rs b/sn_node/src/node.rs index df0998ea47..f59087dc59 100644 --- a/sn_node/src/node.rs +++ b/sn_node/src/node.rs @@ -44,6 +44,7 @@ use tokio::{ sync::{broadcast, mpsc::Receiver}, task::{spawn, JoinHandle}, }; +use xor_name::XorName; /// Interval to trigger replication of all records to all peers. /// This is the max time it should take. Minimum interval at any ndoe will be half this @@ -63,6 +64,7 @@ pub struct NodeBuilder { initial_peers: Vec, local: bool, root_dir: PathBuf, + sybil: Option, #[cfg(feature = "open-metrics")] metrics_server_port: u16, } @@ -75,6 +77,7 @@ impl NodeBuilder { initial_peers: Vec, local: bool, root_dir: PathBuf, + sybil: Option, ) -> Self { Self { keypair, @@ -82,6 +85,7 @@ impl NodeBuilder { initial_peers, local, root_dir, + sybil, #[cfg(feature = "open-metrics")] metrics_server_port: 0, } @@ -135,6 +139,8 @@ impl NodeBuilder { #[cfg(feature = "open-metrics")] network_builder.metrics_server_port(self.metrics_server_port); + network_builder.set_sybil_mode(self.sybil); + let (network, network_event_receiver, swarm_driver) = network_builder.build_node()?; let node_events_channel = NodeEventsChannel::default(); let (node_cmds, _) = broadcast::channel(10); diff --git a/sn_node_manager/Cargo.toml b/sn_node_manager/Cargo.toml index 8c51e501fa..a2be39235d 100644 --- a/sn_node_manager/Cargo.toml +++ b/sn_node_manager/Cargo.toml @@ -34,6 +34,7 @@ clap = { version = "4.4.6", features = ["derive", "env"] } colored = "2.0.4" color-eyre = "~0.6" dirs-next = "2.0.0" +hex = "0.4.3" indicatif = { version = "0.17.5", features = ["tokio"] } libp2p = { version = "0.53", features = [] } libp2p-identity = { version = "0.2.7", features = ["rand"] } @@ -54,6 +55,7 @@ tracing = { version = "~0.1.26" } prost = { version = "0.9" } tonic = { version = "0.6.2" } uuid = { version = "1.5.0", features = ["v4"] } +xor_name = "5.0.0" [target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] nix = { version = "0.27.1", features = ["fs", "user"] } diff --git a/sn_node_manager/src/bin/cli/main.rs b/sn_node_manager/src/bin/cli/main.rs index 4a2ff8ccd0..1ba4498a39 100644 --- a/sn_node_manager/src/bin/cli/main.rs +++ b/sn_node_manager/src/bin/cli/main.rs @@ -172,6 +172,14 @@ pub enum SubCmd { /// The number of nodes to run. #[clap(long, default_value_t = DEFAULT_NODE_COUNT)] count: u16, + /// Set to have the node/s joining to act as sybil nodes eclipsing a specified CID/address. + /// + /// A hex-encoded xorname shall be provided to have nodes to eclipse the content at such address + /// by dropping any provider record, as well as queries, targeting such address. + /// This can be used for testing sybil defense and detection using an address which belongs to + /// only test content so real users content is not affected. + #[clap(long, name = "CID's xorname")] + sybil: Option, /// Path to a faucet binary /// /// The path and version arguments are mutually exclusive. @@ -647,6 +655,7 @@ async fn main() -> Result<()> { SubCmd::Join { build, count, + sybil, faucet_path, faucet_version, interval, @@ -658,6 +667,7 @@ async fn main() -> Result<()> { cmd::local::join( build, count, + sybil, faucet_path, faucet_version, interval, diff --git a/sn_node_manager/src/cmd/local.rs b/sn_node_manager/src/cmd/local.rs index 3e211c6ae7..1fe151b2d1 100644 --- a/sn_node_manager/src/cmd/local.rs +++ b/sn_node_manager/src/cmd/local.rs @@ -20,10 +20,12 @@ use sn_service_management::{ control::ServiceController, get_local_node_registry_path, NodeRegistry, }; use std::path::PathBuf; +use xor_name::XOR_NAME_LEN; pub async fn join( build: bool, count: u16, + sybil: Option, faucet_path: Option, faucet_version: Option, interval: u64, @@ -32,9 +34,19 @@ pub async fn join( peers: PeersArgs, skip_validation: bool, ) -> Result<(), Report> { - println!("================================================="); - println!(" Joining Local Network "); - println!("================================================="); + let sybil = sybil.map(|xorname_str| { + let bytes = hex::decode(xorname_str).unwrap(); + let mut arr = [0u8; XOR_NAME_LEN]; + arr.copy_from_slice(&bytes); + xor_name::XorName(arr) + }); + + println!("===================================================="); + println!(" Joining Local Network "); + if let Some(xorname) = sybil { + println!("** WITH SYBIL NODE/s TO ECLIPSE XorName: {xorname} **"); + } + println!("===================================================="); let local_node_reg_path = &get_local_node_registry_path()?; let mut local_node_registry = NodeRegistry::load(local_node_reg_path)?; @@ -71,6 +83,7 @@ pub async fn join( interval, join: true, node_count: count, + sybil, peers, safenode_bin_path: node_path, skip_validation, @@ -159,6 +172,7 @@ pub async fn run( join: false, interval, node_count: count, + sybil: None, peers: None, safenode_bin_path: node_path, skip_validation, diff --git a/sn_node_manager/src/local.rs b/sn_node_manager/src/local.rs index 6f2a9d3dff..21b8196e6e 100644 --- a/sn_node_manager/src/local.rs +++ b/sn_node_manager/src/local.rs @@ -35,6 +35,7 @@ pub trait Launcher { &self, rpc_socket_addr: SocketAddr, bootstrap_peers: Vec, + sybil: Option, ) -> Result<()>; fn wait(&self, delay: u64); } @@ -68,6 +69,7 @@ impl Launcher for LocalSafeLauncher { &self, rpc_socket_addr: SocketAddr, bootstrap_peers: Vec, + sybil: Option, ) -> Result<()> { let mut args = Vec::new(); if bootstrap_peers.is_empty() { @@ -81,6 +83,10 @@ impl Launcher for LocalSafeLauncher { args.push("--local".to_string()); args.push("--rpc".to_string()); args.push(rpc_socket_addr.to_string()); + if let Some(xorname) = sybil { + args.push("--sybil".to_string()); + args.push(hex::encode(xorname)); + } Command::new(self.safenode_bin_path.clone()) .args(args) @@ -165,6 +171,7 @@ pub struct LocalNetworkOptions { pub join: bool, pub interval: u64, pub node_count: u16, + pub sybil: Option, pub peers: Option>, pub safenode_bin_path: PathBuf, pub skip_validation: bool, @@ -203,6 +210,7 @@ pub async fn run_network( number, genesis: true, interval: options.interval, + sybil: None, rpc_socket_addr, bootstrap_peers: vec![], }, @@ -230,6 +238,7 @@ pub async fn run_network( number, genesis: false, interval: options.interval, + sybil: options.sybil, rpc_socket_addr, bootstrap_peers: bootstrap_peers.clone(), }, @@ -277,6 +286,7 @@ pub struct RunNodeOptions { pub number: u16, pub genesis: bool, pub interval: u64, + pub sybil: Option, pub rpc_socket_addr: SocketAddr, pub bootstrap_peers: Vec, } @@ -290,6 +300,7 @@ pub async fn run_node( launcher.launch_node( run_options.rpc_socket_addr, run_options.bootstrap_peers.clone(), + run_options.sybil, )?; launcher.wait(run_options.interval); @@ -417,9 +428,9 @@ mod tests { let rpc_socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 13000); mock_launcher .expect_launch_node() - .with(eq(rpc_socket_addr), eq(vec![])) + .with(eq(rpc_socket_addr), eq(vec![]), eq(None)) .times(1) - .returning(|_, _| Ok(())); + .returning(|_, _, _| Ok(())); mock_launcher .expect_wait() .with(eq(100)) @@ -459,6 +470,7 @@ mod tests { number: 1, genesis: true, interval: 100, + sybil: None, rpc_socket_addr, bootstrap_peers: vec![], },