Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
"crates/trippy-packet",
"crates/trippy-privilege",
"crates/trippy-dns",
"crates/trippy-sim",
"examples/*",
]

Expand All @@ -29,6 +30,7 @@ trippy-core = { version = "0.14.0-dev", path = "crates/trippy-core" }
trippy-privilege = { version = "0.14.0-dev", path = "crates/trippy-privilege" }
trippy-dns = { version = "0.14.0-dev", path = "crates/trippy-dns" }
trippy-packet = { version = "0.14.0-dev", path = "crates/trippy-packet" }
trippy-sim = { version = "0.14.0-dev", path = "crates/trippy-sim" }
anyhow = "1.0.91"
arrayvec = { version = "0.7.6", default-features = false }
bitflags = "2.10.0"
Expand Down Expand Up @@ -99,6 +101,7 @@ cast_precision_loss = "allow"
bool_assert_comparison = "allow"
missing_const_for_fn = "allow"
struct_field_names = "allow"
missing_panics_doc = "allow"
cognitive_complexity = "allow"

[profile.release]
Expand Down
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ RUN mkdir -p /app/crates/trippy-core/src
RUN mkdir -p /app/crates/trippy-dns/src
RUN mkdir -p /app/crates/trippy-packet/src
RUN mkdir -p /app/crates/trippy-privilege/src
RUN mkdir -p /app/crates/trippy-sim/src
COPY crates/trippy/Cargo.toml /app/crates/trippy/Cargo.toml
COPY crates/trippy-tui/Cargo.toml /app/crates/trippy-tui/Cargo.toml
COPY crates/trippy-core/Cargo.toml /app/crates/trippy-core/Cargo.toml
COPY crates/trippy-dns/Cargo.toml /app/crates/trippy-dns/Cargo.toml
COPY crates/trippy-packet/Cargo.toml /app/crates/trippy-packet/Cargo.toml
COPY crates/trippy-privilege/Cargo.toml /app/crates/trippy-privilege/Cargo.toml
COPY crates/trippy-sim/Cargo.toml /app/crates/trippy-sim/Cargo.toml
COPY examples/ /app/examples/

# dummy build to cache dependencies
Expand All @@ -24,6 +26,7 @@ RUN touch /app/crates/trippy-core/src/lib.rs
RUN touch /app/crates/trippy-dns/src/lib.rs
RUN touch /app/crates/trippy-packet/src/lib.rs
RUN touch /app/crates/trippy-privilege/src/lib.rs
RUN touch /app/crates/trippy-sim/src/lib.rs
RUN cargo build --release --target=x86_64-unknown-linux-musl --package trippy

# copy the actual application code and build
Expand Down
7 changes: 1 addition & 6 deletions crates/trippy-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,17 @@ widestring.workspace = true
windows-sys = { workspace = true, features = ["Win32_Foundation", "Win32_Networking_WinSock", "Win32_System_IO", "Win32_NetworkManagement_IpHelper", "Win32_NetworkManagement_Ndis", "Win32_System_IO", "Win32_System_Threading", "Win32_Security"] }

[dev-dependencies]
trippy-sim.workspace = true
anyhow.workspace = true
hex-literal.workspace = true
ipnetwork.workspace = true
mockall.workspace = true
rand.workspace = true
serde = { workspace = true, default-features = false, features = ["derive"] }
test-case.workspace = true
tokio-util.workspace = true
tokio = { workspace = true, features = ["full"] }
toml = { workspace = true, default-features = false, features = ["parse"] }
tracing-subscriber = { workspace = true, default-features = false, features = ["env-filter", "fmt"] }

# see https://github.com/meh/rust-tun/pull/74
[target.'cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))'.dev-dependencies]
tun = { workspace = true, features = ["async"] }

[features]
# Enable simulation integration tests
sim-tests = []
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use crate::simulation::Simulation;
use crate::tun_device::tun;
use crate::{network, tracer};
#![cfg(all(
feature = "sim-tests",
any(target_os = "linux", target_os = "macos", target_os = "windows")
))]
#![allow(clippy::needless_pass_by_value, clippy::redundant_clone)]

use std::sync::{Arc, Mutex, OnceLock};
use test_case::test_case;
use tokio::runtime::Runtime;
use tokio_util::sync::CancellationToken;
use tracing::{error, info, warn};
use tracing_subscriber::fmt::format::FmtSpan;
use trippy_sim::Simulation;

/// The maximum number of attempts for each test.
const MAX_ATTEMPTS: usize = 5;
Expand All @@ -17,7 +20,9 @@ pub fn runtime() -> &'static Arc<Mutex<Runtime>> {
RUNTIME.get_or_init(|| {
tracing_subscriber::fmt()
.with_span_events(FmtSpan::NONE)
.with_env_filter("trippy=off,sim=debug")
.with_env_filter(
"sim=debug,trippy_sim::tracer=debug,trippy_sim::network=debug,trippy_core=info",
)
.init();

let runtime = tokio::runtime::Builder::new_multi_thread()
Expand All @@ -30,7 +35,7 @@ pub fn runtime() -> &'static Arc<Mutex<Runtime>> {

macro_rules! sim {
($path:expr) => {{
let data = include_str!(concat!("../resources/simulation/", $path));
let data = include_str!(concat!("resources/simulation/", $path));
toml::from_str(data)?
}};
}
Expand Down Expand Up @@ -63,7 +68,6 @@ fn test_simulation_macos(simulation: Simulation) -> anyhow::Result<()> {

fn run_simulation_with_retry(simulation: Simulation) -> anyhow::Result<()> {
let runtime = runtime().lock().unwrap();
let simulation = Arc::new(simulation);
let name = simulation.name.clone();
if !trippy_privilege::Privilege::discover()?.has_privileges() {
// Skip if the current test as the user cannot create a tun device.
Expand All @@ -72,7 +76,7 @@ fn run_simulation_with_retry(simulation: Simulation) -> anyhow::Result<()> {
}
for attempt in 1..=MAX_ATTEMPTS {
info!("start simulating {} [attempt #{}]", name, attempt);
if let Err(err) = runtime.block_on(run_simulation(simulation.clone())) {
if let Err(err) = runtime.block_on(trippy_sim::simulate(simulation.clone())) {
error!("failed simulating {} {} [attempt #{}]", name, err, attempt);
} else {
info!("end simulating {} [attempt #{}]", name, attempt);
Expand All @@ -81,11 +85,3 @@ fn run_simulation_with_retry(simulation: Simulation) -> anyhow::Result<()> {
}
anyhow::bail!("failed simulating {name} after {MAX_ATTEMPTS} attempts")
}

async fn run_simulation(sim: Arc<Simulation>) -> anyhow::Result<()> {
let tun = tun();
let token = CancellationToken::new();
let handle = tokio::spawn(network::run(tun.clone(), sim.clone(), token.clone()));
tokio::task::spawn_blocking(move || tracer::Tracer::new(sim, token).trace()).await??;
handle.await?
}
9 changes: 0 additions & 9 deletions crates/trippy-core/tests/sim/main.rs

This file was deleted.

34 changes: 34 additions & 0 deletions crates/trippy-sim/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[package]
name = "trippy-sim"
description = "A network simulator for Trippy"
version.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
readme.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
keywords.workspace = true
categories.workspace = true

[dependencies]
trippy-core.workspace = true
trippy-packet.workspace = true
trippy-privilege.workspace = true
anyhow.workspace = true
clap.workspace = true
ipnetwork.workspace = true
serde = { workspace = true, default-features = false, features = ["derive"] }
tokio = { workspace = true, features = ["full"] }
tokio-util.workspace = true
toml = { workspace = true, default-features = false, features = ["parse"] }
tracing-subscriber = { workspace = true, default-features = false, features = ["env-filter", "fmt"] }
tracing.workspace = true

# see https://github.com/meh/rust-tun/pull/74
[target.'cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))'.dependencies]
tun = { workspace = true, features = ["async"] }

[lints]
workspace = true
31 changes: 31 additions & 0 deletions crates/trippy-sim/src/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#![cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]

use anyhow::Context;
use clap::Parser;
use tracing_subscriber::fmt::format::FmtSpan;
use trippy_privilege::Privilege;
use trippy_sim::{Simulation, simulate};

/// Trace a route to a host and record statistics
#[allow(clippy::doc_markdown)]
#[derive(Parser, Debug)]
#[command(name = "trip", author, version, about, long_about = None, arg_required_else_help(true))]
pub struct Args {
/// A simulation file to run.
pub simulation: String,
}

pub async fn run() -> anyhow::Result<()> {
let args = Args::parse();
tracing_subscriber::fmt()
.with_span_events(FmtSpan::NONE)
.with_env_filter("debug")
.init();
if !Privilege::discover()?.has_privileges() {
return Err(anyhow::anyhow!("Privileges required to run this command"));
}
let simulation_file =
std::fs::read_to_string(&args.simulation).context(args.simulation.clone())?;
let sim: Simulation = toml::from_str(&simulation_file)?;
simulate(sim).await
}
21 changes: 21 additions & 0 deletions crates/trippy-sim/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#![cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]

mod network;
mod simulation;
mod tracer;
mod tun_device;

use crate::tun_device::tun;
pub use simulation::Simulation;
use std::sync::Arc;
use tokio_util::sync::CancellationToken;

/// Run a simulation.
pub async fn simulate(simulation: Simulation) -> anyhow::Result<()> {
let sim = Arc::new(simulation);
let tun = tun();
let token = CancellationToken::new();
let handle = tokio::spawn(network::run(tun.clone(), sim.clone(), token.clone()));
tokio::task::spawn_blocking(move || tracer::Tracer::new(sim.clone(), token).trace()).await??;
handle.await?
}
8 changes: 8 additions & 0 deletions crates/trippy-sim/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
mod app;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
app::run().await?;
Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ pub async fn run(
}
}

// if the ttl is greater than the largest ttl in our sim we will reply as the last node in
// the sim
// if the ttl is greater than the largest ttl in our simulation we will reply as the last node in
// the simulation
let index = std::cmp::min(usize::from(ipv4.get_ttl()) - 1, sim.hops.len() - 1);
let (reply_addr, reply_delay_ms) = match sim.hops[index].resp {
Response::NoResponse => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::net::IpAddr;
use trippy_core::Port;

/// A simulated trace.
#[derive(Debug, Deserialize)]
#[derive(Debug, Clone, Deserialize)]
pub struct Simulation {
pub name: String,
pub rounds: Option<usize>,
Expand All @@ -28,7 +28,8 @@ pub struct Simulation {
}

impl Simulation {
pub fn latest_ttl(&self) -> u8 {
#[must_use]
pub(crate) fn latest_ttl(&self) -> u8 {
if self.hops.is_empty() {
0
} else {
Expand All @@ -38,7 +39,7 @@ impl Simulation {
}

/// A simulated hop.
#[derive(Debug, Deserialize)]
#[derive(Debug, Clone, Deserialize)]
pub struct Hop {
/// The simulated time-to-live (TTL).
pub ttl: u8,
Expand All @@ -47,7 +48,7 @@ pub struct Hop {
}

/// A simulated probe response.
#[derive(Debug, Deserialize)]
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "tag")]
pub enum Response {
/// Simulate a hop which does not response to probes.
Expand All @@ -57,7 +58,7 @@ pub enum Response {
}

/// A simulated probe response with a single addr and fixed ttl.
#[derive(Debug, Deserialize)]
#[derive(Debug, Clone, Deserialize)]
pub struct SingleHost {
/// The simulated host responding to the probe.
pub addr: IpAddr,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use trippy_core::{
};

// The length of time to wait after the completion of the tracing before
// cancelling the network simulator. This is needed to ensure that all
// canceling the network simulator. This is needed to ensure that all
// in-flight packets for the current test are sent or received prior to
// ending the round so that they are not incorrectly used in a subsequent
// test.
Expand Down Expand Up @@ -90,7 +90,7 @@ impl Tracer {
.map_err(anyhow::Error::from);
thread::sleep(CLEANUP_DELAY);
self.token.cancel();
// ensure both the tracer and the validator were successful.
// ensure both the tracer and the validation were successful.
tracer_res.and(result.replace(Ok(())))
}

Expand Down