Skip to content

Commit

Permalink
script/research/dam: Denial-of-service Analysis Multitool added
Browse files Browse the repository at this point in the history
  • Loading branch information
skoupidi committed Feb 21, 2025
1 parent 99ae01a commit 4c7fe93
Show file tree
Hide file tree
Showing 20 changed files with 1,130 additions and 0 deletions.
25 changes: 25 additions & 0 deletions script/research/dam/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
dam
=======

Denial-of-service Analysis Multitool.<br>
This is a suite of tools to simulate flooding attacks on a
P2P network, to verify and fine tune protection mechanisms
against them.<br>
A daemon, a command-line client and a localnet script are
provided.

## damd

Dummy daemon implementing some P2P communication protocols,
along with JSON-RPC endpoints to simulate flooding attacks
over the network.

## dam-cli

Command-line client for `damd`, to trigger flooding attacks
and monitor responses.

## dam-localnet

Localnet folder with script and configuration to deploy
instances to test with.
4 changes: 4 additions & 0 deletions script/research/dam/dam-cli/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/target
Cargo.lock
rustfmt.toml
dam-cli
20 changes: 20 additions & 0 deletions script/research/dam/dam-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "dam-cli"
version = "0.4.1"
description = "CLI-utility to control a Denial-of-service Analysis Multitool daemon."
authors = ["Dyne.org foundation <[email protected]>"]
repository = "https://codeberg.org/darkrenaissance/darkfi"
license = "AGPL-3.0-only"
edition = "2021"

[workspace]

[dependencies]
# Darkfi
darkfi = {path = "../../../../", features = ["async-sdk", "rpc"]}
darkfi-serial = "0.4.2"

# Misc
clap = {version = "4.4.11", features = ["derive"]}
smol = "2.0.2"
url = "2.5.4"
37 changes: 37 additions & 0 deletions script/research/dam/dam-cli/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
.POSIX:

# Install prefix
PREFIX = $(HOME)/.cargo

# Cargo binary
CARGO = cargo +nightly

# Compile target
RUST_TARGET = $(shell rustc -Vv | grep '^host: ' | cut -d' ' -f2)
# Uncomment when doing musl static builds
#RUSTFLAGS = -C target-feature=+crt-static -C link-self-contained=yes

SRC = \
Cargo.toml \
$(shell find src -type f -name '*.rs') \

BIN = $(shell grep '^name = ' Cargo.toml | cut -d' ' -f3 | tr -d '"')

all: $(BIN)

$(BIN): $(SRC)
RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) build --target=$(RUST_TARGET) --release --package $@
cp -f target/$(RUST_TARGET)/release/$@ $@

fmt:
$(CARGO) fmt --all

clippy:
RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) clippy --target=$(RUST_TARGET) \
--release --all-features --workspace --tests

clean:
RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) clean --target=$(RUST_TARGET) --release --package $(BIN)
rm -f $(BIN)

.PHONY: all fmt clippy clean
39 changes: 39 additions & 0 deletions script/research/dam/dam-cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2025 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

use darkfi::{rpc::client::RpcClient, system::ExecutorPtr, Result};
use url::Url;

/// damd JSON-RPC related methods
pub mod rpc;

/// CLI-util structure
pub struct DamCli {
/// JSON-RPC client to execute requests to damd daemon
pub rpc_client: RpcClient,
}

impl DamCli {
pub async fn new(endpoint: &str, ex: &ExecutorPtr) -> Result<Self> {
// Initialize rpc client
let endpoint = Url::parse(endpoint)?;
let rpc_client = RpcClient::new(endpoint, ex.clone()).await?;

Ok(Self { rpc_client })
}
}
98 changes: 98 additions & 0 deletions script/research/dam/dam-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2025 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

use std::sync::Arc;

use clap::{Parser, Subcommand};
use darkfi::{cli_desc, rpc::util::JsonValue, Result};
use smol::Executor;

use dam_cli::DamCli;

#[derive(Parser)]
#[command(about = cli_desc!())]
struct Args {
#[arg(short, long, default_value = "tcp://127.0.0.1:34780")]
/// damd JSON-RPC endpoint
endpoint: String,

#[command(subcommand)]
/// Sub command to execute
command: Subcmd,
}

#[derive(Subcommand)]
enum Subcmd {
/// Send a ping request to the damd RPC endpoint
Ping,

/// This subscription will listen for incoming notifications from damd
Subscribe {
/// The method to subscribe to
method: String,
},

/// Signal damd to execute a flooding attack against the network
Flood,

/// Signal damd to stop an ongoing flooding attack
StopFlood,
}

fn main() -> Result<()> {
// Initialize an executor
let executor = Arc::new(Executor::new());
let ex = executor.clone();
smol::block_on(executor.run(async {
// Parse arguments
let args = Args::parse();

// Execute a subcommand
let dam_cli = DamCli::new(&args.endpoint, &ex).await?;
match args.command {
Subcmd::Ping => {
dam_cli.ping().await?;
}

Subcmd::Subscribe { method } => {
dam_cli.subscribe(&args.endpoint, &method, &ex).await?;
}

Subcmd::Flood => {
dam_cli
.damd_daemon_request(
"flood.switch",
&JsonValue::Array(vec![JsonValue::Boolean(true)]),
)
.await?;
}

Subcmd::StopFlood => {
dam_cli
.damd_daemon_request(
"flood.switch",
&JsonValue::Array(vec![JsonValue::Boolean(false)]),
)
.await?;
}
}
dam_cli.rpc_client.stop().await;

Ok(())
}))
}
136 changes: 136 additions & 0 deletions script/research/dam/dam-cli/src/rpc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2025 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

use std::time::Instant;

use darkfi::{
rpc::{
client::RpcClient,
jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResult},
util::JsonValue,
},
system::{ExecutorPtr, Publisher, StoppableTask},
Error, Result,
};
use url::Url;

use crate::DamCli;

impl DamCli {
/// Auxiliary function to ping configured damd daemon for liveness.
pub async fn ping(&self) -> Result<()> {
println!("Executing ping request to damd...");
let latency = Instant::now();
let rep = self.damd_daemon_request("ping", &JsonValue::Array(vec![])).await?;
let latency = latency.elapsed();
println!("Got reply: {rep:?}");
println!("Latency: {latency:?}");
Ok(())
}

/// Auxiliary function to execute a request towards the configured damd daemon JSON-RPC endpoint.
pub async fn damd_daemon_request(&self, method: &str, params: &JsonValue) -> Result<JsonValue> {
let req = JsonRequest::new(method, params.clone());
let rep = self.rpc_client.request(req).await?;
Ok(rep)
}

/// Subscribes to damd's JSON-RPC notification endpoints.
pub async fn subscribe(&self, endpoint: &str, method: &str, ex: &ExecutorPtr) -> Result<()> {
println!("Subscribing to receive notifications for: {method}");
let endpoint = Url::parse(endpoint)?;
let _method = String::from(method);
let publisher = Publisher::new();
let subscription = publisher.clone().subscribe().await;
let _publisher = publisher.clone();
let _ex = ex.clone();
StoppableTask::new().start(
// Weird hack to prevent lifetimes hell
async move {
let rpc_client = RpcClient::new(endpoint, _ex).await?;
let req = JsonRequest::new(&_method, JsonValue::Array(vec![]));
rpc_client.subscribe(req, _publisher).await
},
|res| async move {
match res {
Ok(()) => { /* Do nothing */ }
Err(e) => {
eprintln!("[subscribe] JSON-RPC server error: {e:?}");
publisher
.notify(JsonResult::Error(JsonError::new(
ErrorCode::InternalError,
None,
0,
)))
.await;
}
}
},
Error::RpcServerStopped,
ex.clone(),
);
println!("Detached subscription to background");
println!("All is good. Waiting for new notifications...");

let e = loop {
match subscription.receive().await {
JsonResult::Notification(n) => {
println!("Got notification from subscription");
if n.method != method {
break Error::UnexpectedJsonRpc(format!(
"Got foreign notification from damd: {}",
n.method
))
}

// Verify parameters
if !n.params.is_array() {
break Error::UnexpectedJsonRpc(
"Received notification params are not an array".to_string(),
)
}
let params = n.params.get::<Vec<JsonValue>>().unwrap();
if params.is_empty() {
break Error::UnexpectedJsonRpc(
"Notification parameters are empty".to_string(),
)
}

for param in params {
let param = param.get::<String>().unwrap();
println!("Notification: {param}");
}
}

JsonResult::Error(e) => {
// Some error happened in the transmission
break Error::UnexpectedJsonRpc(format!("Got error from JSON-RPC: {e:?}"))
}

x => {
// And this is weird
break Error::UnexpectedJsonRpc(format!(
"Got unexpected data from JSON-RPC: {x:?}"
))
}
}
};

Err(e)
}
}
2 changes: 2 additions & 0 deletions script/research/dam/dam-localnet/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
damd0
damd1
9 changes: 9 additions & 0 deletions script/research/dam/dam-localnet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
dam localnet
================

This will start two `damd` node instances in localnet mode.
The first node is considered the defender, and we will listen
to its incoming messages, while the second one is the attacker,
so we will listen to its outgoing messages.
Second node can be queried to start attacking the other one,
using `dam-cli`.
2 changes: 2 additions & 0 deletions script/research/dam/dam-localnet/clean.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
rm -rf damd0 damd1
Loading

0 comments on commit 4c7fe93

Please sign in to comment.