|
@@ -1,8 +1,8 @@
|
|
-use std::{path::{Path, PathBuf}, fs::{File, self, OpenOptions}, io::{BufWriter, Write, self, Read, BufRead}, net::Ipv4Addr, process::{Command, self, Stdio}, time::{Instant, Duration}, thread};
|
|
|
|
|
|
+use std::{path::{Path, PathBuf}, fs::{File, self, OpenOptions}, io::{BufWriter, Write, self, Read, BufRead, BufReader}, net::Ipv4Addr, process::{Command, self, Stdio}, time::{Instant, Duration}, thread, collections::HashSet};
|
|
use json::JsonValue;
|
|
use json::JsonValue;
|
|
use log::info;
|
|
use log::info;
|
|
use rand::prelude::*;
|
|
use rand::prelude::*;
|
|
-use anyhow::{Context, ensure, anyhow};
|
|
|
|
|
|
+use anyhow::{Context, ensure, anyhow, bail};
|
|
|
|
|
|
mod args;
|
|
mod args;
|
|
mod error;
|
|
mod error;
|
|
@@ -17,9 +17,80 @@ pub const BENCH_BIN_PATH: &str = "bin";
|
|
pub const BENCH_LOG_PATH: &str = "log";
|
|
pub const BENCH_LOG_PATH: &str = "log";
|
|
|
|
|
|
const XDP_LOAD_TIMEOUT_SECS: u64 = 5;
|
|
const XDP_LOAD_TIMEOUT_SECS: u64 = 5;
|
|
|
|
+const HITRATE_ALLOWED_DIFFERENCE: f64 = 0.25;
|
|
|
|
+const HITRATE_GENERATION_ALLOWED_ATTEMPTS: usize = 50;
|
|
|
|
|
|
const PRIVILEGE_RUNNER: [&str;1] = ["sudo"];
|
|
const PRIVILEGE_RUNNER: [&str;1] = ["sudo"];
|
|
|
|
|
|
|
|
+#[derive(Clone, Debug)]
|
|
|
|
+struct IpDataSet {
|
|
|
|
+ path: PathBuf,
|
|
|
|
+ subnet_size: usize,
|
|
|
|
+ set: HashSet<Ipv4Addr>
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl IpDataSet {
|
|
|
|
+ fn from(path: PathBuf, subnet_size: usize) -> anyhow::Result<Self> {
|
|
|
|
+ let ip_file = File::open(&path).context(format!("Failed to open ip file {:?}", &path))?;
|
|
|
|
+ let reader = BufReader::new(ip_file);
|
|
|
|
+ let ips: HashSet<_> = reader
|
|
|
|
+ .lines()
|
|
|
|
+ .map(|s| s
|
|
|
|
+ .map_err(|e| anyhow!(e))
|
|
|
|
+ .and_then(|s| s.parse::<Ipv4Addr>().context("Failed to parse ipv4 from ip file")))
|
|
|
|
+ .collect::<Result<HashSet<_>,_>>()?;
|
|
|
|
+
|
|
|
|
+ Ok(Self {
|
|
|
|
+ path,
|
|
|
|
+ subnet_size,
|
|
|
|
+ set: ips
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fn generate_subnet(&self, subnet_size: usize, seed: u64) -> anyhow::Result<HashSet<Ipv4Addr>> {
|
|
|
|
+ if subnet_size == self.subnet_size {
|
|
|
|
+ return Ok(self.set.clone())
|
|
|
|
+ } else if subnet_size > self.subnet_size {
|
|
|
|
+ bail!("Could not generate subnet of size {}, bigger than dataset ({})", subnet_size, self.subnet_size);
|
|
|
|
+ } else {
|
|
|
|
+ let mut rng = SmallRng::seed_from_u64(seed);
|
|
|
|
+ let mask = (1u64 << (self.subnet_size as u64)) - (1u64 << (subnet_size as u64));
|
|
|
|
+ let mask = mask as u32;
|
|
|
|
+
|
|
|
|
+ let hitrate = self.set.len() as f64 / ((1u64 << (self.subnet_size as u64)) as f64);
|
|
|
|
+ info!("{} {} {}", hitrate * (1f64 - HITRATE_ALLOWED_DIFFERENCE), hitrate, hitrate*(1f64 + HITRATE_ALLOWED_DIFFERENCE));
|
|
|
|
+ for _ in 0..HITRATE_GENERATION_ALLOWED_ATTEMPTS {
|
|
|
|
+ let subnet = mask & rng.next_u32();
|
|
|
|
+ if let Some(res) = self.try_generate_subnet(subnet_size, mask, subnet, hitrate) {
|
|
|
|
+ if res.len() == 0 {
|
|
|
|
+ bail!("Failed to generate subnet of size {} with hitrate {}: results in empty set", subnet_size, hitrate)
|
|
|
|
+ }
|
|
|
|
+ return Ok(res)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ bail!("Failed to generate subnet of size {} with hitrate {} after {} attempts", subnet_size, hitrate, HITRATE_GENERATION_ALLOWED_ATTEMPTS);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fn try_generate_subnet(&self,subnet_size: usize, mask: u32, subnet: u32, hitrate: f64) -> Option<HashSet<Ipv4Addr>> {
|
|
|
|
+ let mut new_set = HashSet::new();
|
|
|
|
+
|
|
|
|
+ for ip in self.set.iter() {
|
|
|
|
+ if u32::from(*ip) & mask == subnet {
|
|
|
|
+ new_set.insert(*ip);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let new_hitrate = (new_set.len() as f64) / ((1u64 << subnet_size) as f64);
|
|
|
|
+ if (((new_hitrate as f64) / (hitrate as f64)) - 1f64).abs() > HITRATE_ALLOWED_DIFFERENCE {
|
|
|
|
+ info!("{}/{}: {}", Ipv4Addr::from(subnet), 32 - subnet_size, new_hitrate);
|
|
|
|
+ return None
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Some(new_set)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
fn log<R>(log_path: &Path, reader: &mut R, name: &str) -> anyhow::Result<()>
|
|
fn log<R>(log_path: &Path, reader: &mut R, name: &str) -> anyhow::Result<()>
|
|
where
|
|
where
|
|
R: ?Sized,
|
|
R: ?Sized,
|
|
@@ -57,10 +128,14 @@ pub fn run() -> Result<(), anyhow::Error> {
|
|
let cores: u32 = 4;
|
|
let cores: u32 = 4;
|
|
|
|
|
|
let seed: u64 = 0x1337133713371337;
|
|
let seed: u64 = 0x1337133713371337;
|
|
- let scan_sizes: Vec<u64> = vec![8, 16];//, 24];//,32]; // TODO 8 only for test purposes
|
|
|
|
|
|
+
|
|
|
|
+ let times: u64 = 2;
|
|
|
|
+
|
|
|
|
+ let scan_sizes: Vec<u64> = vec![16];//, 24];//,32]; // TODO 8 only for test purposes
|
|
// let scan_sizes: Vec<u64> = vec![24];
|
|
// let scan_sizes: Vec<u64> = vec![24];
|
|
// let hit_rates: Vec<f64> = vec![0.001, 0.0032,0.01,0.032,0.1];
|
|
// let hit_rates: Vec<f64> = vec![0.001, 0.0032,0.01,0.032,0.1];
|
|
- let hit_rates: Vec<f64> = vec![0.02];
|
|
|
|
|
|
+ info!("Loading in data sets");
|
|
|
|
+ let ip_data_sets: Vec<IpDataSet> = vec![IpDataSet::from(PathBuf::from("./data/http-scan.txt"), 32usize)?];
|
|
// let false_positive_rates: Vec<TestType> = vec![Baseline, EmptyFilter,Normal(0.1),Normal(0.01),Normal(0.001),Normal(0.0001)];
|
|
// let false_positive_rates: Vec<TestType> = vec![Baseline, EmptyFilter,Normal(0.1),Normal(0.01),Normal(0.001),Normal(0.0001)];
|
|
let false_positive_rates: Vec<TestType> = vec![Baseline, Normal(0.001), BpfStats(0.001)];
|
|
let false_positive_rates: Vec<TestType> = vec![Baseline, Normal(0.001), BpfStats(0.001)];
|
|
let filter_types: Vec<FilterType> = vec![Bitmap, Bloom];
|
|
let filter_types: Vec<FilterType> = vec![Bitmap, Bloom];
|
|
@@ -68,20 +143,24 @@ pub fn run() -> Result<(), anyhow::Error> {
|
|
// let scan_rates: Vec<u64> = vec![316_000, 562_000, 1_000_000, 1_780_000, 3_160_000];
|
|
// let scan_rates: Vec<u64> = vec![316_000, 562_000, 1_000_000, 1_780_000, 3_160_000];
|
|
let scan_rates: Vec<u64> = vec![500000, 629463, 792447, 997631, 1255943, 1581139, 1990536, 2505936, 3154787, 3971641, 5000000];
|
|
let scan_rates: Vec<u64> = vec![500000, 629463, 792447, 997631, 1255943, 1581139, 1990536, 2505936, 3154787, 3971641, 5000000];
|
|
|
|
|
|
|
|
+ let mut ip_sets = vec![];
|
|
|
|
|
|
- for scan_size in &scan_sizes {
|
|
|
|
- for hit_rate in &hit_rates {
|
|
|
|
- let data_args = DataArgs::from(seed, *scan_size, *hit_rate);
|
|
|
|
- if data_args.entries == 0 {
|
|
|
|
- info!("Skipping {}; no entries", data_args);
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
|
|
+ for (i, ip_data_set) in ip_data_sets.iter().enumerate() {
|
|
|
|
+ for scan_size in &scan_sizes {
|
|
|
|
+ let data_args = DataArgs::from(seed, *scan_size, i as u64);
|
|
|
|
|
|
info!("Building IP file for {}", data_args);
|
|
info!("Building IP file for {}", data_args);
|
|
- let (ip_file_path, subnet) = build_ip_file(data_args)
|
|
|
|
|
|
+ let (ip_file_path, subnet, entries, hitrate) = build_ip_file(ip_data_set, data_args)
|
|
.context(format!("Building ip file for {}", data_args))?;
|
|
.context(format!("Building ip file for {}", data_args))?;
|
|
|
|
+ ip_sets.push((*scan_size, i, ip_file_path, subnet, entries));
|
|
|
|
+ info!("{}: subnet: {}, entries: {}, hitrate: {}", data_args, subnet, entries, hitrate);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- info!("subnet for {} is {}", data_args, subnet);
|
|
|
|
|
|
+
|
|
|
|
+ for time in 0..times {
|
|
|
|
+ for (scan_size, data_index, ip_file_path, subnet, entries) in &ip_sets {
|
|
|
|
+ let data_args = DataArgs::from(seed, *scan_size, *data_index as u64);
|
|
|
|
|
|
for test_type in &false_positive_rates {
|
|
for test_type in &false_positive_rates {
|
|
let filter_types = match test_type {
|
|
let filter_types = match test_type {
|
|
@@ -94,7 +173,7 @@ pub fn run() -> Result<(), anyhow::Error> {
|
|
};
|
|
};
|
|
|
|
|
|
for filter_type in filter_types {
|
|
for filter_type in filter_types {
|
|
- let bloom_args = FilterArgs::from(data_args, *test_type, *filter_type);
|
|
|
|
|
|
+ let bloom_args = FilterArgs::from(data_args, *test_type, *filter_type, *entries);
|
|
info!("Building binaries for {} {}", data_args, bloom_args);
|
|
info!("Building binaries for {} {}", data_args, bloom_args);
|
|
build_binaries(data_args, bloom_args)
|
|
build_binaries(data_args, bloom_args)
|
|
.context(format!("Failed to build binaries for {} {}", data_args, bloom_args))?;
|
|
.context(format!("Failed to build binaries for {} {}", data_args, bloom_args))?;
|
|
@@ -113,7 +192,7 @@ pub fn run() -> Result<(), anyhow::Error> {
|
|
|
|
|
|
for scan_rate in &scan_rates {
|
|
for scan_rate in &scan_rates {
|
|
let scan_args = ScanArgs::new(*scan_rate);
|
|
let scan_args = ScanArgs::new(*scan_rate);
|
|
- let args = BenchArgs {data_args, bloom_filter_args: bloom_args, scan_args};
|
|
|
|
|
|
+ let args = BenchArgs {n: time, data_args, bloom_filter_args: bloom_args, scan_args};
|
|
|
|
|
|
let run_output = (|| {
|
|
let run_output = (|| {
|
|
fs::create_dir_all(args.wd_path())
|
|
fs::create_dir_all(args.wd_path())
|
|
@@ -142,7 +221,7 @@ pub fn run() -> Result<(), anyhow::Error> {
|
|
}
|
|
}
|
|
|
|
|
|
info!("Running zmap for {}", args);
|
|
info!("Running zmap for {}", args);
|
|
- let zmap_result = run_zmap(args, subnet)
|
|
|
|
|
|
+ let zmap_result = run_zmap(args, *subnet)
|
|
.context(format!("Running zmap for {}", args));
|
|
.context(format!("Running zmap for {}", args));
|
|
if let Err(e) = zmap_result {
|
|
if let Err(e) = zmap_result {
|
|
return Err((handle, e));
|
|
return Err((handle, e));
|
|
@@ -231,46 +310,45 @@ fn clean() -> anyhow::Result<()> {
|
|
Ok(())
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
|
|
-fn next_ip(rng: &mut SmallRng, mask: u32) -> u32 {
|
|
|
|
- loop {
|
|
|
|
- let ip = rng.next_u32() & mask;
|
|
|
|
- if ip & 0xff000000 != 0x7f000000 {
|
|
|
|
- // can not have ips in 127.0.0.0/8
|
|
|
|
- break ip;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-fn build_ip_file(data_args: DataArgs) -> anyhow::Result<(PathBuf, Ipv4Addr)> {
|
|
|
|
|
|
+fn build_ip_file(data_set: &IpDataSet, data_args: DataArgs) -> anyhow::Result<(PathBuf, Ipv4Addr, u64, f64)> {
|
|
let mut path = PathBuf::new();
|
|
let mut path = PathBuf::new();
|
|
path.push(BENCH_BASE_PATH);
|
|
path.push(BENCH_BASE_PATH);
|
|
path.push(BENCH_DATA_PATH);
|
|
path.push(BENCH_DATA_PATH);
|
|
|
|
+ path.push("build");
|
|
path.push(data_args.rel_path());
|
|
path.push(data_args.rel_path());
|
|
|
|
+ let mut info_path = path.clone();
|
|
path.push("ips.txt");
|
|
path.push("ips.txt");
|
|
|
|
+ info_path.push("ips-info.json");
|
|
|
|
|
|
fs::create_dir_all(path.parent().unwrap())?;
|
|
fs::create_dir_all(path.parent().unwrap())?;
|
|
|
|
|
|
- let ip_file = File::create(&path)?;
|
|
|
|
- let mut writer = BufWriter::new(ip_file);
|
|
|
|
|
|
+ let ip_set = data_set.generate_subnet(data_args.scan_subnet_size as usize, data_args.seed)?;
|
|
|
|
|
|
- let mut rng = SmallRng::seed_from_u64(data_args.seed);
|
|
|
|
|
|
+ let subnet = u32::from(*ip_set.iter().next().ok_or(anyhow!("dataset empty: {:?}", data_set.path))?) & ((((1u64 << 32u64) - 1) << data_args.scan_subnet_size) as u32);
|
|
|
|
+ let subnet = Ipv4Addr::from(subnet);
|
|
|
|
|
|
- let lower_subnet_mask = ((1u64 << (data_args.scan_subnet_size)) - 1u64) as u32;
|
|
|
|
- let upper_subnet_mask = u32::MAX - lower_subnet_mask;
|
|
|
|
|
|
+ let entries = ip_set.len() as u64;
|
|
|
|
+ let hitrate = (entries as f64) / ((1u64 << data_args.scan_subnet_size) as f64);
|
|
|
|
|
|
- let subnet = next_ip(&mut rng, upper_subnet_mask);
|
|
|
|
|
|
+ let ip_file = File::create(&path)?;
|
|
|
|
+ let mut writer = BufWriter::new(ip_file);
|
|
|
|
|
|
- for _ in 0..data_args.entries {
|
|
|
|
- let ip = subnet | next_ip(&mut rng, lower_subnet_mask);
|
|
|
|
- writer.write(Ipv4Addr::from(ip).to_string().as_bytes())?;
|
|
|
|
|
|
+ for ip in ip_set.into_iter() {
|
|
|
|
+ writer.write(ip.to_string().as_bytes())?;
|
|
writer.write(b"\n")?;
|
|
writer.write(b"\n")?;
|
|
}
|
|
}
|
|
|
|
|
|
- Ok((path, Ipv4Addr::from(subnet)))
|
|
|
|
|
|
+ let mut ip_info_file = File::create(&info_path)?;
|
|
|
|
+ ip_info_file.write_all(format!("{{\"subnet\": \"{}\", \"entries\": {}, \"hitrate\": {}}}", subnet.to_string(), entries, hitrate).as_bytes())?;
|
|
|
|
+
|
|
|
|
+ Ok((path, Ipv4Addr::from(subnet), entries, hitrate))
|
|
}
|
|
}
|
|
|
|
|
|
fn build_binaries(data_args: DataArgs, bloom_args: FilterArgs) -> anyhow::Result<()> {
|
|
fn build_binaries(data_args: DataArgs, bloom_args: FilterArgs) -> anyhow::Result<()> {
|
|
let bin_path = BenchArgs::bin_bin_path(data_args, bloom_args);
|
|
let bin_path = BenchArgs::bin_bin_path(data_args, bloom_args);
|
|
|
|
+ if bin_path.exists() {
|
|
|
|
+ return Ok(())
|
|
|
|
+ }
|
|
fs::create_dir_all(&bin_path).context("Failed to create bench dir")?;
|
|
fs::create_dir_all(&bin_path).context("Failed to create bench dir")?;
|
|
let output = Command::new("cargo")
|
|
let output = Command::new("cargo")
|
|
.args([
|
|
.args([
|
|
@@ -294,13 +372,16 @@ fn build_binaries(data_args: DataArgs, bloom_args: FilterArgs) -> anyhow::Result
|
|
Ok(())
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
|
|
-fn build_filter(data_args: DataArgs, bloom_args: FilterArgs, filter_type: FilterType, ip_file_path: &Path) -> anyhow::Result<PathBuf> {
|
|
|
|
|
|
+fn build_filter(data_args: DataArgs, filter_args: FilterArgs, filter_type: FilterType, ip_file_path: &Path) -> anyhow::Result<PathBuf> {
|
|
let filter_file = match filter_type {
|
|
let filter_file = match filter_type {
|
|
Bloom => "ips.bfb",
|
|
Bloom => "ips.bfb",
|
|
Bitmap => "ips.fb",
|
|
Bitmap => "ips.fb",
|
|
};
|
|
};
|
|
|
|
|
|
- let path = BenchArgs::bin_wd_path(data_args, bloom_args).join(filter_file);
|
|
|
|
|
|
+ let path = BenchArgs::bin_wd_path(data_args, filter_args).join(filter_file);
|
|
|
|
+ if path.exists() {
|
|
|
|
+ return Ok(path)
|
|
|
|
+ }
|
|
fs::create_dir_all(path.parent().unwrap()).context("Failed to create bench dir")?;
|
|
fs::create_dir_all(path.parent().unwrap()).context("Failed to create bench dir")?;
|
|
|
|
|
|
let filter_type_string = filter_type.to_string().to_lowercase();
|
|
let filter_type_string = filter_type.to_string().to_lowercase();
|
|
@@ -308,13 +389,13 @@ fn build_filter(data_args: DataArgs, bloom_args: FilterArgs, filter_type: Filter
|
|
let output = Command::new("/usr/bin/time")
|
|
let output = Command::new("/usr/bin/time")
|
|
.args([
|
|
.args([
|
|
// time args
|
|
// time args
|
|
- "-o", BenchArgs::bin_wd_path(data_args, bloom_args).join("filter_extern_time.json").to_str().unwrap(),
|
|
|
|
|
|
+ "-o", BenchArgs::bin_wd_path(data_args, filter_args).join("filter_extern_time.json").to_str().unwrap(),
|
|
"--format", "{\"clock\": %e, \"cpu_p\": \"%P\", \"kernel_s\": %S, \"user_s\": %U}",
|
|
"--format", "{\"clock\": %e, \"cpu_p\": \"%P\", \"kernel_s\": %S, \"user_s\": %U}",
|
|
|
|
|
|
// actual command
|
|
// actual command
|
|
- BenchArgs::bin_bin_path(data_args, bloom_args).join("tools/build_filter").to_str().unwrap(),
|
|
|
|
|
|
+ BenchArgs::bin_bin_path(data_args, filter_args).join("tools/build_filter").to_str().unwrap(),
|
|
"--force",
|
|
"--force",
|
|
- "--timing-path", BenchArgs::bin_wd_path(data_args, bloom_args).join("filter_intern_time.json").to_str().unwrap(),
|
|
|
|
|
|
+ "--timing-path", BenchArgs::bin_wd_path(data_args, filter_args).join("filter_intern_time.json").to_str().unwrap(),
|
|
filter_type_string.as_str(),
|
|
filter_type_string.as_str(),
|
|
ip_file_path.to_str().unwrap(),
|
|
ip_file_path.to_str().unwrap(),
|
|
path.to_str().unwrap()
|
|
path.to_str().unwrap()
|
|
@@ -326,7 +407,7 @@ fn build_filter(data_args: DataArgs, bloom_args: FilterArgs, filter_type: Filter
|
|
.output()
|
|
.output()
|
|
.context("Failed to run build_filter binary")?;
|
|
.context("Failed to run build_filter binary")?;
|
|
|
|
|
|
- let log_path = BenchArgs::bin_log_path(data_args, bloom_args);
|
|
|
|
|
|
+ let log_path = BenchArgs::bin_log_path(data_args, filter_args);
|
|
log_both(&log_path, &mut output.stderr.as_slice(), &mut output.stdout.as_slice(), "build-filter")?;
|
|
log_both(&log_path, &mut output.stderr.as_slice(), &mut output.stdout.as_slice(), "build-filter")?;
|
|
ensure!(output.status.success(), CommandError::new(output, log_path));
|
|
ensure!(output.status.success(), CommandError::new(output, log_path));
|
|
|
|
|
|
@@ -351,8 +432,6 @@ fn load_xdp(bench_args: BenchArgs, filter_path: &Path) -> Result<(process::Child
|
|
"--filter-type", filter_type.as_str(),
|
|
"--filter-type", filter_type.as_str(),
|
|
]);
|
|
]);
|
|
|
|
|
|
- println!("{:?}", args);
|
|
|
|
-
|
|
|
|
let mut handle = Command::new(args.remove(0))
|
|
let mut handle = Command::new(args.remove(0))
|
|
.args(args)
|
|
.args(args)
|
|
.env("RUST_LOG", "info")
|
|
.env("RUST_LOG", "info")
|
|
@@ -516,7 +595,7 @@ fn run_zmap(bench_args: BenchArgs, subnet: Ipv4Addr) -> anyhow::Result<process::
|
|
let seed = bench_args.data_args.seed.to_string();
|
|
let seed = bench_args.data_args.seed.to_string();
|
|
let mut args = Vec::from(PRIVILEGE_RUNNER);
|
|
let mut args = Vec::from(PRIVILEGE_RUNNER);
|
|
args.extend_from_slice(&[
|
|
args.extend_from_slice(&[
|
|
- "zmap",
|
|
|
|
|
|
+ "./zmap",
|
|
subnet_string.as_str(),
|
|
subnet_string.as_str(),
|
|
"--target-port=80",
|
|
"--target-port=80",
|
|
"--interface", interface,
|
|
"--interface", interface,
|
|
@@ -526,7 +605,7 @@ fn run_zmap(bench_args: BenchArgs, subnet: Ipv4Addr) -> anyhow::Result<process::
|
|
"--sender-threads=7",
|
|
"--sender-threads=7",
|
|
"--cooldown-time=1",
|
|
"--cooldown-time=1",
|
|
"--seed", seed.as_str(),
|
|
"--seed", seed.as_str(),
|
|
- "--blacklist-file=blocklist",
|
|
|
|
|
|
+ "--blocklist-file=blocklist",
|
|
"--max-sendto-failures=-1"
|
|
"--max-sendto-failures=-1"
|
|
]);
|
|
]);
|
|
let output = Command::new(args.remove(0))
|
|
let output = Command::new(args.remove(0))
|