#![allow(dead_code)]
use std::env;
use std::error::Error;
use std::fs;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::path::{Path, PathBuf};
use std::time::Duration;
use crafter::prelude::*;
pub type ExampleResult<T> = std::result::Result<T, Box<dyn Error>>;
pub const EXAMPLE_IFACE: &str = "dry-run0";
pub const LOCAL_IPV4: Ipv4Addr = Ipv4Addr::new(192, 0, 2, 10);
pub const REMOTE_IPV4: Ipv4Addr = Ipv4Addr::new(198, 51, 100, 20);
pub const GATEWAY_IPV4: Ipv4Addr = Ipv4Addr::new(192, 0, 2, 1);
pub const INTERMEDIARY_IPV4: Ipv4Addr = Ipv4Addr::new(203, 0, 113, 1);
pub const LOCAL_IPV6: Ipv6Addr = Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x0010);
pub const REMOTE_IPV6: Ipv6Addr = Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x0020);
pub const LOCAL_MAC: MacAddr = MacAddr::new([0x02, 0x00, 0x5e, 0x00, 0x53, 0x01]);
pub const REMOTE_MAC: MacAddr = MacAddr::new([0x02, 0x00, 0x5e, 0x00, 0x53, 0x02]);
pub const EXAMPLE_PCAP_LINK_TYPE: LinkType = LinkType::Ethernet;
pub const fn local_ipv4() -> Ipv4Addr {
LOCAL_IPV4
}
pub const fn remote_ipv4() -> Ipv4Addr {
REMOTE_IPV4
}
pub const fn gateway_ipv4() -> Ipv4Addr {
GATEWAY_IPV4
}
pub const fn intermediary_ipv4() -> Ipv4Addr {
INTERMEDIARY_IPV4
}
pub const fn local_ipv6() -> Ipv6Addr {
LOCAL_IPV6
}
pub const fn remote_ipv6() -> Ipv6Addr {
REMOTE_IPV6
}
pub const fn local_mac() -> MacAddr {
LOCAL_MAC
}
pub const fn remote_mac() -> MacAddr {
REMOTE_MAC
}
pub fn print_help_if_requested(usage: &str) -> bool {
if env::args().any(|arg| arg == "--help" || arg == "-h") {
println!("{usage}");
true
} else {
false
}
}
pub fn arg_value(name: &str) -> Option<String> {
let mut args = env::args().skip(1);
while let Some(arg) = args.next() {
if arg == name {
return args.next();
}
if let Some(value) = arg.strip_prefix(&format!("{name}=")) {
return Some(value.to_string());
}
}
None
}
pub fn arg_or(name: &str, default: &str) -> String {
arg_value(name).unwrap_or_else(|| default.to_string())
}
pub fn flag_present(name: &str) -> bool {
env::args().skip(1).any(|arg| arg == name)
}
pub const ADVANCED_LIVE_ACK_FLAG: &str = "--i-understand-isolated-lab";
pub const LIVE_WIRE_ENV: &str = "LIBCRAFTER_ENDPOINT";
pub fn live_wire_marker_present() -> bool {
env::var(LIVE_WIRE_ENV)
.map(|value| {
matches!(
value.trim().to_ascii_lowercase().as_str(),
"1" | "true" | "yes" | "isolated"
)
})
.unwrap_or(false)
}
pub fn require_advanced_wire_endpoint(example: &str) -> ExampleResult<()> {
if !flag_present("--live") {
return Ok(());
}
let mut missing = Vec::new();
if !flag_present(ADVANCED_LIVE_ACK_FLAG) {
missing.push(format!("CLI acknowledgement flag {ADVANCED_LIVE_ACK_FLAG}"));
}
if !live_wire_marker_present() {
missing.push(format!("environment marker {LIVE_WIRE_ENV}=1"));
}
if missing.is_empty() {
Ok(())
} else {
Err(format!(
"{example} can change network traffic and is only enabled on an isolated wire endpoint; missing {}",
missing.join(" and ")
)
.into())
}
}
pub fn live_mode(example: &str) -> ExampleResult<bool> {
let live = flag_present("--live");
if live {
require_advanced_wire_endpoint(example)?;
}
Ok(live)
}
pub fn print_advanced_safety(example: &str, live: bool) {
println!("example: {example}");
println!("mode: {}", if live { "wire-endpoint" } else { "dry-run" });
if live {
println!("safety: --live, {ADVANCED_LIVE_ACK_FLAG}, and {LIVE_WIRE_ENV}=1 were required");
} else {
println!("safety: no packets are transmitted without --live, {ADVANCED_LIVE_ACK_FLAG}, and {LIVE_WIRE_ENV}=1");
}
}
pub fn print_packet_plan(label: &str, packet: &Packet) -> ExampleResult<()> {
let compiled = packet.compile()?;
println!("{label}: {}", packet.summary());
println!("{label} bytes: {}", compiled.len());
println!("{label} hexdump:\n{}", compiled.hexdump());
Ok(())
}
pub fn print_batch_send_report(label: &str, report: &BatchSendReport) {
println!("batch: {label}");
println!("dry-run: {}", report.is_dry_run());
println!("requests: {}", report.len());
for entry in report.entries() {
for (attempt, send) in entry.send_reports().iter().enumerate() {
println!(
"request {} attempt {} bytes {} target {:?}",
entry.request_index(),
attempt + 1,
send.bytes_sent(),
send.plan().target()
);
}
}
}
pub fn parse_u16_arg(name: &str, default: u16) -> ExampleResult<u16> {
Ok(match arg_value(name) {
Some(value) => value.parse()?,
None => default,
})
}
pub fn parse_usize_arg(name: &str, default: usize) -> ExampleResult<usize> {
Ok(match arg_value(name) {
Some(value) => value.parse()?,
None => default,
})
}
pub fn parse_ipv4_arg(name: &str, default: Ipv4Addr) -> ExampleResult<Ipv4Addr> {
Ok(match arg_value(name) {
Some(value) => value.parse()?,
None => default,
})
}
pub fn parse_ipv6_arg(name: &str, default: Ipv6Addr) -> ExampleResult<Ipv6Addr> {
Ok(match arg_value(name) {
Some(value) => value.parse()?,
None => default,
})
}
pub fn parse_mac_arg(name: &str, default: MacAddr) -> ExampleResult<MacAddr> {
Ok(match arg_value(name) {
Some(value) => value.parse()?,
None => default,
})
}
pub fn send_options(iface: &str, live: bool, link_layer: bool) -> SendOptions {
let options = if link_layer {
SendOptions::new().iface(iface).link_layer()
} else {
SendOptions::new().iface(iface).network_layer()
};
if live {
options.live()
} else {
options.dry_run()
}
}
pub fn send_recv_options(iface: &str, live: bool, link_layer: bool) -> SendRecv {
let options = if link_layer {
SendRecv::new().iface(iface).link_layer()
} else {
SendRecv::new().iface(iface).network_layer()
};
let options = options.timeout(Duration::from_secs(1)).retries(1);
if live {
options.live()
} else {
options.dry_run()
}
}
pub fn print_send_report(label: &str, packet: &Packet, report: &SendReport) {
println!("example: {label}");
println!(
"mode: {}",
if report.is_dry_run() {
"dry-run"
} else {
"live"
}
);
println!("interface: {}", report.plan().interface());
println!("target: {:?}", report.plan().target());
println!("bytes planned: {}", report.bytes_sent());
println!("summary: {}", packet.summary());
println!("hexdump:\n{}", report.plan().compiled_packet().hexdump());
}
pub fn print_send_recv_report(label: &str, packet: &Packet, report: &SendRecvReport) {
println!("example: {label}");
println!("attempts: {}", report.attempts());
println!("timed out: {}", report.timed_out());
println!(
"effective filter: {}",
report.effective_filter().unwrap_or("")
);
println!("summary: {}", packet.summary());
if let Some(reply) = report.reply() {
println!("reply: {}", reply.summary());
}
}
pub fn default_target_path(file_name: &str) -> PathBuf {
PathBuf::from("target").join(file_name)
}
pub fn ensure_parent(path: &Path) -> ExampleResult<()> {
if let Some(parent) = path.parent() {
if !parent.as_os_str().is_empty() {
fs::create_dir_all(parent)?;
}
}
Ok(())
}
pub fn example_ipv4_icmp_packet(src: Ipv4Addr, dst: Ipv4Addr, payload: &str) -> Packet {
Ipv4::new().src(src).dst(dst).id(0x1234).dont_fragment(true)
/ Icmpv4::echo_request().id(0x4242).seq(1)
/ Raw::from(payload)
}
pub fn example_ethernet_tcp_packet(
src_ip: Ipv4Addr,
dst_ip: Ipv4Addr,
src_mac: &str,
dst_mac: &str,
src_port: u16,
dst_port: u16,
payload: &str,
) -> ExampleResult<Packet> {
Ok(Ethernet::new().src_str(src_mac)?.dst_str(dst_mac)?
/ Ipv4::new()
.src(src_ip)
.dst(dst_ip)
.id(0x2222)
.ipv4_protocol(Ipv4Protocol::Tcp)
/ Tcp::new()
.sport(src_port)
.dport(dst_port)
.flags(TCP_FLAG_SYN | TCP_FLAG_CWR | TCP_FLAG_ECE)
/ Raw::from(payload))
}
pub fn example_pcap_packets(count: usize) -> ExampleResult<Vec<Packet>> {
const BASE_TCP_SOURCE_PORT: u16 = 62_345;
let src_ip = LOCAL_IPV4;
let dst_ip = REMOTE_IPV4;
let src_mac = LOCAL_MAC.to_string();
let dst_mac = REMOTE_MAC.to_string();
let mut packets = Vec::with_capacity(count);
for offset in 0..count {
let port_offset = u16::try_from(offset).unwrap_or(u16::MAX - BASE_TCP_SOURCE_PORT);
let payload = format!("SomeTCPPayload-{offset}\n");
packets.push(example_ethernet_tcp_packet(
src_ip,
dst_ip,
&src_mac,
&dst_mac,
BASE_TCP_SOURCE_PORT.saturating_add(port_offset),
80,
&payload,
)?);
}
Ok(packets)
}
pub fn write_example_pcap(path: &Path, count: usize) -> ExampleResult<Vec<Packet>> {
ensure_parent(path)?;
let packets = example_pcap_packets(count)?;
let mut writer = PacketWire::pcap_recorder(path, EXAMPLE_PCAP_LINK_TYPE)
.open()?
.writer()?;
for packet in &packets {
writer.write_record(&PacketRecord::new(packet.clone()))?;
}
Ok(packets)
}